diff --git a/contextmenu.py b/contextmenu.py
index 3b367ab2..c91da6a1 100644
--- a/contextmenu.py
+++ b/contextmenu.py
@@ -1,159 +1,158 @@
-# -*- coding: utf-8 -*-
-
-#################################################################################################
-
-import os
-import sys
-import urlparse
-
-import xbmc
-import xbmcaddon
-import xbmcgui
-
-addon_ = xbmcaddon.Addon(id='plugin.video.emby')
-addon_path = addon_.getAddonInfo('path').decode('utf-8')
-base_resource = xbmc.translatePath(os.path.join(addon_path, 'resources', 'lib')).decode('utf-8')
-sys.path.append(base_resource)
-
-import artwork
-import utils
-import clientinfo
-import downloadutils
-import librarysync
-import read_embyserver as embyserver
-import embydb_functions as embydb
-import kodidb_functions as kodidb
-import musicutils as musicutils
-import api
-
-def logMsg(msg, lvl=1):
-    utils.logMsg("%s %s" % ("EMBY", "Contextmenu"), msg, lvl)
-
-
-#Kodi contextmenu item to configure the emby settings
-#for now used to set ratings but can later be used to sync individual items etc.
-if __name__ == '__main__':
-    itemid = xbmc.getInfoLabel("ListItem.DBID").decode("utf-8")
-    itemtype = xbmc.getInfoLabel("ListItem.DBTYPE").decode("utf-8")
-    
-    emby = embyserver.Read_EmbyServer()
-    
-    embyid = ""
-    if not itemtype and xbmc.getCondVisibility("Container.Content(albums)"): itemtype = "album"
-    if not itemtype and xbmc.getCondVisibility("Container.Content(artists)"): itemtype = "artist"
-    if not itemtype and xbmc.getCondVisibility("Container.Content(songs)"): itemtype = "song"
-    if not itemtype and xbmc.getCondVisibility("Container.Content(pictures)"): itemtype = "picture"
-    
-    if (not itemid or itemid == "-1") and xbmc.getInfoLabel("ListItem.Property(embyid)"):
-        embyid = xbmc.getInfoLabel("ListItem.Property(embyid)")
-    else:
-        embyconn = utils.kodiSQL('emby')
-        embycursor = embyconn.cursor()
-        emby_db = embydb.Embydb_Functions(embycursor)
-        item = emby_db.getItem_byKodiId(itemid, itemtype)
-        embycursor.close()
-        if item: embyid = item[0]
-    
-    logMsg("Contextmenu opened for embyid: %s  - itemtype: %s" %(embyid,itemtype))
-
-    if embyid:
-        item = emby.getItem(embyid)
-        API = api.API(item)
-        userdata = API.getUserData()
-        likes = userdata['Likes']
-        favourite = userdata['Favorite']
-        
-        options=[]
-        if likes == True:
-            #clear like for the item
-            options.append(utils.language(30402))
-        if likes == False or likes == None:
-            #Like the item
-            options.append(utils.language(30403))
-        if likes == True or likes == None:
-            #Dislike the item
-            options.append(utils.language(30404)) 
-        if favourite == False:
-            #Add to emby favourites
-            options.append(utils.language(30405)) 
-        if favourite == True:
-            #Remove from emby favourites
-            options.append(utils.language(30406))
-        if itemtype == "song":
-            #Set custom song rating
-            options.append(utils.language(30407))
-        
-        #delete item
-        options.append(utils.language(30409))
-        
-        #addon settings
-        options.append(utils.language(30408))
-        
-        #display select dialog and process results
-        header = utils.language(30401)
-        ret = xbmcgui.Dialog().select(header, options)
-        if ret != -1:
-            if options[ret] == utils.language(30402):
-                emby.updateUserRating(embyid, deletelike=True)
-            if options[ret] == utils.language(30403):
-                emby.updateUserRating(embyid, like=True)
-            if options[ret] == utils.language(30404):
-                emby.updateUserRating(embyid, like=False)
-            if options[ret] == utils.language(30405):
-                emby.updateUserRating(embyid, favourite=True)
-            if options[ret] == utils.language(30406):
-                emby.updateUserRating(embyid, favourite=False)
-            if options[ret] == utils.language(30407):
-                kodiconn = utils.kodiSQL('music')
-                kodicursor = kodiconn.cursor()
-                query = ' '.join(("SELECT rating", "FROM song", "WHERE idSong = ?" ))
-                kodicursor.execute(query, (itemid,))
-                currentvalue = int(round(float(kodicursor.fetchone()[0]),0))
-                newvalue = xbmcgui.Dialog().numeric(0, "Set custom song rating (0-5)", str(currentvalue))
-                if newvalue:
-                    newvalue = int(newvalue)
-                    if newvalue > 5: newvalue = "5"
-                    if utils.settings('enableUpdateSongRating') == "true":
-                        musicutils.updateRatingToFile(newvalue, API.getFilePath())
-                    if utils.settings('enableExportSongRating') == "true":
-                        like, favourite, deletelike = musicutils.getEmbyRatingFromKodiRating(newvalue)
-                        emby.updateUserRating(embyid, like, favourite, deletelike)
-                    query = ' '.join(( "UPDATE song","SET rating = ?", "WHERE idSong = ?" ))
-                    kodicursor.execute(query, (newvalue,itemid,))
-                    kodiconn.commit()
-
-            if options[ret] == utils.language(30408):
-                #Open addon settings
-                xbmc.executebuiltin("Addon.OpenSettings(plugin.video.emby)")
-                
-            if options[ret] == utils.language(30409):
-                #delete item from the server
-                delete = True
-                if utils.settings('skipContextMenu') != "true":
-                    resp = xbmcgui.Dialog().yesno(
-                                            heading="Confirm delete",
-                                            line1=("Delete file from Emby Server? This will "
-                                                    "also delete the file(s) from disk!"))
-                    if not resp:
-                        logMsg("User skipped deletion for: %s." % embyid, 1)
-                        delete = False
-                
-                if delete:
-                    import downloadutils
-                    doUtils = downloadutils.DownloadUtils()
-                    url = "{server}/emby/Items/%s?format=json" % embyid
-                    logMsg("Deleting request: %s" % embyid, 0)
-                    doUtils.downloadUrl(url, type="DELETE")
-
-                '''if utils.settings('skipContextMenu') != "true":
-                    if xbmcgui.Dialog().yesno(
-                                        heading="Confirm delete",
-                                        line1=("Delete file on Emby Server? This will "
-                                                "also delete the file(s) from disk!")):
-                        import downloadutils
-                        doUtils = downloadutils.DownloadUtils()
-                        url = "{server}/emby/Items/%s?format=json" % embyid
-                        doUtils.downloadUrl(url, type="DELETE")'''
-            
-            xbmc.sleep(500)
+# -*- coding: utf-8 -*-
+
+#################################################################################################
+
+import os
+import sys
+import urlparse
+
+import xbmc
+import xbmcaddon
+import xbmcgui
+
+addon_ = xbmcaddon.Addon(id='plugin.video.emby')
+addon_path = addon_.getAddonInfo('path').decode('utf-8')
+base_resource = xbmc.translatePath(os.path.join(addon_path, 'resources', 'lib')).decode('utf-8')
+sys.path.append(base_resource)
+
+import artwork
+import utils
+import clientinfo
+import downloadutils
+import librarysync
+import read_embyserver as embyserver
+import embydb_functions as embydb
+import kodidb_functions as kodidb
+import musicutils as musicutils
+import api
+
+def logMsg(msg, lvl=1):
+    utils.logMsg("%s %s" % ("EMBY", "Contextmenu"), msg, lvl)
+
+
+#Kodi contextmenu item to configure the emby settings
+#for now used to set ratings but can later be used to sync individual items etc.
+if __name__ == '__main__':
+    itemid = xbmc.getInfoLabel("ListItem.DBID").decode("utf-8")
+    itemtype = xbmc.getInfoLabel("ListItem.DBTYPE").decode("utf-8")
+    
+    emby = embyserver.Read_EmbyServer()
+    
+    embyid = ""
+    if not itemtype and xbmc.getCondVisibility("Container.Content(albums)"): itemtype = "album"
+    if not itemtype and xbmc.getCondVisibility("Container.Content(artists)"): itemtype = "artist"
+    if not itemtype and xbmc.getCondVisibility("Container.Content(songs)"): itemtype = "song"
+    if not itemtype and xbmc.getCondVisibility("Container.Content(pictures)"): itemtype = "picture"
+    
+    if (not itemid or itemid == "-1") and xbmc.getInfoLabel("ListItem.Property(embyid)"):
+        embyid = xbmc.getInfoLabel("ListItem.Property(embyid)")
+    else:
+        embyconn = utils.kodiSQL('emby')
+        embycursor = embyconn.cursor()
+        emby_db = embydb.Embydb_Functions(embycursor)
+        item = emby_db.getItem_byKodiId(itemid, itemtype)
+        embycursor.close()
+        if item: embyid = item[0]
+    
+    logMsg("Contextmenu opened for embyid: %s  - itemtype: %s" %(embyid,itemtype))
+
+    if embyid:
+        item = emby.getItem(embyid)
+        API = api.API(item)
+        userdata = API.getUserData()
+        likes = userdata['Likes']
+        favourite = userdata['Favorite']
+        
+        options=[]
+        if likes == True:
+            #clear like for the item
+            options.append(utils.language(30402))
+        if likes == False or likes == None:
+            #Like the item
+            options.append(utils.language(30403))
+        if likes == True or likes == None:
+            #Dislike the item
+            options.append(utils.language(30404)) 
+        if favourite == False:
+            #Add to emby favourites
+            options.append(utils.language(30405)) 
+        if favourite == True:
+            #Remove from emby favourites
+            options.append(utils.language(30406))
+        if itemtype == "song":
+            #Set custom song rating
+            options.append(utils.language(30407))
+        
+        #delete item
+        options.append(utils.language(30409))
+        
+        #addon settings
+        options.append(utils.language(30408))
+        
+        #display select dialog and process results
+        header = utils.language(30401)
+        ret = xbmcgui.Dialog().select(header, options)
+        if ret != -1:
+            if options[ret] == utils.language(30402):
+                emby.updateUserRating(embyid, deletelike=True)
+            if options[ret] == utils.language(30403):
+                emby.updateUserRating(embyid, like=True)
+            if options[ret] == utils.language(30404):
+                emby.updateUserRating(embyid, like=False)
+            if options[ret] == utils.language(30405):
+                emby.updateUserRating(embyid, favourite=True)
+            if options[ret] == utils.language(30406):
+                emby.updateUserRating(embyid, favourite=False)
+            if options[ret] == utils.language(30407):
+                kodiconn = utils.kodiSQL('music')
+                kodicursor = kodiconn.cursor()
+                query = ' '.join(("SELECT rating", "FROM song", "WHERE idSong = ?" ))
+                kodicursor.execute(query, (itemid,))
+                currentvalue = int(round(float(kodicursor.fetchone()[0]),0))
+                newvalue = xbmcgui.Dialog().numeric(0, "Set custom song rating (0-5)", str(currentvalue))
+                if newvalue:
+                    newvalue = int(newvalue)
+                    if newvalue > 5: newvalue = "5"
+                    if utils.settings('enableUpdateSongRating') == "true":
+                        musicutils.updateRatingToFile(newvalue, API.getFilePath())
+                    if utils.settings('enableExportSongRating') == "true":
+                        like, favourite, deletelike = musicutils.getEmbyRatingFromKodiRating(newvalue)
+                        emby.updateUserRating(embyid, like, favourite, deletelike)
+                    query = ' '.join(( "UPDATE song","SET rating = ?", "WHERE idSong = ?" ))
+                    kodicursor.execute(query, (newvalue,itemid,))
+                    kodiconn.commit()
+
+            if options[ret] == utils.language(30408):
+                #Open addon settings
+                xbmc.executebuiltin("Addon.OpenSettings(plugin.video.emby)")
+                
+            if options[ret] == utils.language(30409):
+                #delete item from the server
+                delete = True
+                if utils.settings('skipContextMenu') != "true":
+                    resp = xbmcgui.Dialog().yesno(
+                                            heading="Confirm delete",
+                                            line1=("Delete file from Emby Server? This will "
+                                                    "also delete the file(s) from disk!"))
+                    if not resp:
+                        logMsg("User skipped deletion for: %s." % embyid, 1)
+                        delete = False
+                
+                if delete:
+                    import downloadutils
+                    doUtils = downloadutils.DownloadUtils()
+                    url = "{server}/emby/Items/%s?format=json" % embyid
+                    logMsg("Deleting request: %s" % embyid, 0)
+                    doUtils.downloadUrl(url, action_type="DELETE")
+
+                '''if utils.settings('skipContextMenu') != "true":
+                    if xbmcgui.Dialog().yesno(
+                                        heading="Confirm delete",
+                                        line1=("Delete file on Emby Server? This will "
+                                                "also delete the file(s) from disk!")):
+                        import downloadutils
+                        doUtils = downloadutils.DownloadUtils()
+                        doUtils.downloadUrl("{server}/emby/Items/%s?format=json" % embyid, action_type="DELETE")'''
+            
+            xbmc.sleep(500)
             xbmc.executebuiltin("Container.Update")
\ No newline at end of file
diff --git a/resources/language/English/strings.xml b/resources/language/English/strings.xml
index 776e06a1..78a94417 100644
--- a/resources/language/English/strings.xml
+++ b/resources/language/English/strings.xml
@@ -323,7 +323,7 @@
     <string id="33020">Gathering tv shows from:</string>
     <string id="33021">Gathering:</string>
     <string id="33022">Detected the database needs to be recreated for this version of Emby for Kodi. Proceed?</string>
-    <string id="33023">Emby for Kod may not work correctly until the database is reset.</string>
+    <string id="33023">Emby for Kodi may not work correctly until the database is reset.</string>
     <string id="33024">Cancelling the database syncing process. The current Kodi version is unsupported.</string>
     <string id="33025">completed in:</string>
     <string id="33026">Comparing movies from:</string>
diff --git a/resources/lib/api.py b/resources/lib/api.py
index af993adb..97ad4178 100644
--- a/resources/lib/api.py
+++ b/resources/lib/api.py
@@ -37,7 +37,7 @@ class API():
 
         try:
             userdata = self.item['UserData']
-        
+
         except KeyError: # No userdata found.
             pass
 
@@ -57,7 +57,7 @@ class API():
             lastPlayedDate = userdata.get('LastPlayedDate')
             if lastPlayedDate:
                 lastPlayedDate = lastPlayedDate.split('.')[0].replace('T', " ")
-            
+
             if userdata['Played']:
                 # Playcount is tied to the watch status
                 played = True
@@ -91,10 +91,10 @@ class API():
 
         try:
             people = self.item['People']
-        
+
         except KeyError:
             pass
-        
+
         else:
             for person in people:
 
@@ -116,17 +116,16 @@ class API():
         }
 
     def getMediaStreams(self):
-        item = self.item
         videotracks = []
         audiotracks = []
         subtitlelanguages = []
 
         try:
-            media_streams = item['MediaSources'][0]['MediaStreams']
+            media_streams = self.item['MediaSources'][0]['MediaStreams']
 
         except KeyError:
-            if not item.get("MediaStreams"): return None
-            media_streams = item['MediaStreams']
+            if not self.item.get("MediaStreams"): return None
+            media_streams = self.item['MediaStreams']
 
         for media_stream in media_streams:
             # Sort through Video, Audio, Subtitle
@@ -141,12 +140,12 @@ class API():
                     'codec': codec,
                     'height': media_stream.get('Height'),
                     'width': media_stream.get('Width'),
-                    'video3DFormat': item.get('Video3DFormat'),
+                    'video3DFormat': self.item.get('Video3DFormat'),
                     'aspect': 1.85
                 }
 
                 try:
-                    container = item['MediaSources'][0]['Container'].lower()
+                    container = self.item['MediaSources'][0]['Container'].lower()
                 except:
                     container = ""
 
@@ -161,16 +160,16 @@ class API():
                         track['codec'] = "avc1"
 
                 # Aspect ratio
-                if item.get('AspectRatio'):
+                if self.item.get('AspectRatio'):
                     # Metadata AR
-                    aspect = item['AspectRatio']
+                    aspect = self.item['AspectRatio']
                 else: # File AR
                     aspect = media_stream.get('AspectRatio', "0")
 
                 try:
                     aspectwidth, aspectheight = aspect.split(':')
                     track['aspect'] = round(float(aspectwidth) / float(aspectheight), 6)
-                
+
                 except (ValueError, ZeroDivisionError):
                     width = track.get('width')
                     height = track.get('height')
@@ -179,16 +178,16 @@ class API():
                         track['aspect'] = round(float(width / height), 6)
                     else:
                         track['aspect'] = 1.85
-                
-                if item.get("RunTimeTicks"):
-                    track['duration'] = item.get("RunTimeTicks") / 10000000.0
-                
+
+                if self.item.get("RunTimeTicks"):
+                    track['duration'] = self.item.get("RunTimeTicks") / 10000000.0
+
                 videotracks.append(track)
 
             elif stream_type == "Audio":
                 # Codec, Channels, language
                 track = {
-                    
+
                     'codec': codec,
                     'channels': media_stream.get('Channels'),
                     'language': media_stream.get('Language')
@@ -205,18 +204,17 @@ class API():
 
         return {
 
-            'video': videotracks, 
+            'video': videotracks,
             'audio': audiotracks,
             'subtitle': subtitlelanguages
         }
 
     def getRuntime(self):
-        item = self.item
         try:
-            runtime = item['RunTimeTicks'] / 10000000.0
-        
+            runtime = self.item['RunTimeTicks'] / 10000000.0
+
         except KeyError:
-            runtime = item.get('CumulativeRunTimeTicks', 0) / 10000000.0
+            runtime = self.item.get('CumulativeRunTimeTicks', 0) / 10000000.0
 
         return runtime
 
@@ -234,20 +232,19 @@ class API():
 
     def getStudios(self):
         # Process Studios
-        item = self.item
         studios = []
 
         try:
-            studio = item['SeriesStudio']
+            studio = self.item['SeriesStudio']
             studios.append(self.verifyStudio(studio))
-        
+
         except KeyError:
-            studioList = item['Studios']
+            studioList = self.item['Studios']
             for studio in studioList:
 
                 name = studio['Name']
                 studios.append(self.verifyStudio(name))
-        
+
         return studios
 
     def verifyStudio(self, studioName):
@@ -265,12 +262,11 @@ class API():
 
     def getChecksum(self):
         # Use the etags checksum and userdata
-        item = self.item
-        userdata = item['UserData']
+        userdata = self.item['UserData']
 
         checksum = "%s%s%s%s%s%s%s" % (
-            
-            item['Etag'], 
+
+            self.item['Etag'],
             userdata['Played'],
             userdata['IsFavorite'],
             userdata.get('Likes',''),
@@ -282,9 +278,8 @@ class API():
         return checksum
 
     def getGenres(self):
-        item = self.item
         all_genres = ""
-        genres = item.get('Genres', item.get('SeriesGenres'))
+        genres = self.item.get('Genres', self.item.get('SeriesGenres'))
 
         if genres:
             all_genres = " / ".join(genres)
@@ -344,7 +339,7 @@ class API():
     def getMpaa(self):
         # Convert more complex cases
         mpaa = self.item.get('OfficialRating', "")
-        
+
         if mpaa in ("NR", "UR"):
             # Kodi seems to not like NR, but will accept Not Rated
             mpaa = "Not Rated"
@@ -362,9 +357,8 @@ class API():
 
     def getFilePath(self):
 
-        item = self.item
         try:
-            filepath = item['Path']
+            filepath = self.item['Path']
 
         except KeyError:
             filepath = ""
@@ -375,17 +369,16 @@ class API():
                 filepath = filepath.replace("\\\\", "smb://")
                 filepath = filepath.replace("\\", "/")
 
-            if item.get('VideoType'):
-                videotype = item['VideoType']
+            if self.item.get('VideoType'):
+                videotype = self.item['VideoType']
                 # Specific format modification
                 if 'Dvd'in videotype:
                     filepath = "%s/VIDEO_TS/VIDEO_TS.IFO" % filepath
                 elif 'BluRay' in videotype:
                     filepath = "%s/BDMV/index.bdmv" % filepath
-            
+
             if "\\" in filepath:
                 # Local path scenario, with special videotype
                 filepath = filepath.replace("/", "\\")
 
         return filepath
-        
\ No newline at end of file
diff --git a/resources/lib/artwork.py b/resources/lib/artwork.py
index 2740f09c..79fb617c 100644
--- a/resources/lib/artwork.py
+++ b/resources/lib/artwork.py
@@ -1,606 +1,604 @@
-# -*- coding: utf-8 -*-
-
-#################################################################################################
-
-import json
-import requests
-import os
-import urllib
-from sqlite3 import OperationalError
-
-import xbmc
-import xbmcgui
-import xbmcvfs
-
-import utils
-import clientinfo
-import image_cache_thread
-
-#################################################################################################
-
-
-class Artwork():   
-    
-    xbmc_host = 'localhost'
-    xbmc_port = None
-    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()
-
-        self.userId = utils.window('emby_currUser')
-        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)
-    
-
-    def double_urlencode(self, text):
-        text = self.single_urlencode(text)
-        text = self.single_urlencode(text)
-        
-        return text
-
-    def single_urlencode(self, text):
-        
-        text = urllib.urlencode({'blahblahblah':text.encode("utf-8")}) #urlencode needs a utf- string
-        text = text[13:]
-
-        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
-        web_query = {
-
-            "jsonrpc": "2.0",
-            "id": 1,
-            "method": "Settings.GetSettingValue",
-            "params": {
-
-                "setting": "services.webserver"
-            }
-        }
-        result = xbmc.executeJSONRPC(json.dumps(web_query))
-        result = json.loads(result)
-        try:
-            xbmc_webserver_enabled = result['result']['value']
-        except TypeError:
-            xbmc_webserver_enabled = False
-            
-        if not xbmc_webserver_enabled:
-            # Enable the webserver, it is disabled
-            web_port = {
-
-                "jsonrpc": "2.0",
-                "id": 1,
-                "method": "Settings.SetSettingValue",
-                "params": {
-
-                    "setting": "services.webserverport",
-                    "value": 8080
-                }
-            }
-            result = xbmc.executeJSONRPC(json.dumps(web_port))
-            self.xbmc_port = 8080
-
-            web_user = {
-
-                "jsonrpc": "2.0",
-                "id": 1,
-                "method": "Settings.SetSettingValue",
-                "params": {
-
-                    "setting": "services.webserver",
-                    "value": True
-                }
-            }
-            result = xbmc.executeJSONRPC(json.dumps(web_user))
-            self.xbmc_username = "kodi"
-
-
-        # Webserver already enabled
-        web_port = {
-
-            "jsonrpc": "2.0",
-            "id": 1,
-            "method": "Settings.GetSettingValue",
-            "params": {
-
-                "setting": "services.webserverport"
-            }
-        }
-        result = xbmc.executeJSONRPC(json.dumps(web_port))
-        result = json.loads(result)
-        try:
-            self.xbmc_port = result['result']['value']
-        except TypeError:
-            pass
-
-        web_user = {
-
-            "jsonrpc": "2.0",
-            "id": 1,
-            "method": "Settings.GetSettingValue",
-            "params": {
-
-                "setting": "services.webserverusername"
-            }
-        }
-        result = xbmc.executeJSONRPC(json.dumps(web_user))
-        result = json.loads(result)
-        try:
-            self.xbmc_username = result['result']['value']
-        except TypeError:
-            pass
-
-        web_pass = {
-
-            "jsonrpc": "2.0",
-            "id": 1,
-            "method": "Settings.GetSettingValue",
-            "params": {
-
-                "setting": "services.webserverpassword"
-            }
-        }
-        result = xbmc.executeJSONRPC(json.dumps(web_pass))
-        result = json.loads(result)
-        try:
-            self.xbmc_password = result['result']['value']
-        except TypeError:
-            pass
-    
-    def FullTextureCacheSync(self):
-        # This method will sync all Kodi artwork to textures13.db
-        # and cache them locally. This takes diskspace!
-        
-        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.DialogProgress()
-        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:
-                        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')
-            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')
-        cursor = connection.cursor()
-        cursor.execute("SELECT url FROM art WHERE media_type != 'actor'") # dont include actors
-        result = cursor.fetchall()
-        total = len(result)
-        count = 1     
-        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, "Updating Image Cache: " + textMessage)
-            self.CacheTexture(url[0])
-            count += 1
-        cursor.close()
-        
-        # Cache all entries in music DB
-        connection = utils.kodiSQL('music')
-        cursor = connection.cursor()
-        cursor.execute("SELECT url FROM art")
-        result = cursor.fetchall()
-        total = len(result)
-        count = 1     
-        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, "Updating Image Cache: " + textMessage)
-            self.CacheTexture(url[0])
-            count += 1
-        cursor.close()
-        
-        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, "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)
-
-    
-    def addArtwork(self, artwork, kodiId, mediaType, cursor):
-        # Kodi conversion table
-        kodiart = {
-
-            'Primary': ["thumb", "poster"],
-            'Banner': "banner",
-            'Logo': "clearlogo",
-            'Art': "clearart",
-            'Thumb': "landscape",
-            'Disc': "discart",
-            'Backdrop': "fanart",
-            'BoxRear': "poster"
-        }
-
-        # Artwork is a dictionary
-        for art in artwork:
-            
-            if art == "Backdrop":
-                # Backdrop entry is a list
-                # Process extra fanart for artwork downloader (fanart, fanart1, fanart2...)
-                backdrops = artwork[art]
-                backdropsNumber = len(backdrops)
-
-                query = ' '.join((
-
-                    "SELECT url",
-                    "FROM art",
-                    "WHERE media_id = ?",
-                    "AND media_type = ?",
-                    "AND type LIKE ?"
-                ))
-                cursor.execute(query, (kodiId, mediaType, "fanart%",))
-                rows = cursor.fetchall()
-
-                if len(rows) > backdropsNumber:
-                    # More backdrops in database. Delete extra fanart.
-                    query = ' '.join((
-
-                        "DELETE FROM art",
-                        "WHERE media_id = ?",
-                        "AND media_type = ?",
-                        "AND type LIKE ?"
-                    ))
-                    cursor.execute(query, (kodiId, mediaType, "fanart_",))
-
-                # Process backdrops and extra fanart
-                index = ""
-                for backdrop in backdrops:
-                    self.addOrUpdateArt(
-                        imageUrl=backdrop,
-                        kodiId=kodiId,
-                        mediaType=mediaType,
-                        imageType="%s%s" % ("fanart", index),
-                        cursor=cursor)
-                    
-                    if backdropsNumber > 1:
-                        try: # Will only fail on the first try, str to int.
-                            index += 1
-                        except TypeError:
-                            index = 1
-                
-            elif art == "Primary":
-                # Primary art is processed as thumb and poster for Kodi.
-                for artType in kodiart[art]:
-                    self.addOrUpdateArt(
-                        imageUrl=artwork[art],
-                        kodiId=kodiId,
-                        mediaType=mediaType,
-                        imageType=artType,
-                        cursor=cursor)
-            
-            elif kodiart.get(art):
-                # Process the rest artwork type that Kodi can use
-                self.addOrUpdateArt(
-                    imageUrl=artwork[art],
-                    kodiId=kodiId,
-                    mediaType=mediaType,
-                    imageType=kodiart[art],
-                    cursor=cursor)
-
-    def addOrUpdateArt(self, imageUrl, kodiId, mediaType, imageType, cursor):
-        # Possible that the imageurl is an empty string
-        if imageUrl:
-            cacheimage = False
-
-            query = ' '.join((
-
-                "SELECT url",
-                "FROM art",
-                "WHERE media_id = ?",
-                "AND media_type = ?",
-                "AND type = ?"
-            ))
-            cursor.execute(query, (kodiId, mediaType, imageType,))
-            try: # Update the artwork
-                url = cursor.fetchone()[0]
-            
-            except TypeError: # Add the artwork
-                cacheimage = True
-                self.logMsg("Adding Art Link for kodiId: %s (%s)" % (kodiId, imageUrl), 2)
-                
-                query = (
-                    '''
-                    INSERT INTO art(media_id, media_type, type, url)
-
-                    VALUES (?, ?, ?, ?)
-                    '''
-                )
-                cursor.execute(query, (kodiId, mediaType, imageType, imageUrl))
-            
-            else: # Only cache artwork if it changed
-                if url != imageUrl:
-                    cacheimage = True
-                    
-                    # Only for the main backdrop, poster
-                    if (utils.window('emby_initialScan') != "true" and
-                            imageType in ("fanart", "poster")):
-                        # Delete current entry before updating with the new one
-                        self.deleteCachedArtwork(url)
-                    
-                    self.logMsg(
-                        "Updating Art url for %s kodiId: %s (%s) -> (%s)"
-                        % (imageType, kodiId, url, imageUrl), 1)
-
-                    query = ' '.join((
-
-                        "UPDATE art",
-                        "SET url = ?",
-                        "WHERE media_id = ?",
-                        "AND media_type = ?",
-                        "AND type = ?"
-                    ))
-                    cursor.execute(query, (imageUrl, kodiId, mediaType, imageType))
-                    
-            # Cache fanart and poster in Kodi texture cache
-            if cacheimage and imageType in ("fanart", "poster"):
-                self.CacheTexture(imageUrl)
-
-    def deleteArtwork(self, kodiid, mediatype, cursor):
-
-        query = ' '.join((
-
-            "SELECT url, type",
-            "FROM art",
-            "WHERE media_id = ?",
-            "AND media_type = ?"
-        ))
-        cursor.execute(query, (kodiid, mediatype,))
-        rows = cursor.fetchall()
-        for row in rows:
-
-            url = row[0]
-            imagetype = row[1]
-            if imagetype in ("poster", "fanart"):
-                self.deleteCachedArtwork(url)
-
-    def deleteCachedArtwork(self, url):
-        # Only necessary to remove and apply a new backdrop or poster
-        connection = utils.kodiSQL('texture')
-        cursor = connection.cursor()
-
-        try:
-            cursor.execute("SELECT cachedurl FROM texture WHERE url = ?", (url,))
-            cachedurl = cursor.fetchone()[0]
-        
-        except TypeError:
-            self.logMsg("Could not find cached url.", 1)
-
-        except OperationalError:
-            self.logMsg("Database is locked. Skip deletion process.", 1)
-        
-        else: # Delete thumbnail as well as the entry
-            thumbnails = xbmc.translatePath("special://thumbnails/%s" % cachedurl).decode('utf-8')
-            self.logMsg("Deleting cached thumbnail: %s" % thumbnails, 1)
-            xbmcvfs.delete(thumbnails)
-            
-            try:
-                cursor.execute("DELETE FROM texture WHERE url = ?", (url,))
-                connection.commit()
-            except OperationalError:
-                self.logMsg("Issue deleting url from cache. Skipping.", 2)
-        
-        finally:
-            cursor.close()
-
-    def getPeopleArtwork(self, people):
-        # append imageurl if existing
-        for person in people:
-
-            personId = person['Id']
-            tag = person.get('PrimaryImageTag')
-
-            image = ""
-            if tag:
-                image = (
-                    "%s/emby/Items/%s/Images/Primary?"
-                    "MaxWidth=400&MaxHeight=400&Index=0&Tag=%s"
-                    % (self.server, personId, tag))
-            
-            person['imageurl'] = image
-
-        return people
-
-    def getUserArtwork(self, itemid, itemtype):
-        # Load user information set by UserClient
-        image = ("%s/emby/Users/%s/Images/%s?Format=original"
-                    % (self.server, itemid, itemtype))
-        return image
-
-    def getAllArtwork(self, item, parentInfo=False):
-
-        server = self.server
-
-        itemid = item['Id']
-        artworks = item['ImageTags']
-        backdrops = item.get('BackdropImageTags',[])
-
-        maxHeight = 10000
-        maxWidth = 10000
-        customquery = ""
-
-        if utils.settings('compressArt') == "true":
-            customquery = "&Quality=90"
-
-        if utils.settings('enableCoverArt') == "false":
-            customquery += "&EnableImageEnhancers=false"
-
-        allartworks = {
-
-            'Primary': "",
-            'Art': "",
-            'Banner': "",
-            'Logo': "",
-            'Thumb': "",
-            'Disc': "",
-            'Backdrop': []
-        }
-        
-        # Process backdrops
-        for index, tag in enumerate(backdrops):
-            artwork = (
-                "%s/emby/Items/%s/Images/Backdrop/%s?"
-                "MaxWidth=%s&MaxHeight=%s&Format=original&Tag=%s%s"
-                % (server, itemid, index, maxWidth, maxHeight, tag, customquery))
-            allartworks['Backdrop'].append(artwork)
-
-        # Process the rest of the artwork
-        for art in artworks:
-            # Filter backcover
-            if art != "BoxRear":
-                tag = artworks[art]
-                artwork = (
-                    "%s/emby/Items/%s/Images/%s/0?"
-                    "MaxWidth=%s&MaxHeight=%s&Format=original&Tag=%s%s"
-                    % (server, itemid, art, maxWidth, maxHeight, tag, customquery))
-                allartworks[art] = artwork
-
-        # Process parent items if the main item is missing artwork
-        if parentInfo:
-            
-            # Process parent backdrops
-            if not allartworks['Backdrop']:
-                
-                parentId = item.get('ParentBackdropItemId')
-                if parentId:
-                    # If there is a parentId, go through the parent backdrop list
-                    parentbackdrops = item['ParentBackdropImageTags']
-
-                    for index, tag in enumerate(parentbackdrops):
-                        artwork = (
-                            "%s/emby/Items/%s/Images/Backdrop/%s?"
-                            "MaxWidth=%s&MaxHeight=%s&Format=original&Tag=%s%s"
-                            % (server, parentId, index, maxWidth, maxHeight, tag, customquery))
-                        allartworks['Backdrop'].append(artwork)
-
-            # Process the rest of the artwork
-            parentartwork = ['Logo', 'Art', 'Thumb']
-            for parentart in parentartwork:
-
-                if not allartworks[parentart]:
-                    
-                    parentId = item.get('Parent%sItemId' % parentart)
-                    if parentId:
-                        
-                        parentTag = item['Parent%sImageTag' % parentart]
-                        artwork = (
-                            "%s/emby/Items/%s/Images/%s/0?"
-                            "MaxWidth=%s&MaxHeight=%s&Format=original&Tag=%s%s"
-                            % (server, parentId, parentart,
-                                maxWidth, maxHeight, parentTag, customquery))
-                        allartworks[parentart] = artwork
-
-            # Parent album works a bit differently
-            if not allartworks['Primary']:
-
-                parentId = item.get('AlbumId')
-                if parentId and item.get('AlbumPrimaryImageTag'):
-                    
-                    parentTag = item['AlbumPrimaryImageTag']
-                    artwork = (
-                        "%s/emby/Items/%s/Images/Primary/0?"
-                        "MaxWidth=%s&MaxHeight=%s&Format=original&Tag=%s%s"
-                        % (server, parentId, maxWidth, maxHeight, parentTag, customquery))
-                    allartworks['Primary'] = artwork
-
+# -*- coding: utf-8 -*-
+
+#################################################################################################
+
+import json
+import requests
+import os
+import urllib
+from sqlite3 import OperationalError
+
+import xbmc
+import xbmcgui
+import xbmcvfs
+
+import utils
+import clientinfo
+import image_cache_thread
+
+#################################################################################################
+
+
+class Artwork():
+
+    xbmc_host = 'localhost'
+    xbmc_port = None
+    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()
+
+        self.userId = utils.window('emby_currUser')
+        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)
+
+
+    def double_urlencode(self, text):
+        text = self.single_urlencode(text)
+        text = self.single_urlencode(text)
+
+        return text
+
+    def single_urlencode(self, text):
+
+        text = urllib.urlencode({'blahblahblah':text.encode("utf-8")}) #urlencode needs a utf- string
+        text = text[13:]
+
+        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
+        web_query = {
+
+            "jsonrpc": "2.0",
+            "id": 1,
+            "method": "Settings.GetSettingValue",
+            "params": {
+
+                "setting": "services.webserver"
+            }
+        }
+        result = xbmc.executeJSONRPC(json.dumps(web_query))
+        result = json.loads(result)
+        try:
+            xbmc_webserver_enabled = result['result']['value']
+        except TypeError:
+            xbmc_webserver_enabled = False
+
+        if not xbmc_webserver_enabled:
+            # Enable the webserver, it is disabled
+            web_port = {
+
+                "jsonrpc": "2.0",
+                "id": 1,
+                "method": "Settings.SetSettingValue",
+                "params": {
+
+                    "setting": "services.webserverport",
+                    "value": 8080
+                }
+            }
+            result = xbmc.executeJSONRPC(json.dumps(web_port))
+            self.xbmc_port = 8080
+
+            web_user = {
+
+                "jsonrpc": "2.0",
+                "id": 1,
+                "method": "Settings.SetSettingValue",
+                "params": {
+
+                    "setting": "services.webserver",
+                    "value": True
+                }
+            }
+            result = xbmc.executeJSONRPC(json.dumps(web_user))
+            self.xbmc_username = "kodi"
+
+
+        # Webserver already enabled
+        web_port = {
+
+            "jsonrpc": "2.0",
+            "id": 1,
+            "method": "Settings.GetSettingValue",
+            "params": {
+
+                "setting": "services.webserverport"
+            }
+        }
+        result = xbmc.executeJSONRPC(json.dumps(web_port))
+        result = json.loads(result)
+        try:
+            self.xbmc_port = result['result']['value']
+        except TypeError:
+            pass
+
+        web_user = {
+
+            "jsonrpc": "2.0",
+            "id": 1,
+            "method": "Settings.GetSettingValue",
+            "params": {
+
+                "setting": "services.webserverusername"
+            }
+        }
+        result = xbmc.executeJSONRPC(json.dumps(web_user))
+        result = json.loads(result)
+        try:
+            self.xbmc_username = result['result']['value']
+        except TypeError:
+            pass
+
+        web_pass = {
+
+            "jsonrpc": "2.0",
+            "id": 1,
+            "method": "Settings.GetSettingValue",
+            "params": {
+
+                "setting": "services.webserverpassword"
+            }
+        }
+        result = xbmc.executeJSONRPC(json.dumps(web_pass))
+        result = json.loads(result)
+        try:
+            self.xbmc_password = result['result']['value']
+        except TypeError:
+            pass
+
+    def FullTextureCacheSync(self):
+        # This method will sync all Kodi artwork to textures13.db
+        # and cache them locally. This takes diskspace!
+
+        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.DialogProgress()
+        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:
+                        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')
+            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')
+        cursor = connection.cursor()
+        cursor.execute("SELECT url FROM art WHERE media_type != 'actor'") # dont include actors
+        result = cursor.fetchall()
+        total = len(result)
+        count = 1
+        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, "Updating Image Cache: " + textMessage)
+            self.CacheTexture(url[0])
+            count += 1
+        cursor.close()
+
+        # Cache all entries in music DB
+        connection = utils.kodiSQL('music')
+        cursor = connection.cursor()
+        cursor.execute("SELECT url FROM art")
+        result = cursor.fetchall()
+        total = len(result)
+        count = 1
+        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, "Updating Image Cache: " + textMessage)
+            self.CacheTexture(url[0])
+            count += 1
+        cursor.close()
+
+        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, "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)
+
+
+    def addArtwork(self, artwork, kodiId, mediaType, cursor):
+        # Kodi conversion table
+        kodiart = {
+
+            'Primary': ["thumb", "poster"],
+            'Banner': "banner",
+            'Logo': "clearlogo",
+            'Art': "clearart",
+            'Thumb': "landscape",
+            'Disc': "discart",
+            'Backdrop': "fanart",
+            'BoxRear': "poster"
+        }
+
+        # Artwork is a dictionary
+        for art in artwork:
+
+            if art == "Backdrop":
+                # Backdrop entry is a list
+                # Process extra fanart for artwork downloader (fanart, fanart1, fanart2...)
+                backdrops = artwork[art]
+                backdropsNumber = len(backdrops)
+
+                query = ' '.join((
+
+                    "SELECT url",
+                    "FROM art",
+                    "WHERE media_id = ?",
+                    "AND media_type = ?",
+                    "AND type LIKE ?"
+                ))
+                cursor.execute(query, (kodiId, mediaType, "fanart%",))
+                rows = cursor.fetchall()
+
+                if len(rows) > backdropsNumber:
+                    # More backdrops in database. Delete extra fanart.
+                    query = ' '.join((
+
+                        "DELETE FROM art",
+                        "WHERE media_id = ?",
+                        "AND media_type = ?",
+                        "AND type LIKE ?"
+                    ))
+                    cursor.execute(query, (kodiId, mediaType, "fanart_",))
+
+                # Process backdrops and extra fanart
+                index = ""
+                for backdrop in backdrops:
+                    self.addOrUpdateArt(
+                        imageUrl=backdrop,
+                        kodiId=kodiId,
+                        mediaType=mediaType,
+                        imageType="%s%s" % ("fanart", index),
+                        cursor=cursor)
+
+                    if backdropsNumber > 1:
+                        try: # Will only fail on the first try, str to int.
+                            index += 1
+                        except TypeError:
+                            index = 1
+
+            elif art == "Primary":
+                # Primary art is processed as thumb and poster for Kodi.
+                for artType in kodiart[art]:
+                    self.addOrUpdateArt(
+                        imageUrl=artwork[art],
+                        kodiId=kodiId,
+                        mediaType=mediaType,
+                        imageType=artType,
+                        cursor=cursor)
+
+            elif kodiart.get(art):
+                # Process the rest artwork type that Kodi can use
+                self.addOrUpdateArt(
+                    imageUrl=artwork[art],
+                    kodiId=kodiId,
+                    mediaType=mediaType,
+                    imageType=kodiart[art],
+                    cursor=cursor)
+
+    def addOrUpdateArt(self, imageUrl, kodiId, mediaType, imageType, cursor):
+        # Possible that the imageurl is an empty string
+        if imageUrl:
+            cacheimage = False
+
+            query = ' '.join((
+
+                "SELECT url",
+                "FROM art",
+                "WHERE media_id = ?",
+                "AND media_type = ?",
+                "AND type = ?"
+            ))
+            cursor.execute(query, (kodiId, mediaType, imageType,))
+            try: # Update the artwork
+                url = cursor.fetchone()[0]
+
+            except TypeError: # Add the artwork
+                cacheimage = True
+                self.logMsg("Adding Art Link for kodiId: %s (%s)" % (kodiId, imageUrl), 2)
+
+                query = (
+                    '''
+                    INSERT INTO art(media_id, media_type, type, url)
+
+                    VALUES (?, ?, ?, ?)
+                    '''
+                )
+                cursor.execute(query, (kodiId, mediaType, imageType, imageUrl))
+
+            else: # Only cache artwork if it changed
+                if url != imageUrl:
+                    cacheimage = True
+
+                    # Only for the main backdrop, poster
+                    if (utils.window('emby_initialScan') != "true" and
+                            imageType in ("fanart", "poster")):
+                        # Delete current entry before updating with the new one
+                        self.deleteCachedArtwork(url)
+
+                    self.logMsg(
+                        "Updating Art url for %s kodiId: %s (%s) -> (%s)"
+                        % (imageType, kodiId, url, imageUrl), 1)
+
+                    query = ' '.join((
+
+                        "UPDATE art",
+                        "SET url = ?",
+                        "WHERE media_id = ?",
+                        "AND media_type = ?",
+                        "AND type = ?"
+                    ))
+                    cursor.execute(query, (imageUrl, kodiId, mediaType, imageType))
+
+            # Cache fanart and poster in Kodi texture cache
+            if cacheimage and imageType in ("fanart", "poster"):
+                self.CacheTexture(imageUrl)
+
+    def deleteArtwork(self, kodiid, mediatype, cursor):
+
+        query = ' '.join((
+
+            "SELECT url, type",
+            "FROM art",
+            "WHERE media_id = ?",
+            "AND media_type = ?"
+        ))
+        cursor.execute(query, (kodiid, mediatype,))
+        rows = cursor.fetchall()
+        for row in rows:
+
+            url = row[0]
+            imagetype = row[1]
+            if imagetype in ("poster", "fanart"):
+                self.deleteCachedArtwork(url)
+
+    def deleteCachedArtwork(self, url):
+        # Only necessary to remove and apply a new backdrop or poster
+        connection = utils.kodiSQL('texture')
+        cursor = connection.cursor()
+
+        try:
+            cursor.execute("SELECT cachedurl FROM texture WHERE url = ?", (url,))
+            cachedurl = cursor.fetchone()[0]
+
+        except TypeError:
+            self.logMsg("Could not find cached url.", 1)
+
+        except OperationalError:
+            self.logMsg("Database is locked. Skip deletion process.", 1)
+
+        else: # Delete thumbnail as well as the entry
+            thumbnails = xbmc.translatePath("special://thumbnails/%s" % cachedurl).decode('utf-8')
+            self.logMsg("Deleting cached thumbnail: %s" % thumbnails, 1)
+            xbmcvfs.delete(thumbnails)
+
+            try:
+                cursor.execute("DELETE FROM texture WHERE url = ?", (url,))
+                connection.commit()
+            except OperationalError:
+                self.logMsg("Issue deleting url from cache. Skipping.", 2)
+
+        finally:
+            cursor.close()
+
+    def getPeopleArtwork(self, people):
+        # append imageurl if existing
+        for person in people:
+
+            personId = person['Id']
+            tag = person.get('PrimaryImageTag')
+
+            image = ""
+            if tag:
+                image = (
+                    "%s/emby/Items/%s/Images/Primary?"
+                    "MaxWidth=400&MaxHeight=400&Index=0&Tag=%s"
+                    % (self.server, personId, tag))
+
+            person['imageurl'] = image
+
+        return people
+
+    def getUserArtwork(self, itemid, itemtype):
+        # Load user information set by UserClient
+        image = ("%s/emby/Users/%s/Images/%s?Format=original"
+                    % (self.server, itemid, itemtype))
+        return image
+
+    def getAllArtwork(self, item, parentInfo=False):
+
+        itemid = item['Id']
+        artworks = item['ImageTags']
+        backdrops = item.get('BackdropImageTags',[])
+
+        maxHeight = 10000
+        maxWidth = 10000
+        customquery = ""
+
+        if utils.settings('compressArt') == "true":
+            customquery = "&Quality=90"
+
+        if utils.settings('enableCoverArt') == "false":
+            customquery += "&EnableImageEnhancers=false"
+
+        allartworks = {
+
+            'Primary': "",
+            'Art': "",
+            'Banner': "",
+            'Logo': "",
+            'Thumb': "",
+            'Disc': "",
+            'Backdrop': []
+        }
+
+        # Process backdrops
+        for index, tag in enumerate(backdrops):
+            artwork = (
+                "%s/emby/Items/%s/Images/Backdrop/%s?"
+                "MaxWidth=%s&MaxHeight=%s&Format=original&Tag=%s%s"
+                % (self.server, itemid, index, maxWidth, maxHeight, tag, customquery))
+            allartworks['Backdrop'].append(artwork)
+
+        # Process the rest of the artwork
+        for art in artworks:
+            # Filter backcover
+            if art != "BoxRear":
+                tag = artworks[art]
+                artwork = (
+                    "%s/emby/Items/%s/Images/%s/0?"
+                    "MaxWidth=%s&MaxHeight=%s&Format=original&Tag=%s%s"
+                    % (self.server, itemid, art, maxWidth, maxHeight, tag, customquery))
+                allartworks[art] = artwork
+
+        # Process parent items if the main item is missing artwork
+        if parentInfo:
+
+            # Process parent backdrops
+            if not allartworks['Backdrop']:
+
+                parentId = item.get('ParentBackdropItemId')
+                if parentId:
+                    # If there is a parentId, go through the parent backdrop list
+                    parentbackdrops = item['ParentBackdropImageTags']
+
+                    for index, tag in enumerate(parentbackdrops):
+                        artwork = (
+                            "%s/emby/Items/%s/Images/Backdrop/%s?"
+                            "MaxWidth=%s&MaxHeight=%s&Format=original&Tag=%s%s"
+                            % (self.server, parentId, index, maxWidth, maxHeight, tag, customquery))
+                        allartworks['Backdrop'].append(artwork)
+
+            # Process the rest of the artwork
+            parentartwork = ['Logo', 'Art', 'Thumb']
+            for parentart in parentartwork:
+
+                if not allartworks[parentart]:
+
+                    parentId = item.get('Parent%sItemId' % parentart)
+                    if parentId:
+
+                        parentTag = item['Parent%sImageTag' % parentart]
+                        artwork = (
+                            "%s/emby/Items/%s/Images/%s/0?"
+                            "MaxWidth=%s&MaxHeight=%s&Format=original&Tag=%s%s"
+                            % (self.server, parentId, parentart,
+                                maxWidth, maxHeight, parentTag, customquery))
+                        allartworks[parentart] = artwork
+
+            # Parent album works a bit differently
+            if not allartworks['Primary']:
+
+                parentId = item.get('AlbumId')
+                if parentId and item.get('AlbumPrimaryImageTag'):
+
+                    parentTag = item['AlbumPrimaryImageTag']
+                    artwork = (
+                        "%s/emby/Items/%s/Images/Primary/0?"
+                        "MaxWidth=%s&MaxHeight=%s&Format=original&Tag=%s%s"
+                        % (self.server, parentId, maxWidth, maxHeight, parentTag, customquery))
+                    allartworks['Primary'] = artwork
+
         return allartworks
\ No newline at end of file
diff --git a/resources/lib/connect.py b/resources/lib/connect.py
index 05b563a3..2bd5c05d 100644
--- a/resources/lib/connect.py
+++ b/resources/lib/connect.py
@@ -21,7 +21,7 @@ requests.packages.urllib3.disable_warnings(InsecurePlatformWarning)
 
 
 class ConnectUtils():
-    
+
     # Borg - multiple instances, shared state
     _shared_state = {}
     clientInfo = clientinfo.ClientInfo()
@@ -60,8 +60,6 @@ class ConnectUtils():
 
     def startSession(self):
 
-        log = self.logMsg
-
         self.deviceId = self.clientInfo.getDeviceId()
 
         # User is identified from this point
@@ -75,8 +73,8 @@ class ConnectUtils():
             if self.sslclient is not None:
                 verify = self.sslclient
         except:
-            log("Could not load SSL settings.", 1)
-        
+            self.logMsg("Could not load SSL settings.", 1)
+
         # Start session
         self.c = requests.Session()
         self.c.headers = header
@@ -85,7 +83,7 @@ class ConnectUtils():
         self.c.mount("http://", requests.adapters.HTTPAdapter(max_retries=1))
         self.c.mount("https://", requests.adapters.HTTPAdapter(max_retries=1))
 
-        log("Requests session started on: %s" % self.server, 1)
+        self.logMsg("Requests session started on: %s" % self.server, 1)
 
     def stopSession(self):
         try:
@@ -95,8 +93,7 @@ class ConnectUtils():
 
     def getHeader(self, authenticate=True):
 
-        clientInfo = self.clientInfo
-        version = clientInfo.getVersion()
+        version = self.clientInfo.getVersion()
 
         if not authenticate:
             # If user is not authenticated
@@ -105,9 +102,9 @@ class ConnectUtils():
                 'X-Application': "Kodi/%s" % version,
                 'Content-type': 'application/x-www-form-urlencoded; charset=UTF-8',
                 'Accept': "application/json"
-            }      
+            }
             self.logMsg("Header: %s" % header, 1)
-        
+
         else:
             token = self.token
             # Attached to the requests session
@@ -117,18 +114,17 @@ class ConnectUtils():
                 'Accept': "application/json",
                 'X-Application': "Kodi/%s" % version,
                 'X-Connect-UserToken': token
-            }        
+            }
             self.logMsg("Header: %s" % header, 1)
-        
+
         return header
 
     def doUrl(self, url, data=None, postBody=None, rtype="GET",
                 parameters=None, authenticate=True, timeout=None):
 
-        log = self.logMsg
         window = utils.window
 
-        log("=== ENTER connectUrl ===", 2)
+        self.logMsg("=== ENTER connectUrl ===", 2)
         default_link = ""
         if timeout is None:
             timeout = self.timeout
@@ -137,7 +133,7 @@ class ConnectUtils():
         try:
             # If connect user is authenticated
             if authenticate:
-                try: 
+                try:
                     c = self.c
                     # Replace for the real values
                     url = url.replace("{server}", self.server)
@@ -167,7 +163,7 @@ class ConnectUtils():
                             verifyssl = self.sslclient
                     except AttributeError:
                         pass
-                    
+
                     # Prepare request
                     if rtype == "GET":
                         r = requests.get(url,
@@ -195,7 +191,7 @@ class ConnectUtils():
                         verifyssl = self.sslclient
                 except AttributeError:
                     pass
-                
+
                 # Prepare request
                 if rtype == "GET":
                     r = requests.get(url,
@@ -213,28 +209,28 @@ class ConnectUtils():
                                     verify=verifyssl)
 
             ##### THE RESPONSE #####
-            log(r.url, 1)
-            log(r, 1)
+            self.logMsg(r.url, 1)
+            self.logMsg(r, 1)
 
             if r.status_code == 204:
                 # No body in the response
-                log("====== 204 Success ======", 1)
+                self.logMsg("====== 204 Success ======", 1)
 
             elif r.status_code == requests.codes.ok:
-               
-                try: 
+
+                try:
                     # UNICODE - JSON object
                     r = r.json()
-                    log("====== 200 Success ======", 1)
-                    log("Response: %s" % r, 1)
+                    self.logMsg("====== 200 Success ======", 1)
+                    self.logMsg("Response: %s" % r, 1)
                     return r
 
                 except:
                     if r.headers.get('content-type') != "text/html":
-                        log("Unable to convert the response for: %s" % url, 1)
+                        self.logMsg("Unable to convert the response for: %s" % url, 1)
             else:
                 r.raise_for_status()
-        
+
         ##### EXCEPTIONS #####
 
         except requests.exceptions.ConnectionError as e:
@@ -242,8 +238,8 @@ class ConnectUtils():
             pass
 
         except requests.exceptions.ConnectTimeout as e:
-            log("Server timeout at: %s" % url, 0)
-            log(e, 1)
+            self.logMsg("Server timeout at: %s" % url, 0)
+            self.logMsg(e, 1)
 
         except requests.exceptions.HTTPError as e:
 
@@ -259,11 +255,11 @@ class ConnectUtils():
                 pass
 
         except requests.exceptions.SSLError as e:
-            log("Invalid SSL certificate for: %s" % url, 0)
-            log(e, 1)
+            self.logMsg("Invalid SSL certificate for: %s" % url, 0)
+            self.logMsg(e, 1)
 
         except requests.exceptions.RequestException as e:
-            log("Unknown error connecting to: %s" % url, 0)
-            log(e, 1)
+            self.logMsg("Unknown error connecting to: %s" % url, 0)
+            self.logMsg(e, 1)
 
         return default_link
diff --git a/resources/lib/downloadutils.py b/resources/lib/downloadutils.py
index de340f07..a74ee6f2 100644
--- a/resources/lib/downloadutils.py
+++ b/resources/lib/downloadutils.py
@@ -23,7 +23,7 @@ requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
 
 
 class DownloadUtils():
-    
+
     # Borg - multiple instances, shared state
     _shared_state = {}
     clientInfo = clientinfo.ClientInfo()
@@ -77,11 +77,11 @@ class DownloadUtils():
         # Post settings to session
         url = "{server}/emby/Sessions/Capabilities/Full?format=json"
         data = {
-            
+
             'PlayableMediaTypes': "Audio,Video",
             'SupportsMediaControl': True,
             'SupportedCommands': (
-                
+
                 "MoveUp,MoveDown,MoveLeft,MoveRight,Select,"
                 "Back,ToggleContextMenu,ToggleFullscreen,ToggleOsdMenu,"
                 "GoHome,PageUp,NextLetter,GoToSearch,"
@@ -97,7 +97,7 @@ class DownloadUtils():
         self.logMsg("Capabilities URL: %s" % url, 2)
         self.logMsg("Postdata: %s" % data, 2)
 
-        self.downloadUrl(url, postBody=data, type="POST")
+        self.downloadUrl(url, postBody=data, action_type="POST")
         self.logMsg("Posted capabilities to %s" % self.server, 2)
 
         # Attempt at getting sessionId
@@ -105,19 +105,19 @@ class DownloadUtils():
         result = self.downloadUrl(url)
         try:
             sessionId = result[0]['Id']
-        
+
         except (KeyError, TypeError):
             self.logMsg("Failed to retrieve sessionId.", 1)
-        
+
         else:
             self.logMsg("Session: %s" % result, 2)
             self.logMsg("SessionId: %s" % sessionId, 1)
             utils.window('emby_sessionId', value=sessionId)
-            
+
             # Post any permanent additional users
             additionalUsers = utils.settings('additionalUsers')
             if additionalUsers:
-                
+
                 additionalUsers = additionalUsers.split(',')
                 self.logMsg(
                     "List of permanent users added to the session: %s"
@@ -140,13 +140,11 @@ class DownloadUtils():
                                     "{server}/emby/Sessions/%s/Users/%s?format=json"
                                     % (sessionId, userId)
                             )
-                            self.downloadUrl(url, postBody={}, type="POST")
+                            self.downloadUrl(url, postBody={}, action_type="POST")
 
 
     def startSession(self):
 
-        log = self.logMsg
-
         self.deviceId = self.clientInfo.getDeviceId()
 
         # User is identified from this point
@@ -160,8 +158,8 @@ class DownloadUtils():
             if self.sslclient is not None:
                 verify = self.sslclient
         except:
-            log("Could not load SSL settings.", 1)
-        
+            self.logMsg("Could not load SSL settings.", 1)
+
         # Start session
         self.s = requests.Session()
         self.s.headers = header
@@ -170,7 +168,7 @@ class DownloadUtils():
         self.s.mount("http://", requests.adapters.HTTPAdapter(max_retries=1))
         self.s.mount("https://", requests.adapters.HTTPAdapter(max_retries=1))
 
-        log("Requests session started on: %s" % self.server, 1)
+        self.logMsg("Requests session started on: %s" % self.server, 1)
 
     def stopSession(self):
         try:
@@ -180,12 +178,10 @@ class DownloadUtils():
 
     def getHeader(self, authenticate=True):
 
-        clientInfo = self.clientInfo
-
-        deviceName = clientInfo.getDeviceName()
+        deviceName = self.clientInfo.getDeviceName()
         deviceName = utils.normalize_string(deviceName.encode('utf-8'))
-        deviceId = clientInfo.getDeviceId()
-        version = clientInfo.getVersion()
+        deviceId = self.clientInfo.getDeviceId()
+        version = self.clientInfo.getVersion()
 
         if not authenticate:
             # If user is not authenticated
@@ -198,9 +194,9 @@ class DownloadUtils():
                 'Accept-encoding': 'gzip',
                 'Accept-Charset': 'UTF-8,*',
                 'Authorization': auth
-            }      
+            }
             self.logMsg("Header: %s" % header, 2)
-        
+
         else:
             userId = self.userId
             token = self.token
@@ -215,36 +211,35 @@ class DownloadUtils():
                 'Accept-Charset': 'UTF-8,*',
                 'Authorization': auth,
                 'X-MediaBrowser-Token': token
-            }        
+            }
             self.logMsg("Header: %s" % header, 2)
-        
+
         return header
 
-    def downloadUrl(self, url, postBody=None, type="GET", parameters=None, authenticate=True):
-        
+    def downloadUrl(self, url, postBody=None, action_type="GET", parameters=None, authenticate=True):
+
         self.logMsg("=== ENTER downloadUrl ===", 2)
 
-        timeout = self.timeout
         default_link = ""
 
         try:
             # If user is authenticated
             if (authenticate):
                 # Get requests session
-                try: 
+                try:
                     s = self.s
                     # Replace for the real values
                     url = url.replace("{server}", self.server)
                     url = url.replace("{UserId}", self.userId)
 
                     # Prepare request
-                    if type == "GET":
-                        r = s.get(url, json=postBody, params=parameters, timeout=timeout)
-                    elif type == "POST":
-                        r = s.post(url, json=postBody, timeout=timeout)
-                    elif type == "DELETE":
-                        r = s.delete(url, json=postBody, timeout=timeout)
-                
+                    if action_type == "GET":
+                        r = s.get(url, json=postBody, params=parameters, timeout=self.timeout)
+                    elif action_type == "POST":
+                        r = s.post(url, json=postBody, timeout=self.timeout)
+                    elif action_type == "DELETE":
+                        r = s.delete(url, json=postBody, timeout=self.timeout)
+
                 except AttributeError:
                     # request session does not exists
                     # Get user information
@@ -266,26 +261,26 @@ class DownloadUtils():
                     url = url.replace("{UserId}", self.userId)
 
                     # Prepare request
-                    if type == "GET":
+                    if action_type == "GET":
                         r = requests.get(url,
                                         json=postBody,
                                         params=parameters,
                                         headers=header,
-                                        timeout=timeout,
+                                        timeout=self.timeout,
                                         verify=verifyssl)
 
-                    elif type == "POST":
+                    elif action_type == "POST":
                         r = requests.post(url,
                                         json=postBody,
                                         headers=header,
-                                        timeout=timeout,
+                                        timeout=self.timeout,
                                         verify=verifyssl)
 
-                    elif type == "DELETE":
+                    elif action_type == "DELETE":
                         r = requests.delete(url,
                                         json=postBody,
                                         headers=header,
-                                        timeout=timeout,
+                                        timeout=self.timeout,
                                         verify=verifyssl)
 
             # If user is not authenticated
@@ -301,23 +296,23 @@ class DownloadUtils():
                         verifyssl = self.sslclient
                 except AttributeError:
                     pass
-                
+
                 # Prepare request
-                if type == "GET":
+                if action_type == "GET":
                     r = requests.get(url,
                                     json=postBody,
                                     params=parameters,
                                     headers=header,
-                                    timeout=timeout,
+                                    timeout=self.timeout,
                                     verify=verifyssl)
 
-                elif type == "POST":
+                elif action_type == "POST":
                     r = requests.post(url,
                                     json=postBody,
                                     headers=header,
-                                    timeout=timeout,
+                                    timeout=self.timeout,
                                     verify=verifyssl)
-        
+
             ##### THE RESPONSE #####
             self.logMsg(r.url, 2)
             if r.status_code == 204:
@@ -325,8 +320,8 @@ class DownloadUtils():
                 self.logMsg("====== 204 Success ======", 2)
 
             elif r.status_code == requests.codes.ok:
-               
-                try: 
+
+                try:
                     # UNICODE - JSON object
                     r = r.json()
                     self.logMsg("====== 200 Success ======", 2)
@@ -338,7 +333,7 @@ class DownloadUtils():
                         self.logMsg("Unable to convert the response for: %s" % url, 1)
             else:
                 r.raise_for_status()
-        
+
         ##### EXCEPTIONS #####
 
         except requests.exceptions.ConnectionError as e:
@@ -369,7 +364,7 @@ class DownloadUtils():
                                                 icon=xbmcgui.NOTIFICATION_ERROR,
                                                 time=5000)
                         return False
-                    
+
                     elif r.headers['X-Application-Error-Code'] == "UnauthorizedAccessException":
                         # User tried to do something his emby account doesn't allow
                         pass
diff --git a/resources/lib/embydb_functions.py b/resources/lib/embydb_functions.py
index 518498f5..cfebd5ff 100644
--- a/resources/lib/embydb_functions.py
+++ b/resources/lib/embydb_functions.py
@@ -1,325 +1,292 @@
-# -*- coding: utf-8 -*-
-
-#################################################################################################
-
-import utils
-import clientinfo
-
-#################################################################################################
-
-
-class Embydb_Functions():
-
-
-    def __init__(self, embycursor):
-
-        self.embycursor = embycursor
-
-        self.clientInfo = clientinfo.ClientInfo()
-        self.addonName = self.clientInfo.getAddonName()
-
-    def logMsg(self, msg, lvl=1):
-
-        className = self.__class__.__name__
-        utils.logMsg("%s %s" % (self.addonName, className), msg, lvl)
-
-    def getViews(self):
-
-        embycursor = self.embycursor
-        views = []
-
-        query = ' '.join((
-
-            "SELECT view_id",
-            "FROM view"
-        ))
-        embycursor.execute(query)
-        rows = embycursor.fetchall()
-        for row in rows:
-            views.append(row[0])
-        
-        return views
-
-    def getView_byId(self, viewid):
-
-        embycursor = self.embycursor
-
-        query = ' '.join((
-
-            "SELECT view_name, media_type, kodi_tagid",
-            "FROM view",
-            "WHERE view_id = ?"
-        ))
-        embycursor.execute(query, (viewid,))
-        view = embycursor.fetchone()
-        
-        return view
-
-    def getView_byType(self, mediatype):
-
-        embycursor = self.embycursor
-        views = []
-
-        query = ' '.join((
-
-            "SELECT view_id, view_name",
-            "FROM view",
-            "WHERE media_type = ?"
-        ))
-        embycursor.execute(query, (mediatype,))
-        rows = embycursor.fetchall()
-        for row in rows:
-            views.append({
-
-                'id': row[0],
-                'name': row[1]
-            })
-
-        return views
-
-    def getView_byName(self, tagname):
-
-        embycursor = self.embycursor
-
-        query = ' '.join((
-
-            "SELECT view_id",
-            "FROM view",
-            "WHERE view_name = ?"
-        ))
-        embycursor.execute(query, (tagname,))
-        try:
-            view = embycursor.fetchone()[0]
-        
-        except TypeError:
-            view = None
-
-        return view
-
-    def addView(self, embyid, name, mediatype, tagid):
-
-        query = (
-            '''
-            INSERT INTO view(
-                view_id, view_name, media_type, kodi_tagid)
-
-            VALUES (?, ?, ?, ?)
-            '''
-        )
-        self.embycursor.execute(query, (embyid, name, mediatype, tagid))
-
-    def updateView(self, name, tagid, mediafolderid):
-
-        query = ' '.join((
-
-            "UPDATE view",
-            "SET view_name = ?, kodi_tagid = ?",
-            "WHERE view_id = ?"
-        ))
-        self.embycursor.execute(query, (name, tagid, mediafolderid))
-
-    def removeView(self, viewid):
-
-        query = ' '.join((
-
-            "DELETE FROM view",
-            "WHERE view_id = ?"
-        ))
-        self.embycursor.execute(query, (viewid,))
-
-    def getItem_byId(self, embyid):
-
-        embycursor = self.embycursor
-
-        query = ' '.join((
-
-            "SELECT kodi_id, kodi_fileid, kodi_pathid, parent_id, media_type, emby_type",
-            "FROM emby",
-            "WHERE emby_id = ?"
-        ))
-        try:
-            embycursor.execute(query, (embyid,))
-            item = embycursor.fetchone()
-            return item
-        except: return None
-
-    def getItem_byWildId(self, embyid):
-
-        embycursor = self.embycursor
-
-        query = ' '.join((
-
-            "SELECT kodi_id, media_type",
-            "FROM emby",
-            "WHERE emby_id LIKE ?"
-        ))
-        embycursor.execute(query, (embyid+"%",))
-        items = embycursor.fetchall()
-
-        return items
-
-    def getItem_byView(self, mediafolderid):
-
-        embycursor = self.embycursor
-
-        query = ' '.join((
-
-            "SELECT kodi_id",
-            "FROM emby",
-            "WHERE media_folder = ?"
-        ))
-        embycursor.execute(query, (mediafolderid,))
-        items = embycursor.fetchall()
-
-        return items
-
-    def getItem_byKodiId(self, kodiid, mediatype):
-
-        embycursor = self.embycursor
-
-        query = ' '.join((
-
-            "SELECT emby_id, parent_id",
-            "FROM emby",
-            "WHERE kodi_id = ?",
-            "AND media_type = ?"
-        ))
-        embycursor.execute(query, (kodiid, mediatype,))
-        item = embycursor.fetchone()
-
-        return item
-
-    def getItem_byParentId(self, parentid, mediatype):
-
-        embycursor = self.embycursor
-
-        query = ' '.join((
-
-            "SELECT emby_id, kodi_id, kodi_fileid",
-            "FROM emby",
-            "WHERE parent_id = ?",
-            "AND media_type = ?"
-        ))
-        embycursor.execute(query, (parentid, mediatype,))
-        items = embycursor.fetchall()
-
-        return items
-
-    def getItemId_byParentId(self, parentid, mediatype):
-
-        embycursor = self.embycursor
-
-        query = ' '.join((
-
-            "SELECT emby_id, kodi_id",
-            "FROM emby",
-            "WHERE parent_id = ?",
-            "AND media_type = ?"
-        ))
-        embycursor.execute(query, (parentid, mediatype,))
-        items = embycursor.fetchall()
-
-        return items
-
-    def getChecksum(self, mediatype):
-
-        embycursor = self.embycursor
-
-        query = ' '.join((
-
-            "SELECT emby_id, checksum",
-            "FROM emby",
-            "WHERE emby_type = ?"
-        ))
-        embycursor.execute(query, (mediatype,))
-        items = embycursor.fetchall()
-
-        return items
-
-    def getMediaType_byId(self, embyid):
-
-        embycursor = self.embycursor
-
-        query = ' '.join((
-
-            "SELECT emby_type",
-            "FROM emby",
-            "WHERE emby_id = ?"
-        ))
-        embycursor.execute(query, (embyid,))
-        try:
-            itemtype = embycursor.fetchone()[0]
-        
-        except TypeError:
-            itemtype = None
-
-        return itemtype
-
-    def sortby_mediaType(self, itemids, unsorted=True):
-
-        sorted_items = {}
-        
-        for itemid in itemids:
-            
-            mediatype = self.getMediaType_byId(itemid)
-            if mediatype:
-                sorted_items.setdefault(mediatype, []).append(itemid)
-            elif unsorted:
-                sorted_items.setdefault('Unsorted', []).append(itemid)
-
-        return sorted_items
-
-    def addReference(self, embyid, kodiid, embytype, mediatype, fileid=None, pathid=None,
-                        parentid=None, checksum=None, mediafolderid=None):
-        query = (
-            '''
-            INSERT OR REPLACE INTO emby(
-                emby_id, kodi_id, kodi_fileid, kodi_pathid, emby_type, media_type, parent_id,
-                checksum, media_folder)
-
-            VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
-            '''
-        )
-        self.embycursor.execute(query, (embyid, kodiid, fileid, pathid, embytype, mediatype,
-            parentid, checksum, mediafolderid))
-
-    def updateReference(self, embyid, checksum):
-
-        query = "UPDATE emby SET checksum = ? WHERE emby_id = ?"
-        self.embycursor.execute(query, (checksum, embyid))
-
-    def updateParentId(self, embyid, parent_kodiid):
-        
-        query = "UPDATE emby SET parent_id = ? WHERE emby_id = ?"
-        self.embycursor.execute(query, (parent_kodiid, embyid))
-
-    def removeItems_byParentId(self, parent_kodiid, mediatype):
-
-        query = ' '.join((
-
-            "DELETE FROM emby",
-            "WHERE parent_id = ?",
-            "AND media_type = ?"
-        ))
-        self.embycursor.execute(query, (parent_kodiid, mediatype,))
-
-    def removeItem_byKodiId(self, kodiid, mediatype):
-
-        query = ' '.join((
-
-            "DELETE FROM emby",
-            "WHERE kodi_id = ?",
-            "AND media_type = ?"
-        ))
-        self.embycursor.execute(query, (kodiid, mediatype,))
-
-    def removeItem(self, embyid):
-
-        query = "DELETE FROM emby WHERE emby_id = ?"
-        self.embycursor.execute(query, (embyid,))
-
-    def removeWildItem(self, embyid):
-
-        query = "DELETE FROM emby WHERE emby_id LIKE ?"
-        self.embycursor.execute(query, (embyid+"%",))
+# -*- coding: utf-8 -*-
+
+#################################################################################################
+
+import utils
+import clientinfo
+
+#################################################################################################
+
+
+class Embydb_Functions():
+
+
+    def __init__(self, embycursor):
+
+        self.embycursor = embycursor
+
+        self.clientInfo = clientinfo.ClientInfo()
+        self.addonName = self.clientInfo.getAddonName()
+
+    def logMsg(self, msg, lvl=1):
+
+        className = self.__class__.__name__
+        utils.logMsg("%s %s" % (self.addonName, className), msg, lvl)
+
+    def getViews(self):
+
+        views = []
+
+        query = ' '.join((
+
+            "SELECT view_id",
+            "FROM view"
+        ))
+        self.embycursor.execute(query)
+        rows = self.embycursor.fetchall()
+        for row in rows:
+            views.append(row[0])
+        
+        return views
+
+    def getView_byId(self, viewid):
+
+
+        query = ' '.join((
+
+            "SELECT view_name, media_type, kodi_tagid",
+            "FROM view",
+            "WHERE view_id = ?"
+        ))
+        self.embycursor.execute(query, (viewid,))
+        view = self.embycursor.fetchone()
+        
+        return view
+
+    def getView_byType(self, mediatype):
+
+        views = []
+
+        query = ' '.join((
+
+            "SELECT view_id, view_name",
+            "FROM view",
+            "WHERE media_type = ?"
+        ))
+        self.embycursor.execute(query, (mediatype,))
+        rows = self.embycursor.fetchall()
+        for row in rows:
+            views.append({
+
+                'id': row[0],
+                'name': row[1]
+            })
+
+        return views
+
+    def getView_byName(self, tagname):
+
+        query = ' '.join((
+
+            "SELECT view_id",
+            "FROM view",
+            "WHERE view_name = ?"
+        ))
+        self.embycursor.execute(query, (tagname,))
+        try:
+            view = self.embycursor.fetchone()[0]
+        
+        except TypeError:
+            view = None
+
+        return view
+
+    def addView(self, embyid, name, mediatype, tagid):
+
+        query = (
+            '''
+            INSERT INTO view(
+                view_id, view_name, media_type, kodi_tagid)
+
+            VALUES (?, ?, ?, ?)
+            '''
+        )
+        self.embycursor.execute(query, (embyid, name, mediatype, tagid))
+
+    def updateView(self, name, tagid, mediafolderid):
+
+        query = ' '.join((
+
+            "UPDATE view",
+            "SET view_name = ?, kodi_tagid = ?",
+            "WHERE view_id = ?"
+        ))
+        self.embycursor.execute(query, (name, tagid, mediafolderid))
+
+    def removeView(self, viewid):
+
+        query = ' '.join((
+
+            "DELETE FROM view",
+            "WHERE view_id = ?"
+        ))
+        self.embycursor.execute(query, (viewid,))
+
+    def getItem_byId(self, embyid):
+
+        query = ' '.join((
+
+            "SELECT kodi_id, kodi_fileid, kodi_pathid, parent_id, media_type, emby_type",
+            "FROM emby",
+            "WHERE emby_id = ?"
+        ))
+        try:
+            self.embycursor.execute(query, (embyid,))
+            item = self.embycursor.fetchone()
+            return item
+        except: return None
+
+    def getItem_byWildId(self, embyid):
+
+        query = ' '.join((
+
+            "SELECT kodi_id, media_type",
+            "FROM emby",
+            "WHERE emby_id LIKE ?"
+        ))
+        self.embycursor.execute(query, (embyid+"%",))
+        return self.embycursor.fetchall()
+
+    def getItem_byView(self, mediafolderid):
+
+        query = ' '.join((
+
+            "SELECT kodi_id",
+            "FROM emby",
+            "WHERE media_folder = ?"
+        ))
+        self.embycursor.execute(query, (mediafolderid,))
+        return self.embycursor.fetchall()
+
+    def getItem_byKodiId(self, kodiid, mediatype):
+
+        query = ' '.join((
+
+            "SELECT emby_id, parent_id",
+            "FROM emby",
+            "WHERE kodi_id = ?",
+            "AND media_type = ?"
+        ))
+        self.embycursor.execute(query, (kodiid, mediatype,))
+        return self.embycursor.fetchone()
+
+    def getItem_byParentId(self, parentid, mediatype):
+
+        query = ' '.join((
+
+            "SELECT emby_id, kodi_id, kodi_fileid",
+            "FROM emby",
+            "WHERE parent_id = ?",
+            "AND media_type = ?"
+        ))
+        self.embycursor.execute(query, (parentid, mediatype,))
+        return self.embycursor.fetchall()
+
+    def getItemId_byParentId(self, parentid, mediatype):
+
+        query = ' '.join((
+
+            "SELECT emby_id, kodi_id",
+            "FROM emby",
+            "WHERE parent_id = ?",
+            "AND media_type = ?"
+        ))
+        self.embycursor.execute(query, (parentid, mediatype,))
+        return self.embycursor.fetchall()
+
+    def getChecksum(self, mediatype):
+
+        query = ' '.join((
+
+            "SELECT emby_id, checksum",
+            "FROM emby",
+            "WHERE emby_type = ?"
+        ))
+        self.embycursor.execute(query, (mediatype,))
+        return self.embycursor.fetchall()
+
+    def getMediaType_byId(self, embyid):
+
+        query = ' '.join((
+
+            "SELECT emby_type",
+            "FROM emby",
+            "WHERE emby_id = ?"
+        ))
+        self.embycursor.execute(query, (embyid,))
+        try:
+            itemtype = self.embycursor.fetchone()[0]
+        
+        except TypeError:
+            itemtype = None
+
+        return itemtype
+
+    def sortby_mediaType(self, itemids, unsorted=True):
+
+        sorted_items = {}
+        
+        for itemid in itemids:
+            
+            mediatype = self.getMediaType_byId(itemid)
+            if mediatype:
+                sorted_items.setdefault(mediatype, []).append(itemid)
+            elif unsorted:
+                sorted_items.setdefault('Unsorted', []).append(itemid)
+
+        return sorted_items
+
+    def addReference(self, embyid, kodiid, embytype, mediatype, fileid=None, pathid=None,
+                        parentid=None, checksum=None, mediafolderid=None):
+        query = (
+            '''
+            INSERT OR REPLACE INTO emby(
+                emby_id, kodi_id, kodi_fileid, kodi_pathid, emby_type, media_type, parent_id,
+                checksum, media_folder)
+
+            VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
+            '''
+        )
+        self.embycursor.execute(query, (embyid, kodiid, fileid, pathid, embytype, mediatype,
+            parentid, checksum, mediafolderid))
+
+    def updateReference(self, embyid, checksum):
+
+        query = "UPDATE emby SET checksum = ? WHERE emby_id = ?"
+        self.embycursor.execute(query, (checksum, embyid))
+
+    def updateParentId(self, embyid, parent_kodiid):
+        
+        query = "UPDATE emby SET parent_id = ? WHERE emby_id = ?"
+        self.embycursor.execute(query, (parent_kodiid, embyid))
+
+    def removeItems_byParentId(self, parent_kodiid, mediatype):
+
+        query = ' '.join((
+
+            "DELETE FROM emby",
+            "WHERE parent_id = ?",
+            "AND media_type = ?"
+        ))
+        self.embycursor.execute(query, (parent_kodiid, mediatype,))
+
+    def removeItem_byKodiId(self, kodiid, mediatype):
+
+        query = ' '.join((
+
+            "DELETE FROM emby",
+            "WHERE kodi_id = ?",
+            "AND media_type = ?"
+        ))
+        self.embycursor.execute(query, (kodiid, mediatype,))
+
+    def removeItem(self, embyid):
+
+        query = "DELETE FROM emby WHERE emby_id = ?"
+        self.embycursor.execute(query, (embyid,))
+
+    def removeWildItem(self, embyid):
+
+        query = "DELETE FROM emby WHERE emby_id LIKE ?"
+        self.embycursor.execute(query, (embyid+"%",))
         
\ No newline at end of file
diff --git a/resources/lib/entrypoint.py b/resources/lib/entrypoint.py
index 11c66f5d..bc81ad0a 100644
--- a/resources/lib/entrypoint.py
+++ b/resources/lib/entrypoint.py
@@ -1,1089 +1,1089 @@
-# -*- coding: utf-8 -*-
-
-#################################################################################################
-
-import json
-import os
-import sys
-import urlparse
-
-import xbmc
-import xbmcaddon
-import xbmcgui
-import xbmcvfs
-import xbmcplugin
-
-import artwork
-import utils
-import clientinfo
-import downloadutils
-import librarysync
-import read_embyserver as embyserver
-import embydb_functions as embydb
-import playlist
-import playbackutils as pbutils
-import playutils
-import api
-
-
-#################################################################################################
-
-
-def doPlayback(itemid, dbid):
-
-    emby = embyserver.Read_EmbyServer()
-    item = emby.getItem(itemid)
-    pbutils.PlaybackUtils(item).play(itemid, dbid)
-
-##### DO RESET AUTH #####
-def resetAuth():
-    # User tried login and failed too many times
-    resp = xbmcgui.Dialog().yesno(
-                heading="Warning",
-                line1=(
-                    "Emby might lock your account if you fail to log in too many times. "
-                    "Proceed anyway?"))
-    if resp == 1:
-        utils.logMsg("EMBY", "Reset login attempts.", 1)
-        utils.window('emby_serverStatus', value="Auth")
-    else:
-        xbmc.executebuiltin('Addon.OpenSettings(plugin.video.emby)')
-
-def addDirectoryItem(label, path, folder=True):
-    li = xbmcgui.ListItem(label, path=path)
-    li.setThumbnailImage("special://home/addons/plugin.video.emby/icon.png")
-    li.setArt({"fanart":"special://home/addons/plugin.video.emby/fanart.jpg"})
-    li.setArt({"landscape":"special://home/addons/plugin.video.emby/fanart.jpg"})
-    xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=path, listitem=li, isFolder=folder)
-
-def doMainListing():
-    xbmcplugin.setContent(int(sys.argv[1]), 'files')    
-    # Get emby nodes from the window props
-    embyprops = utils.window('Emby.nodes.total')
-    if embyprops:
-        totalnodes = int(embyprops)
-        for i in range(totalnodes):
-            path = utils.window('Emby.nodes.%s.index' % i)
-            if not path:
-                path = utils.window('Emby.nodes.%s.content' % i)
-            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 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":
-                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")
-    addDirectoryItem("Add user to session", "plugin://plugin.video.emby/?mode=adduser")
-    addDirectoryItem("Refresh Emby playlists/nodes", "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]))
-
-
-##### Generate a new deviceId
-def resetDeviceId():
-
-    dialog = xbmcgui.Dialog()
-    language = utils.language
-
-    deviceId_old = utils.window('emby_deviceId')
-    try:
-        utils.window('emby_deviceId', clear=True)
-        deviceId = clientinfo.ClientInfo().getDeviceId(reset=True)
-    except Exception as e:
-        utils.logMsg("EMBY", "Failed to generate a new device Id: %s" % e, 1)
-        dialog.ok(
-            heading="Emby for Kodi",
-            line1=language(33032))
-    else:
-        utils.logMsg("EMBY", "Successfully removed old deviceId: %s New deviceId: %s"
-                    % (deviceId_old, deviceId), 1)
-        dialog.ok(
-            heading="Emby for Kodi",
-            line1=language(33033))
-        xbmc.executebuiltin('RestartApp')
-
-##### Delete Item
-def deleteItem():
-
-    # Serves as a keymap action
-    if xbmc.getInfoLabel('ListItem.Property(embyid)'): # If we already have the embyid
-        embyid = xbmc.getInfoLabel('ListItem.Property(embyid)')
-    else:
-        dbid = xbmc.getInfoLabel('ListItem.DBID')
-        itemtype = xbmc.getInfoLabel('ListItem.DBTYPE')
-
-        if not itemtype:
-
-            if xbmc.getCondVisibility('Container.Content(albums)'):
-                itemtype = "album"
-            elif xbmc.getCondVisibility('Container.Content(artists)'):
-                itemtype = "artist"
-            elif xbmc.getCondVisibility('Container.Content(songs)'):
-                itemtype = "song"
-            elif xbmc.getCondVisibility('Container.Content(pictures)'):
-                itemtype = "picture"
-            else:
-                utils.logMsg("EMBY delete", "Unknown type, unable to proceed.", 1)
-                return
-
-        embyconn = utils.kodiSQL('emby')
-        embycursor = embyconn.cursor()
-        emby_db = embydb.Embydb_Functions(embycursor)
-        item = emby_db.getItem_byKodiId(dbid, itemtype)
-        embycursor.close()
-
-        try:
-            embyid = item[0]
-        except TypeError:
-            utils.logMsg("EMBY delete", "Unknown embyId, unable to proceed.", 1)
-            return
-
-    if utils.settings('skipContextMenu') != "true":
-        resp = xbmcgui.Dialog().yesno(
-                                heading="Confirm delete",
-                                line1=("Delete file from Emby Server? This will "
-                                        "also delete the file(s) from disk!"))
-        if not resp:
-            utils.logMsg("EMBY delete", "User skipped deletion for: %s." % embyid, 1)
-            return
-    
-    doUtils = downloadutils.DownloadUtils()
-    url = "{server}/emby/Items/%s?format=json" % embyid
-    utils.logMsg("EMBY delete", "Deleting request: %s" % embyid, 0)
-    doUtils.downloadUrl(url, type="DELETE")
-
-##### ADD ADDITIONAL USERS #####
-def addUser():
-
-    doUtils = downloadutils.DownloadUtils()
-    art = artwork.Artwork()
-    clientInfo = clientinfo.ClientInfo()
-    deviceId = clientInfo.getDeviceId()
-    deviceName = clientInfo.getDeviceName()
-    userid = utils.window('emby_currUser')
-    dialog = xbmcgui.Dialog()
-
-    # Get session
-    url = "{server}/emby/Sessions?DeviceId=%s&format=json" % deviceId
-    result = doUtils.downloadUrl(url)
-    
-    try:
-        sessionId = result[0]['Id']
-        additionalUsers = result[0]['AdditionalUsers']
-        # Add user to session
-        userlist = {}
-        users = []
-        url = "{server}/emby/Users?IsDisabled=false&IsHidden=false&format=json"
-        result = doUtils.downloadUrl(url)
-
-        # pull the list of users
-        for user in result:
-            name = user['Name']
-            userId = user['Id']
-            if userid != userId:
-                userlist[name] = userId
-                users.append(name)
-
-        # Display dialog if there's additional users
-        if additionalUsers:
-
-            option = dialog.select("Add/Remove user from the session", ["Add user", "Remove user"])
-            # Users currently in the session
-            additionalUserlist = {}
-            additionalUsername = []
-            # Users currently in the session
-            for user in additionalUsers:
-                name = user['UserName']
-                userId = user['UserId']
-                additionalUserlist[name] = userId
-                additionalUsername.append(name)
-
-            if option == 1:
-                # User selected Remove user
-                resp = dialog.select("Remove user from the session", additionalUsername)
-                if resp > -1:
-                    selected = additionalUsername[resp]
-                    selected_userId = additionalUserlist[selected]
-                    url = "{server}/emby/Sessions/%s/Users/%s" % (sessionId, selected_userId)
-                    doUtils.downloadUrl(url, postBody={}, type="DELETE")
-                    dialog.notification(
-                            heading="Success!",
-                            message="%s removed from viewing session" % selected,
-                            icon="special://home/addons/plugin.video.emby/icon.png",
-                            time=1000)
-
-                    # clear picture
-                    position = utils.window('EmbyAdditionalUserPosition.%s' % selected_userId)
-                    utils.window('EmbyAdditionalUserImage.%s' % position, clear=True)
-                    return
-                else:
-                    return
-
-            elif option == 0:
-                # User selected Add user
-                for adduser in additionalUsername:
-                    try: # Remove from selected already added users. It is possible they are hidden.
-                        users.remove(adduser)
-                    except: pass
-
-            elif option < 0:
-                # User cancelled
-                return
-
-        # Subtract any additional users
-        utils.logMsg("EMBY", "Displaying list of users: %s" % users)
-        resp = dialog.select("Add user to the session", users)
-        # post additional user
-        if resp > -1:
-            selected = users[resp]
-            selected_userId = userlist[selected]
-            url = "{server}/emby/Sessions/%s/Users/%s" % (sessionId, selected_userId)
-            doUtils.downloadUrl(url, postBody={}, type="POST")
-            dialog.notification(
-                    heading="Success!",
-                    message="%s added to viewing session" % selected,
-                    icon="special://home/addons/plugin.video.emby/icon.png",
-                    time=1000)
-
-    except:
-        utils.logMsg("EMBY", "Failed to add user to session.")
-        dialog.notification(
-                heading="Error",
-                message="Unable to add/remove user from the session.",
-                icon=xbmcgui.NOTIFICATION_ERROR)
-
-    # Add additional user images
-    # always clear the individual items first
-    totalNodes = 10
-    for i in range(totalNodes):
-        if not utils.window('EmbyAdditionalUserImage.%s' % i):
-            break
-        utils.window('EmbyAdditionalUserImage.%s' % i, clear=True)
-
-    url = "{server}/emby/Sessions?DeviceId=%s" % deviceId
-    result = doUtils.downloadUrl(url)
-    additionalUsers = result[0]['AdditionalUsers']
-    count = 0
-    for additionaluser in additionalUsers:
-        userid = additionaluser['UserId']
-        url = "{server}/emby/Users/%s?format=json" % userid
-        result = doUtils.downloadUrl(url)
-        utils.window('EmbyAdditionalUserImage.%s' % count,
-            value=art.getUserArtwork(result['Id'], 'Primary'))
-        utils.window('EmbyAdditionalUserPosition.%s' % userid, value=str(count))
-        count +=1
-
-##### THEME MUSIC/VIDEOS #####
-def getThemeMedia():
-
-    doUtils = downloadutils.DownloadUtils()
-    dialog = xbmcgui.Dialog()
-    playback = None
-
-    # Choose playback method
-    resp = dialog.select("Playback method for your themes", ["Direct Play", "Direct Stream"])
-    if resp == 0:
-        playback = "DirectPlay"
-    elif resp == 1:
-        playback = "DirectStream"
-    else:
-        return
-
-    library = xbmc.translatePath(
-                "special://profile/addon_data/plugin.video.emby/library/").decode('utf-8')
-    # Create library directory
-    if not xbmcvfs.exists(library):
-        xbmcvfs.mkdir(library)
-
-    # Set custom path for user
-    tvtunes_path = xbmc.translatePath(
-        "special://profile/addon_data/script.tvtunes/").decode('utf-8')
-    if xbmcvfs.exists(tvtunes_path):
-        tvtunes = xbmcaddon.Addon(id="script.tvtunes")
-        tvtunes.setSetting('custom_path_enable', "true")
-        tvtunes.setSetting('custom_path', library)
-        utils.logMsg("EMBY", "TV Tunes custom path is enabled and set.", 1)
-    else:
-        # if it does not exist this will not work so warn user
-        # often they need to edit the settings first for it to be created.
-        dialog.ok(
-            heading="Warning",
-            line1=(
-                "The settings file does not exist in tvtunes. ",
-                "Go to the tvtunes addon and change a setting, then come back and re-run."))
-        xbmc.executebuiltin('Addon.OpenSettings(script.tvtunes)')
-        return
-        
-    # Get every user view Id
-    embyconn = utils.kodiSQL('emby')
-    embycursor = embyconn.cursor()
-    emby_db = embydb.Embydb_Functions(embycursor)
-    viewids = emby_db.getViews()
-    embycursor.close()
-
-    # Get Ids with Theme Videos
-    itemIds = {}
-    for view in viewids:
-        url = "{server}/emby/Users/{UserId}/Items?HasThemeVideo=True&ParentId=%s&format=json" % view
-        result = doUtils.downloadUrl(url)
-        if result['TotalRecordCount'] != 0:
-            for item in result['Items']:
-                itemId = item['Id']
-                folderName = item['Name']
-                folderName = utils.normalize_string(folderName.encode('utf-8'))
-                itemIds[itemId] = folderName
-
-    # Get paths for theme videos
-    for itemId in itemIds:
-        nfo_path = xbmc.translatePath(
-            "special://profile/addon_data/plugin.video.emby/library/%s/" % itemIds[itemId])
-        # Create folders for each content
-        if not xbmcvfs.exists(nfo_path):
-            xbmcvfs.mkdir(nfo_path)
-        # Where to put the nfos
-        nfo_path = "%s%s" % (nfo_path, "tvtunes.nfo")
-
-        url = "{server}/emby/Items/%s/ThemeVideos?format=json" % itemId
-        result = doUtils.downloadUrl(url)
-
-        # Create nfo and write themes to it
-        nfo_file = xbmcvfs.File(nfo_path, 'w')
-        pathstowrite = ""
-        # May be more than one theme
-        for theme in result['Items']:
-            putils = playutils.PlayUtils(theme)
-            if playback == "DirectPlay":
-                playurl = putils.directPlay()
-            else:
-                playurl = putils.directStream()
-            pathstowrite += ('<file>%s</file>' % playurl.encode('utf-8'))
-        
-        # Check if the item has theme songs and add them   
-        url = "{server}/emby/Items/%s/ThemeSongs?format=json" % itemId
-        result = doUtils.downloadUrl(url)
-
-        # May be more than one theme
-        for theme in result['Items']:
-            putils = playutils.PlayUtils(theme)  
-            if playback == "DirectPlay":
-                playurl = putils.directPlay()
-            else:
-                playurl = putils.directStream()
-            pathstowrite += ('<file>%s</file>' % playurl.encode('utf-8'))
-
-        nfo_file.write(
-            '<tvtunes>%s</tvtunes>' % pathstowrite
-        )
-        # Close nfo file
-        nfo_file.close()
-
-    # Get Ids with Theme songs
-    musicitemIds = {}
-    for view in viewids:
-        url = "{server}/emby/Users/{UserId}/Items?HasThemeSong=True&ParentId=%s&format=json" % view
-        result = doUtils.downloadUrl(url)
-        if result['TotalRecordCount'] != 0:
-            for item in result['Items']:
-                itemId = item['Id']
-                folderName = item['Name']
-                folderName = utils.normalize_string(folderName.encode('utf-8'))
-                musicitemIds[itemId] = folderName
-
-    # Get paths
-    for itemId in musicitemIds:
-        
-        # if the item was already processed with video themes back out
-        if itemId in itemIds:
-            continue
-        
-        nfo_path = xbmc.translatePath(
-            "special://profile/addon_data/plugin.video.emby/library/%s/" % musicitemIds[itemId])
-        # Create folders for each content
-        if not xbmcvfs.exists(nfo_path):
-            xbmcvfs.mkdir(nfo_path)
-        # Where to put the nfos
-        nfo_path = "%s%s" % (nfo_path, "tvtunes.nfo")
-        
-        url = "{server}/emby/Items/%s/ThemeSongs?format=json" % itemId
-        result = doUtils.downloadUrl(url)
-
-        # Create nfo and write themes to it
-        nfo_file = xbmcvfs.File(nfo_path, 'w')
-        pathstowrite = ""
-        # May be more than one theme
-        for theme in result['Items']: 
-            putils = playutils.PlayUtils(theme)
-            if playback == "DirectPlay":
-                playurl = putils.directPlay()
-            else:
-                playurl = putils.directStream()
-            pathstowrite += ('<file>%s</file>' % playurl.encode('utf-8'))
-
-        nfo_file.write(
-            '<tvtunes>%s</tvtunes>' % pathstowrite
-        )
-        # Close nfo file
-        nfo_file.close()
-
-    dialog.notification(
-            heading="Emby for Kodi",
-            message="Themes added!",
-            icon="special://home/addons/plugin.video.emby/icon.png",
-            time=1000,
-            sound=False)
-
-##### REFRESH EMBY PLAYLISTS #####
-def refreshPlaylist():
-
-    lib = librarysync.LibrarySync()
-    dialog = xbmcgui.Dialog()
-    try:
-        # First remove playlists
-        utils.deletePlaylists()
-        # Remove video nodes
-        utils.deleteNodes()
-        # Refresh views
-        lib.refreshViews()
-        dialog.notification(
-                heading="Emby for Kodi",
-                message="Emby playlists/nodes refreshed",
-                icon="special://home/addons/plugin.video.emby/icon.png",
-                time=1000,
-                sound=False)
-
-    except Exception as e:
-        utils.logMsg("EMBY", "Refresh playlists/nodes failed: %s" % e, 1)
-        dialog.notification(
-            heading="Emby for Kodi",
-            message="Emby playlists/nodes refresh failed",
-            icon=xbmcgui.NOTIFICATION_ERROR,
-            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=""):
-    
-    emby = embyserver.Read_EmbyServer()
-    art = artwork.Artwork()
-    doUtils = downloadutils.DownloadUtils()
-    
-    #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:
-        views = emby.getViews(type)
-        for view in views:
-            if view.get("name") == viewname.decode('utf-8'):
-                folderid = view.get("id")
-    
-    if viewname is not None:
-        utils.logMsg("BrowseContent","viewname: %s - type: %s - folderid: %s - filter: %s" %(viewname.decode('utf-8'), type.decode('utf-8'), folderid.decode('utf-8'), filter.decode('utf-8')))
-    #set the correct params for the content type
-    #only proceed if we have a folderid
-    if folderid:
-        if type.lower() == "homevideos":
-            xbmcplugin.setContent(int(sys.argv[1]), 'episodes')
-            itemtype = "Video,Folder,PhotoAlbum"
-        elif type.lower() == "photos":
-            xbmcplugin.setContent(int(sys.argv[1]), 'files')
-            itemtype = "Photo,PhotoAlbum,Folder"
-        else:
-            itemtype = ""
-        
-        #get the actual listing
-        if type == "recordings":
-            listing = emby.getTvRecordings(folderid)
-        elif type == "tvchannels":
-            listing = emby.getTvChannels()
-        elif filter == "recent":
-            listing = emby.getFilteredSection(folderid, itemtype=itemtype.split(",")[0], sortby="DateCreated", recursive=True, limit=25, sortorder="Descending")
-        elif filter == "random":
-            listing = emby.getFilteredSection(folderid, itemtype=itemtype.split(",")[0], sortby="Random", recursive=True, limit=150, sortorder="Descending")
-        elif filter == "recommended":
-            listing = emby.getFilteredSection(folderid, itemtype=itemtype.split(",")[0], sortby="SortName", recursive=True, limit=25, sortorder="Ascending", filter="IsFavorite")
-        elif filter == "sets":
-            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)
-        
-        #process the listing
-        if listing:
-            for item in listing.get("Items"):
-                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].decode('utf-8'), viewname.decode('utf-8'), type.decode('utf-8'), item.get("Id").decode('utf-8'))
-                    xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=path, listitem=li, isFolder=True)
-                else:
-                    #playable item, set plugin path and mediastreams
-                    xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=li.getProperty("path"), listitem=li)
-
-
-    if filter == "recent":
-        xbmcplugin.addSortMethod(int(sys.argv[1]), xbmcplugin.SORT_METHOD_DATE)
-    else:
-        xbmcplugin.addSortMethod(int(sys.argv[1]), xbmcplugin.SORT_METHOD_VIDEO_TITLE)
-        xbmcplugin.addSortMethod(int(sys.argv[1]), xbmcplugin.SORT_METHOD_DATE)
-        xbmcplugin.addSortMethod(int(sys.argv[1]), xbmcplugin.SORT_METHOD_VIDEO_RATING)
-        xbmcplugin.addSortMethod(int(sys.argv[1]), xbmcplugin.SORT_METHOD_VIDEO_RUNTIME)
-
-    xbmcplugin.endOfDirectory(handle=int(sys.argv[1]))
-
-##### CREATE LISTITEM FROM EMBY METADATA #####
-def createListItemFromEmbyItem(item,art=artwork.Artwork(),doUtils=downloadutils.DownloadUtils()):
-    API = api.API(item)
-    itemid = item['Id']
-    
-    title = item.get('Name')
-    li = xbmcgui.ListItem(title)
-    
-    premieredate = item.get('PremiereDate',"")
-    if not premieredate: premieredate = item.get('DateCreated',"")
-    if premieredate:
-        premieredatelst = premieredate.split('T')[0].split("-")
-        premieredate = "%s.%s.%s" %(premieredatelst[2],premieredatelst[1],premieredatelst[0])
-
-    li.setProperty("embyid",itemid)
-    
-    allart = art.getAllArtwork(item)
-    
-    if item["Type"] == "Photo":
-        #listitem setup for pictures...
-        img_path = allart.get('Primary')
-        li.setProperty("path",img_path)
-        picture = doUtils.downloadUrl("{server}/Items/%s/Images" %itemid)
-        if picture:
-            picture = picture[0]
-            if picture.get("Width") > picture.get("Height"):
-                li.setArt( {"fanart":  img_path}) #add image as fanart for use with skinhelper auto thumb/backgrund creation
-            li.setInfo('pictures', infoLabels={ "picturepath": img_path, "date": premieredate, "size": picture.get("Size"), "exif:width": str(picture.get("Width")), "exif:height": str(picture.get("Height")), "title": title})
-        li.setThumbnailImage(img_path)
-        li.setProperty("plot",API.getOverview())
-        li.setIconImage('DefaultPicture.png')
-    else:
-        #normal video items
-        li.setProperty('IsPlayable', 'true')
-        path = "%s?id=%s&mode=play" % (sys.argv[0], item.get("Id"))
-        li.setProperty("path",path)
-        genre = API.getGenres()
-        overlay = 0
-        userdata = API.getUserData()
-        runtime = item.get("RunTimeTicks",0)/ 10000000.0
-        seektime = userdata['Resume']
-        if seektime:
-            li.setProperty("resumetime", str(seektime))
-            li.setProperty("totaltime", str(runtime))
-        
-        played = userdata['Played']
-        if played: overlay = 7
-        else: overlay = 6       
-        playcount = userdata['PlayCount']
-        if playcount is None:
-            playcount = 0
-            
-        rating = item.get('CommunityRating')
-        if not rating: rating = userdata['UserRating']
-
-        # Populate the extradata list and artwork
-        extradata = {
-            'id': itemid,
-            'rating': rating,
-            'year': item.get('ProductionYear'),
-            '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)
-        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
-    
-##### BROWSE EMBY CHANNELS #####    
-def BrowseChannels(itemid, folderid=None):
-    
-    _addon_id   =   int(sys.argv[1])
-    _addon_url  =   sys.argv[0]
-    doUtils = downloadutils.DownloadUtils()
-    art = artwork.Artwork()
-
-    xbmcplugin.setContent(int(sys.argv[1]), 'files')
-    if folderid:
-        url = (
-                "{server}/emby/Channels/%s/Items?userid={UserId}&folderid=%s&format=json"
-                % (itemid, folderid))
-    elif itemid == "0":
-        # id 0 is the root channels folder
-        url = "{server}/emby/Channels?{UserId}&format=json"
-    else:
-        url = "{server}/emby/Channels/%s/Items?UserId={UserId}&format=json" % itemid
-
-    result = doUtils.downloadUrl(url)
-    if result and result.get("Items"):
-        for item in result.get("Items"):
-            itemid = item['Id']
-            itemtype = item['Type']
-            li = createListItemFromEmbyItem(item,art,doUtils)
-            
-            isFolder = item.get('IsFolder', False)
-
-            channelId = item.get('ChannelId', "")
-            channelName = item.get('ChannelName', "")
-            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)
-            else:
-                path = "%s?id=%s&mode=play" % (_addon_url, itemid)
-                li.setProperty('IsPlayable', 'true')
-                xbmcplugin.addDirectoryItem(handle=_addon_id, url=path, listitem=li)
-
-    xbmcplugin.endOfDirectory(handle=int(sys.argv[1]))
-
-##### LISTITEM SETUP FOR VIDEONODES #####
-def createListItem(item):
-
-    title = item['title']
-    li = xbmcgui.ListItem(title)
-    li.setProperty('IsPlayable', "true")
-    
-    metadata = {
-
-        'Title': title,
-        'duration': str(item['runtime']/60),
-        'Plot': item['plot'],
-        'Playcount': item['playcount']
-    }
-
-    if "episode" in item:
-        episode = item['episode']
-        metadata['Episode'] = episode
-
-    if "season" in item:
-        season = item['season']
-        metadata['Season'] = season
-
-    if season and episode:
-        li.setProperty('episodeno', "s%.2de%.2d" % (season, episode))
-
-    if "firstaired" in item:
-        metadata['Premiered'] = item['firstaired']
-
-    if "showtitle" in item:
-        metadata['TVshowTitle'] = item['showtitle']
-
-    if "rating" in item:
-        metadata['Rating'] = str(round(float(item['rating']),1))
-
-    if "director" in item:
-        metadata['Director'] = " / ".join(item['director'])
-
-    if "writer" in item:
-        metadata['Writer'] = " / ".join(item['writer'])
-
-    if "cast" in item:
-        cast = []
-        castandrole = []
-        for person in item['cast']:
-            name = person['name']
-            cast.append(name)
-            castandrole.append((name, person['role']))
-        metadata['Cast'] = cast
-        metadata['CastAndRole'] = castandrole
-
-    li.setInfo(type="Video", infoLabels=metadata)  
-    li.setProperty('resumetime', str(item['resume']['position']))
-    li.setProperty('totaltime', str(item['resume']['total']))
-    li.setArt(item['art'])
-    li.setThumbnailImage(item['art'].get('thumb',''))
-    li.setIconImage('DefaultTVShows.png')
-    li.setProperty('dbid', str(item['episodeid']))
-    li.setProperty('fanart_image', item['art'].get('tvshow.fanart',''))
-    for key, value in item['streamdetails'].iteritems():
-        for stream in value:
-            li.addStreamInfo(key, stream)
-    
-    return li
-
-##### GET NEXTUP EPISODES FOR TAGNAME #####    
-def getNextUpEpisodes(tagname, limit):
-    
-    count = 0
-    # if the addon is called with nextup parameter,
-    # we return the nextepisodes list of the given tagname
-    xbmcplugin.setContent(int(sys.argv[1]), 'episodes')
-    # First we get a list of all the TV shows - filtered by tag
-    query = {
-
-        'jsonrpc': "2.0",
-        'id': "libTvShows",
-        'method': "VideoLibrary.GetTVShows",
-        'params': {
-
-            'sort': {'order': "descending", 'method': "lastplayed"},
-            'filter': {
-                'and': [
-                    {'operator': "true", 'field': "inprogress", 'value': ""},
-                    {'operator': "is", 'field': "tag", 'value': "%s" % tagname}
-                ]},
-            'properties': ['title', 'studio', 'mpaa', 'file', 'art']
-        }
-    }
-    result = xbmc.executeJSONRPC(json.dumps(query))
-    result = json.loads(result)
-    # If we found any, find the oldest unwatched show for each one.
-    try:
-        items = result['result']['tvshows']
-    except (KeyError, TypeError):
-        pass
-    else:
-        for item in items:
-            if utils.settings('ignoreSpecialsNextEpisodes') == "true":
-                query = {
-
-                    'jsonrpc': "2.0",
-                    'id': 1,
-                    'method': "VideoLibrary.GetEpisodes",
-                    'params': {
-
-                        'tvshowid': item['tvshowid'],
-                        'sort': {'method': "episode"},
-                        'filter': {
-                            'and': [
-                                {'operator': "lessthan", 'field': "playcount", 'value': "1"},
-                                {'operator': "greaterthan", 'field': "season", 'value': "0"}
-                        ]},
-                        'properties': [
-                            "title", "playcount", "season", "episode", "showtitle",
-                            "plot", "file", "rating", "resume", "tvshowid", "art",
-                            "streamdetails", "firstaired", "runtime", "writer",
-                            "dateadded", "lastplayed"
-                        ],
-                        'limits': {"end": 1}
-                    }
-                }
-            else:
-                query = {
-
-                    'jsonrpc': "2.0",
-                    'id': 1,
-                    'method': "VideoLibrary.GetEpisodes",
-                    'params': {
-
-                        'tvshowid': item['tvshowid'],
-                        'sort': {'method': "episode"},
-                        'filter': {'operator': "lessthan", 'field': "playcount", 'value': "1"},
-                        'properties': [
-                            "title", "playcount", "season", "episode", "showtitle",
-                            "plot", "file", "rating", "resume", "tvshowid", "art",
-                            "streamdetails", "firstaired", "runtime", "writer",
-                            "dateadded", "lastplayed"
-                        ],
-                        'limits': {"end": 1}
-                    }
-                }
-
-            result = xbmc.executeJSONRPC(json.dumps(query))
-            result = json.loads(result)
-            try:
-                episodes = result['result']['episodes']
-            except (KeyError, TypeError):
-                pass
-            else:
-                for episode in episodes:
-                    li = createListItem(episode)
-                    xbmcplugin.addDirectoryItem(
-                                handle=int(sys.argv[1]),
-                                url=episode['file'],
-                                listitem=li)
-                    count += 1
-
-            if count == limit:
-                break
-
-    xbmcplugin.endOfDirectory(handle=int(sys.argv[1]))
-
-##### GET INPROGRESS EPISODES FOR TAGNAME #####    
-def getInProgressEpisodes(tagname, limit):
-    
-    count = 0
-    # if the addon is called with inprogressepisodes parameter,
-    # we return the inprogressepisodes list of the given tagname
-    xbmcplugin.setContent(int(sys.argv[1]), 'episodes')
-    # First we get a list of all the in-progress TV shows - filtered by tag
-    query = {
-
-        'jsonrpc': "2.0",
-        'id': "libTvShows",
-        'method': "VideoLibrary.GetTVShows",
-        'params': {
-
-            'sort': {'order': "descending", 'method': "lastplayed"},
-            'filter': {
-                'and': [
-                    {'operator': "true", 'field': "inprogress", 'value': ""},
-                    {'operator': "is", 'field': "tag", 'value': "%s" % tagname}
-                ]},
-            'properties': ['title', 'studio', 'mpaa', 'file', 'art']
-        }
-    }
-    result = xbmc.executeJSONRPC(json.dumps(query))
-    result = json.loads(result)
-    # If we found any, find the oldest unwatched show for each one.
-    try:
-        items = result['result']['tvshows']
-    except (KeyError, TypeError):
-        pass
-    else:
-        for item in items:
-            query = {
-
-                'jsonrpc': "2.0",
-                'id': 1,
-                'method': "VideoLibrary.GetEpisodes",
-                'params': {
-
-                    'tvshowid': item['tvshowid'],
-                    'sort': {'method': "episode"},
-                    'filter': {'operator': "true", 'field': "inprogress", 'value': ""},
-                    'properties': [
-                        "title", "playcount", "season", "episode", "showtitle", "plot",
-                        "file", "rating", "resume", "tvshowid", "art", "cast",
-                        "streamdetails", "firstaired", "runtime", "writer",
-                        "dateadded", "lastplayed"
-                    ]
-                }
-            }
-            result = xbmc.executeJSONRPC(json.dumps(query))
-            result = json.loads(result)
-            try:
-                episodes = result['result']['episodes']
-            except (KeyError, TypeError):
-                pass
-            else:
-                for episode in episodes:
-                    li = createListItem(episode)
-                    xbmcplugin.addDirectoryItem(
-                                handle=int(sys.argv[1]),
-                                url=episode['file'],
-                                listitem=li)
-                    count += 1
-
-            if count == limit:
-                break
-
-    xbmcplugin.endOfDirectory(handle=int(sys.argv[1]))
-
-##### GET RECENT EPISODES FOR TAGNAME #####    
-def getRecentEpisodes(tagname, limit):
-    
-    count = 0
-    # if the addon is called with recentepisodes parameter,
-    # we return the recentepisodes list of the given tagname
-    xbmcplugin.setContent(int(sys.argv[1]), 'episodes')
-    # First we get a list of all the TV shows - filtered by tag
-    query = {
-
-        'jsonrpc': "2.0",
-        'id': "libTvShows",
-        'method': "VideoLibrary.GetTVShows",
-        'params': {
-
-            'sort': {'order': "descending", 'method': "dateadded"},
-            'filter': {'operator': "is", 'field': "tag", 'value': "%s" % tagname},
-            'properties': ["title","sorttitle"]
-        }
-    }
-    result = xbmc.executeJSONRPC(json.dumps(query))
-    result = json.loads(result)
-    # If we found any, find the oldest unwatched show for each one.
-    try:
-        items = result['result']['tvshows']
-    except (KeyError, TypeError):
-        pass
-    else:
-        allshowsIds = set()
-        for item in items:
-            allshowsIds.add(item['tvshowid'])
-
-        query = {
-
-            'jsonrpc': "2.0",
-            'id': 1,
-            'method': "VideoLibrary.GetEpisodes",
-            'params': {
-
-                'sort': {'order': "descending", 'method': "dateadded"},
-                'filter': {'operator': "lessthan", 'field': "playcount", 'value': "1"},
-                'properties': [
-                    "title", "playcount", "season", "episode", "showtitle", "plot",
-                    "file", "rating", "resume", "tvshowid", "art", "streamdetails",
-                    "firstaired", "runtime", "cast", "writer", "dateadded", "lastplayed"
-                ],
-                "limits": {"end": limit}
-            }
-        }
-        result = xbmc.executeJSONRPC(json.dumps(query))
-        result = json.loads(result)
-        try:
-            episodes = result['result']['episodes']
-        except (KeyError, TypeError):
-            pass
-        else:
-            for episode in episodes:
-                if episode['tvshowid'] in allshowsIds:
-                    li = createListItem(episode)
-                    xbmcplugin.addDirectoryItem(
-                                handle=int(sys.argv[1]),
-                                url=episode['file'],
-                                listitem=li)
-                    count += 1
-
-                if count == limit:
-                    break
-
-    xbmcplugin.endOfDirectory(handle=int(sys.argv[1]))
-
-##### GET VIDEO EXTRAS FOR LISTITEM #####
-def getVideoFiles(embyId,embyPath):
-    #returns the video files for the item as plugin listing, can be used for browsing the actual files or videoextras etc.
-    emby = embyserver.Read_EmbyServer()
-    if not embyId:
-        if "plugin.video.emby" in embyPath:
-            embyId = embyPath.split("/")[-2]
-    if embyId:
-        item = emby.getItem(embyId)
-        putils = playutils.PlayUtils(item)
-        if putils.isDirectPlay():
-            #only proceed if we can access the files directly. TODO: copy local on the fly if accessed outside
-            filelocation = putils.directPlay()
-            if not filelocation.endswith("/"):
-                filelocation = filelocation.rpartition("/")[0]
-            dirs, files = xbmcvfs.listdir(filelocation)
-            for file in files:
-                file = filelocation + file
-                li = xbmcgui.ListItem(file, path=file)
-                xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=file, listitem=li)
-            for dir in dirs:
-                dir = filelocation + dir
-                li = xbmcgui.ListItem(dir, path=dir)
-                xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=dir, listitem=li, isFolder=True)
-    xbmcplugin.endOfDirectory(int(sys.argv[1]))
-    
-##### GET EXTRAFANART FOR LISTITEM #####
-def getExtraFanArt(embyId,embyPath):
-    
-    emby = embyserver.Read_EmbyServer()
-    art = artwork.Artwork()
-    
-    # Get extrafanart for listitem 
-    # will be called by skinhelper script to get the extrafanart
-    try:
-        # for tvshows we get the embyid just from the path
-        if not embyId:
-            if "plugin.video.emby" in embyPath:
-                embyId = embyPath.split("/")[-2]
-        
-        if embyId:
-            #only proceed if we actually have a emby id
-            utils.logMsg("EMBY", "Requesting extrafanart for Id: %s" % embyId, 0)
-
-            # We need to store the images locally for this to work
-            # because of the caching system in xbmc
-            fanartDir = xbmc.translatePath("special://thumbnails/emby/%s/" % embyId).decode('utf-8')
-            
-            if not xbmcvfs.exists(fanartDir):
-                # Download the images to the cache directory
-                xbmcvfs.mkdirs(fanartDir)
-                item = emby.getItem(embyId)
-                if item:
-                    backdrops = art.getAllArtwork(item)['Backdrop']
-                    tags = item['BackdropImageTags']
-                    count = 0
-                    for backdrop in backdrops:
-                        # Same ordering as in artwork
-                        tag = tags[count]
-                        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]),
-                                            url=fanartFile,
-                                            listitem=li)
-                        xbmcvfs.copy(backdrop, fanartFile) 
-                        count += 1               
-            else:
-                utils.logMsg("EMBY", "Found cached backdrop.", 2)
-                # Use existing cached images
-                dirs, files = xbmcvfs.listdir(fanartDir)
-                for file in files:
-                    fanartFile = os.path.join(fanartDir, file.decode('utf-8'))
-                    li = xbmcgui.ListItem(file, path=fanartFile)
-                    xbmcplugin.addDirectoryItem(
-                                            handle=int(sys.argv[1]),
-                                            url=fanartFile,
-                                            listitem=li)
-    except Exception as e:
-        utils.logMsg("EMBY", "Error getting extrafanart: %s" % e, 0)
-    
-    # Always do endofdirectory to prevent errors in the logs
+# -*- coding: utf-8 -*-
+
+#################################################################################################
+
+import json
+import os
+import sys
+import urlparse
+
+import xbmc
+import xbmcaddon
+import xbmcgui
+import xbmcvfs
+import xbmcplugin
+
+import artwork
+import utils
+import clientinfo
+import downloadutils
+import librarysync
+import read_embyserver as embyserver
+import embydb_functions as embydb
+import playlist
+import playbackutils as pbutils
+import playutils
+import api
+
+
+#################################################################################################
+
+
+def doPlayback(itemid, dbid):
+
+    emby = embyserver.Read_EmbyServer()
+    item = emby.getItem(itemid)
+    pbutils.PlaybackUtils(item).play(itemid, dbid)
+
+##### DO RESET AUTH #####
+def resetAuth():
+    # User tried login and failed too many times
+    resp = xbmcgui.Dialog().yesno(
+                heading="Warning",
+                line1=(
+                    "Emby might lock your account if you fail to log in too many times. "
+                    "Proceed anyway?"))
+    if resp == 1:
+        utils.logMsg("EMBY", "Reset login attempts.", 1)
+        utils.window('emby_serverStatus', value="Auth")
+    else:
+        xbmc.executebuiltin('Addon.OpenSettings(plugin.video.emby)')
+
+def addDirectoryItem(label, path, folder=True):
+    li = xbmcgui.ListItem(label, path=path)
+    li.setThumbnailImage("special://home/addons/plugin.video.emby/icon.png")
+    li.setArt({"fanart":"special://home/addons/plugin.video.emby/fanart.jpg"})
+    li.setArt({"landscape":"special://home/addons/plugin.video.emby/fanart.jpg"})
+    xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=path, listitem=li, isFolder=folder)
+
+def doMainListing():
+    xbmcplugin.setContent(int(sys.argv[1]), 'files')    
+    # Get emby nodes from the window props
+    embyprops = utils.window('Emby.nodes.total')
+    if embyprops:
+        totalnodes = int(embyprops)
+        for i in range(totalnodes):
+            path = utils.window('Emby.nodes.%s.index' % i)
+            if not path:
+                path = utils.window('Emby.nodes.%s.content' % i)
+            label = utils.window('Emby.nodes.%s.title' % i)
+            node_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 library video nodes in the video library and all nodes in any other window
+            if path and xbmc.getCondVisibility("Window.IsActive(Pictures)") and node_type == "photos":
+                addDirectoryItem(label, path)
+            elif path and xbmc.getCondVisibility("Window.IsActive(VideoLibrary)") and node_type != "photos":
+                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")
+    addDirectoryItem("Add user to session", "plugin://plugin.video.emby/?mode=adduser")
+    addDirectoryItem("Refresh Emby playlists/nodes", "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]))
+
+
+##### Generate a new deviceId
+def resetDeviceId():
+
+    dialog = xbmcgui.Dialog()
+    language = utils.language
+
+    deviceId_old = utils.window('emby_deviceId')
+    try:
+        utils.window('emby_deviceId', clear=True)
+        deviceId = clientinfo.ClientInfo().getDeviceId(reset=True)
+    except Exception as e:
+        utils.logMsg("EMBY", "Failed to generate a new device Id: %s" % e, 1)
+        dialog.ok(
+            heading="Emby for Kodi",
+            line1=language(33032))
+    else:
+        utils.logMsg("EMBY", "Successfully removed old deviceId: %s New deviceId: %s"
+                    % (deviceId_old, deviceId), 1)
+        dialog.ok(
+            heading="Emby for Kodi",
+            line1=language(33033))
+        xbmc.executebuiltin('RestartApp')
+
+##### Delete Item
+def deleteItem():
+
+    # Serves as a keymap action
+    if xbmc.getInfoLabel('ListItem.Property(embyid)'): # If we already have the embyid
+        embyid = xbmc.getInfoLabel('ListItem.Property(embyid)')
+    else:
+        dbid = xbmc.getInfoLabel('ListItem.DBID')
+        itemtype = xbmc.getInfoLabel('ListItem.DBTYPE')
+
+        if not itemtype:
+
+            if xbmc.getCondVisibility('Container.Content(albums)'):
+                itemtype = "album"
+            elif xbmc.getCondVisibility('Container.Content(artists)'):
+                itemtype = "artist"
+            elif xbmc.getCondVisibility('Container.Content(songs)'):
+                itemtype = "song"
+            elif xbmc.getCondVisibility('Container.Content(pictures)'):
+                itemtype = "picture"
+            else:
+                utils.logMsg("EMBY delete", "Unknown type, unable to proceed.", 1)
+                return
+
+        embyconn = utils.kodiSQL('emby')
+        embycursor = embyconn.cursor()
+        emby_db = embydb.Embydb_Functions(embycursor)
+        item = emby_db.getItem_byKodiId(dbid, itemtype)
+        embycursor.close()
+
+        try:
+            embyid = item[0]
+        except TypeError:
+            utils.logMsg("EMBY delete", "Unknown embyId, unable to proceed.", 1)
+            return
+
+    if utils.settings('skipContextMenu') != "true":
+        resp = xbmcgui.Dialog().yesno(
+                                heading="Confirm delete",
+                                line1=("Delete file from Emby Server? This will "
+                                        "also delete the file(s) from disk!"))
+        if not resp:
+            utils.logMsg("EMBY delete", "User skipped deletion for: %s." % embyid, 1)
+            return
+    
+    doUtils = downloadutils.DownloadUtils()
+    url = "{server}/emby/Items/%s?format=json" % embyid
+    utils.logMsg("EMBY delete", "Deleting request: %s" % embyid, 0)
+    doUtils.downloadUrl(url, action_type="DELETE")
+
+##### ADD ADDITIONAL USERS #####
+def addUser():
+
+    doUtils = downloadutils.DownloadUtils()
+    art = artwork.Artwork()
+    clientInfo = clientinfo.ClientInfo()
+    deviceId = clientInfo.getDeviceId()
+    deviceName = clientInfo.getDeviceName()
+    userid = utils.window('emby_currUser')
+    dialog = xbmcgui.Dialog()
+
+    # Get session
+    url = "{server}/emby/Sessions?DeviceId=%s&format=json" % deviceId
+    result = doUtils.downloadUrl(url)
+    
+    try:
+        sessionId = result[0]['Id']
+        additionalUsers = result[0]['AdditionalUsers']
+        # Add user to session
+        userlist = {}
+        users = []
+        url = "{server}/emby/Users?IsDisabled=false&IsHidden=false&format=json"
+        result = doUtils.downloadUrl(url)
+
+        # pull the list of users
+        for user in result:
+            name = user['Name']
+            userId = user['Id']
+            if userid != userId:
+                userlist[name] = userId
+                users.append(name)
+
+        # Display dialog if there's additional users
+        if additionalUsers:
+
+            option = dialog.select("Add/Remove user from the session", ["Add user", "Remove user"])
+            # Users currently in the session
+            additionalUserlist = {}
+            additionalUsername = []
+            # Users currently in the session
+            for user in additionalUsers:
+                name = user['UserName']
+                userId = user['UserId']
+                additionalUserlist[name] = userId
+                additionalUsername.append(name)
+
+            if option == 1:
+                # User selected Remove user
+                resp = dialog.select("Remove user from the session", additionalUsername)
+                if resp > -1:
+                    selected = additionalUsername[resp]
+                    selected_userId = additionalUserlist[selected]
+                    url = "{server}/emby/Sessions/%s/Users/%s" % (sessionId, selected_userId)
+                    doUtils.downloadUrl(url, postBody={}, action_type="DELETE")
+                    dialog.notification(
+                            heading="Success!",
+                            message="%s removed from viewing session" % selected,
+                            icon="special://home/addons/plugin.video.emby/icon.png",
+                            time=1000)
+
+                    # clear picture
+                    position = utils.window('EmbyAdditionalUserPosition.%s' % selected_userId)
+                    utils.window('EmbyAdditionalUserImage.%s' % position, clear=True)
+                    return
+                else:
+                    return
+
+            elif option == 0:
+                # User selected Add user
+                for adduser in additionalUsername:
+                    try: # Remove from selected already added users. It is possible they are hidden.
+                        users.remove(adduser)
+                    except: pass
+
+            elif option < 0:
+                # User cancelled
+                return
+
+        # Subtract any additional users
+        utils.logMsg("EMBY", "Displaying list of users: %s" % users)
+        resp = dialog.select("Add user to the session", users)
+        # post additional user
+        if resp > -1:
+            selected = users[resp]
+            selected_userId = userlist[selected]
+            url = "{server}/emby/Sessions/%s/Users/%s" % (sessionId, selected_userId)
+            doUtils.downloadUrl(url, postBody={}, action_type="POST")
+            dialog.notification(
+                    heading="Success!",
+                    message="%s added to viewing session" % selected,
+                    icon="special://home/addons/plugin.video.emby/icon.png",
+                    time=1000)
+
+    except:
+        utils.logMsg("EMBY", "Failed to add user to session.")
+        dialog.notification(
+                heading="Error",
+                message="Unable to add/remove user from the session.",
+                icon=xbmcgui.NOTIFICATION_ERROR)
+
+    # Add additional user images
+    # always clear the individual items first
+    totalNodes = 10
+    for i in range(totalNodes):
+        if not utils.window('EmbyAdditionalUserImage.%s' % i):
+            break
+        utils.window('EmbyAdditionalUserImage.%s' % i, clear=True)
+
+    url = "{server}/emby/Sessions?DeviceId=%s" % deviceId
+    result = doUtils.downloadUrl(url)
+    additionalUsers = result[0]['AdditionalUsers']
+    count = 0
+    for additionaluser in additionalUsers:
+        userid = additionaluser['UserId']
+        url = "{server}/emby/Users/%s?format=json" % userid
+        result = doUtils.downloadUrl(url)
+        utils.window('EmbyAdditionalUserImage.%s' % count,
+            value=art.getUserArtwork(result['Id'], 'Primary'))
+        utils.window('EmbyAdditionalUserPosition.%s' % userid, value=str(count))
+        count +=1
+
+##### THEME MUSIC/VIDEOS #####
+def getThemeMedia():
+
+    doUtils = downloadutils.DownloadUtils()
+    dialog = xbmcgui.Dialog()
+    playback = None
+
+    # Choose playback method
+    resp = dialog.select("Playback method for your themes", ["Direct Play", "Direct Stream"])
+    if resp == 0:
+        playback = "DirectPlay"
+    elif resp == 1:
+        playback = "DirectStream"
+    else:
+        return
+
+    library = xbmc.translatePath(
+                "special://profile/addon_data/plugin.video.emby/library/").decode('utf-8')
+    # Create library directory
+    if not xbmcvfs.exists(library):
+        xbmcvfs.mkdir(library)
+
+    # Set custom path for user
+    tvtunes_path = xbmc.translatePath(
+        "special://profile/addon_data/script.tvtunes/").decode('utf-8')
+    if xbmcvfs.exists(tvtunes_path):
+        tvtunes = xbmcaddon.Addon(id="script.tvtunes")
+        tvtunes.setSetting('custom_path_enable', "true")
+        tvtunes.setSetting('custom_path', library)
+        utils.logMsg("EMBY", "TV Tunes custom path is enabled and set.", 1)
+    else:
+        # if it does not exist this will not work so warn user
+        # often they need to edit the settings first for it to be created.
+        dialog.ok(
+            heading="Warning",
+            line1=(
+                "The settings file does not exist in tvtunes. ",
+                "Go to the tvtunes addon and change a setting, then come back and re-run."))
+        xbmc.executebuiltin('Addon.OpenSettings(script.tvtunes)')
+        return
+        
+    # Get every user view Id
+    embyconn = utils.kodiSQL('emby')
+    embycursor = embyconn.cursor()
+    emby_db = embydb.Embydb_Functions(embycursor)
+    viewids = emby_db.getViews()
+    embycursor.close()
+
+    # Get Ids with Theme Videos
+    itemIds = {}
+    for view in viewids:
+        url = "{server}/emby/Users/{UserId}/Items?HasThemeVideo=True&ParentId=%s&format=json" % view
+        result = doUtils.downloadUrl(url)
+        if result['TotalRecordCount'] != 0:
+            for item in result['Items']:
+                itemId = item['Id']
+                folderName = item['Name']
+                folderName = utils.normalize_string(folderName.encode('utf-8'))
+                itemIds[itemId] = folderName
+
+    # Get paths for theme videos
+    for itemId in itemIds:
+        nfo_path = xbmc.translatePath(
+            "special://profile/addon_data/plugin.video.emby/library/%s/" % itemIds[itemId])
+        # Create folders for each content
+        if not xbmcvfs.exists(nfo_path):
+            xbmcvfs.mkdir(nfo_path)
+        # Where to put the nfos
+        nfo_path = "%s%s" % (nfo_path, "tvtunes.nfo")
+
+        url = "{server}/emby/Items/%s/ThemeVideos?format=json" % itemId
+        result = doUtils.downloadUrl(url)
+
+        # Create nfo and write themes to it
+        nfo_file = xbmcvfs.File(nfo_path, 'w')
+        pathstowrite = ""
+        # May be more than one theme
+        for theme in result['Items']:
+            putils = playutils.PlayUtils(theme)
+            if playback == "DirectPlay":
+                playurl = putils.directPlay()
+            else:
+                playurl = putils.directStream()
+            pathstowrite += ('<file>%s</file>' % playurl.encode('utf-8'))
+        
+        # Check if the item has theme songs and add them   
+        url = "{server}/emby/Items/%s/ThemeSongs?format=json" % itemId
+        result = doUtils.downloadUrl(url)
+
+        # May be more than one theme
+        for theme in result['Items']:
+            putils = playutils.PlayUtils(theme)  
+            if playback == "DirectPlay":
+                playurl = putils.directPlay()
+            else:
+                playurl = putils.directStream()
+            pathstowrite += ('<file>%s</file>' % playurl.encode('utf-8'))
+
+        nfo_file.write(
+            '<tvtunes>%s</tvtunes>' % pathstowrite
+        )
+        # Close nfo file
+        nfo_file.close()
+
+    # Get Ids with Theme songs
+    musicitemIds = {}
+    for view in viewids:
+        url = "{server}/emby/Users/{UserId}/Items?HasThemeSong=True&ParentId=%s&format=json" % view
+        result = doUtils.downloadUrl(url)
+        if result['TotalRecordCount'] != 0:
+            for item in result['Items']:
+                itemId = item['Id']
+                folderName = item['Name']
+                folderName = utils.normalize_string(folderName.encode('utf-8'))
+                musicitemIds[itemId] = folderName
+
+    # Get paths
+    for itemId in musicitemIds:
+        
+        # if the item was already processed with video themes back out
+        if itemId in itemIds:
+            continue
+        
+        nfo_path = xbmc.translatePath(
+            "special://profile/addon_data/plugin.video.emby/library/%s/" % musicitemIds[itemId])
+        # Create folders for each content
+        if not xbmcvfs.exists(nfo_path):
+            xbmcvfs.mkdir(nfo_path)
+        # Where to put the nfos
+        nfo_path = "%s%s" % (nfo_path, "tvtunes.nfo")
+        
+        url = "{server}/emby/Items/%s/ThemeSongs?format=json" % itemId
+        result = doUtils.downloadUrl(url)
+
+        # Create nfo and write themes to it
+        nfo_file = xbmcvfs.File(nfo_path, 'w')
+        pathstowrite = ""
+        # May be more than one theme
+        for theme in result['Items']: 
+            putils = playutils.PlayUtils(theme)
+            if playback == "DirectPlay":
+                playurl = putils.directPlay()
+            else:
+                playurl = putils.directStream()
+            pathstowrite += ('<file>%s</file>' % playurl.encode('utf-8'))
+
+        nfo_file.write(
+            '<tvtunes>%s</tvtunes>' % pathstowrite
+        )
+        # Close nfo file
+        nfo_file.close()
+
+    dialog.notification(
+            heading="Emby for Kodi",
+            message="Themes added!",
+            icon="special://home/addons/plugin.video.emby/icon.png",
+            time=1000,
+            sound=False)
+
+##### REFRESH EMBY PLAYLISTS #####
+def refreshPlaylist():
+
+    lib = librarysync.LibrarySync()
+    dialog = xbmcgui.Dialog()
+    try:
+        # First remove playlists
+        utils.deletePlaylists()
+        # Remove video nodes
+        utils.deleteNodes()
+        # Refresh views
+        lib.refreshViews()
+        dialog.notification(
+                heading="Emby for Kodi",
+                message="Emby playlists/nodes refreshed",
+                icon="special://home/addons/plugin.video.emby/icon.png",
+                time=1000,
+                sound=False)
+
+    except Exception as e:
+        utils.logMsg("EMBY", "Refresh playlists/nodes failed: %s" % e, 1)
+        dialog.notification(
+            heading="Emby for Kodi",
+            message="Emby playlists/nodes refresh failed",
+            icon=xbmcgui.NOTIFICATION_ERROR,
+            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))
+            addDirectoryItem(title, path)
+    xbmcplugin.endOfDirectory(int(sys.argv[1]))
+              
+##### BROWSE EMBY NODES DIRECTLY #####    
+def BrowseContent(viewname, browse_type="", folderid=""):
+    
+    emby = embyserver.Read_EmbyServer()
+    art = artwork.Artwork()
+    doUtils = downloadutils.DownloadUtils()
+    
+    #folderid used as filter ?
+    if folderid in ["recent","recentepisodes","inprogress","inprogressepisodes","unwatched","nextepisodes","sets","genres","random","recommended"]:
+        filter_type = folderid
+        folderid = ""
+    else:
+        filter_type = ""
+    
+    xbmcplugin.setPluginCategory(int(sys.argv[1]), viewname)
+    #get views for root level
+    if not folderid:
+        views = emby.getViews(browse_type)
+        for view in views:
+            if view.get("name") == viewname.decode('utf-8'):
+                folderid = view.get("id")
+                break
+    
+    if viewname is not None:
+        utils.logMsg("BrowseContent","viewname: %s - type: %s - folderid: %s - filter: %s" %(viewname.decode('utf-8'), browse_type.decode('utf-8'), folderid.decode('utf-8'), filter_type.decode('utf-8')))
+    #set the correct params for the content type
+    #only proceed if we have a folderid
+    if folderid:
+        if browse_type.lower() == "homevideos":
+            xbmcplugin.setContent(int(sys.argv[1]), 'episodes')
+            itemtype = "Video,Folder,PhotoAlbum"
+        elif browse_type.lower() == "photos":
+            xbmcplugin.setContent(int(sys.argv[1]), 'files')
+            itemtype = "Photo,PhotoAlbum,Folder"
+        else:
+            itemtype = ""
+        
+        #get the actual listing
+        if browse_type == "recordings":
+            listing = emby.getTvRecordings(folderid)
+        elif browse_type == "tvchannels":
+            listing = emby.getTvChannels()
+        elif filter_type == "recent":
+            listing = emby.getFilteredSection(folderid, itemtype=itemtype.split(",")[0], sortby="DateCreated", recursive=True, limit=25, sortorder="Descending")
+        elif filter_type == "random":
+            listing = emby.getFilteredSection(folderid, itemtype=itemtype.split(",")[0], sortby="Random", recursive=True, limit=150, sortorder="Descending")
+        elif filter_type == "recommended":
+            listing = emby.getFilteredSection(folderid, itemtype=itemtype.split(",")[0], sortby="SortName", recursive=True, limit=25, sortorder="Ascending", filter_type="IsFavorite")
+        elif filter_type == "sets":
+            listing = emby.getFilteredSection(folderid, itemtype=itemtype.split(",")[1], sortby="SortName", recursive=True, limit=25, sortorder="Ascending", filter_type="IsFavorite")
+        else:
+            listing = emby.getFilteredSection(folderid, itemtype=itemtype, recursive=False)
+        
+        #process the listing
+        if listing:
+            for item in listing.get("Items"):
+                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].decode('utf-8'), viewname.decode('utf-8'), browse_type.decode('utf-8'), item.get("Id").decode('utf-8'))
+                    xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=path, listitem=li, isFolder=True)
+                else:
+                    #playable item, set plugin path and mediastreams
+                    xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=li.getProperty("path"), listitem=li)
+
+
+    if filter_type == "recent":
+        xbmcplugin.addSortMethod(int(sys.argv[1]), xbmcplugin.SORT_METHOD_DATE)
+    else:
+        xbmcplugin.addSortMethod(int(sys.argv[1]), xbmcplugin.SORT_METHOD_VIDEO_TITLE)
+        xbmcplugin.addSortMethod(int(sys.argv[1]), xbmcplugin.SORT_METHOD_DATE)
+        xbmcplugin.addSortMethod(int(sys.argv[1]), xbmcplugin.SORT_METHOD_VIDEO_RATING)
+        xbmcplugin.addSortMethod(int(sys.argv[1]), xbmcplugin.SORT_METHOD_VIDEO_RUNTIME)
+
+    xbmcplugin.endOfDirectory(handle=int(sys.argv[1]))
+
+##### CREATE LISTITEM FROM EMBY METADATA #####
+def createListItemFromEmbyItem(item,art=artwork.Artwork(),doUtils=downloadutils.DownloadUtils()):
+    API = api.API(item)
+    itemid = item['Id']
+    
+    title = item.get('Name')
+    li = xbmcgui.ListItem(title)
+    
+    premieredate = item.get('PremiereDate',"")
+    if not premieredate: premieredate = item.get('DateCreated',"")
+    if premieredate:
+        premieredatelst = premieredate.split('T')[0].split("-")
+        premieredate = "%s.%s.%s" %(premieredatelst[2],premieredatelst[1],premieredatelst[0])
+
+    li.setProperty("embyid",itemid)
+    
+    allart = art.getAllArtwork(item)
+    
+    if item["Type"] == "Photo":
+        #listitem setup for pictures...
+        img_path = allart.get('Primary')
+        li.setProperty("path",img_path)
+        picture = doUtils.downloadUrl("{server}/Items/%s/Images" %itemid)
+        if picture:
+            picture = picture[0]
+            if picture.get("Width") > picture.get("Height"):
+                li.setArt( {"fanart":  img_path}) #add image as fanart for use with skinhelper auto thumb/backgrund creation
+            li.setInfo('pictures', infoLabels={ "picturepath": img_path, "date": premieredate, "size": picture.get("Size"), "exif:width": str(picture.get("Width")), "exif:height": str(picture.get("Height")), "title": title})
+        li.setThumbnailImage(img_path)
+        li.setProperty("plot",API.getOverview())
+        li.setIconImage('DefaultPicture.png')
+    else:
+        #normal video items
+        li.setProperty('IsPlayable', 'true')
+        path = "%s?id=%s&mode=play" % (sys.argv[0], item.get("Id"))
+        li.setProperty("path",path)
+        genre = API.getGenres()
+        overlay = 0
+        userdata = API.getUserData()
+        runtime = item.get("RunTimeTicks",0)/ 10000000.0
+        seektime = userdata['Resume']
+        if seektime:
+            li.setProperty("resumetime", str(seektime))
+            li.setProperty("totaltime", str(runtime))
+        
+        played = userdata['Played']
+        if played: overlay = 7
+        else: overlay = 6       
+        playcount = userdata['PlayCount']
+        if playcount is None:
+            playcount = 0
+            
+        rating = item.get('CommunityRating')
+        if not rating: rating = userdata['UserRating']
+
+        # Populate the extradata list and artwork
+        extradata = {
+            'id': itemid,
+            'rating': rating,
+            'year': item.get('ProductionYear'),
+            '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)
+        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
+    
+##### BROWSE EMBY CHANNELS #####    
+def BrowseChannels(itemid, folderid=None):
+    
+    _addon_id   =   int(sys.argv[1])
+    _addon_url  =   sys.argv[0]
+    doUtils = downloadutils.DownloadUtils()
+    art = artwork.Artwork()
+
+    xbmcplugin.setContent(int(sys.argv[1]), 'files')
+    if folderid:
+        url = (
+                "{server}/emby/Channels/%s/Items?userid={UserId}&folderid=%s&format=json"
+                % (itemid, folderid))
+    elif itemid == "0":
+        # id 0 is the root channels folder
+        url = "{server}/emby/Channels?{UserId}&format=json"
+    else:
+        url = "{server}/emby/Channels/%s/Items?UserId={UserId}&format=json" % itemid
+
+    result = doUtils.downloadUrl(url)
+    if result and result.get("Items"):
+        for item in result.get("Items"):
+            itemid = item['Id']
+            itemtype = item['Type']
+            li = createListItemFromEmbyItem(item,art,doUtils)
+            
+            isFolder = item.get('IsFolder', False)
+
+            channelId = item.get('ChannelId', "")
+            channelName = item.get('ChannelName', "")
+            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)
+            else:
+                path = "%s?id=%s&mode=play" % (_addon_url, itemid)
+                li.setProperty('IsPlayable', 'true')
+                xbmcplugin.addDirectoryItem(handle=_addon_id, url=path, listitem=li)
+
+    xbmcplugin.endOfDirectory(handle=int(sys.argv[1]))
+
+##### LISTITEM SETUP FOR VIDEONODES #####
+def createListItem(item):
+
+    title = item['title']
+    li = xbmcgui.ListItem(title)
+    li.setProperty('IsPlayable', "true")
+    
+    metadata = {
+
+        'Title': title,
+        'duration': str(item['runtime']/60),
+        'Plot': item['plot'],
+        'Playcount': item['playcount']
+    }
+
+    if "episode" in item:
+        episode = item['episode']
+        metadata['Episode'] = episode
+
+    if "season" in item:
+        season = item['season']
+        metadata['Season'] = season
+
+    if season and episode:
+        li.setProperty('episodeno', "s%.2de%.2d" % (season, episode))
+
+    if "firstaired" in item:
+        metadata['Premiered'] = item['firstaired']
+
+    if "showtitle" in item:
+        metadata['TVshowTitle'] = item['showtitle']
+
+    if "rating" in item:
+        metadata['Rating'] = str(round(float(item['rating']),1))
+
+    if "director" in item:
+        metadata['Director'] = " / ".join(item['director'])
+
+    if "writer" in item:
+        metadata['Writer'] = " / ".join(item['writer'])
+
+    if "cast" in item:
+        cast = []
+        castandrole = []
+        for person in item['cast']:
+            name = person['name']
+            cast.append(name)
+            castandrole.append((name, person['role']))
+        metadata['Cast'] = cast
+        metadata['CastAndRole'] = castandrole
+
+    li.setInfo(type="Video", infoLabels=metadata)  
+    li.setProperty('resumetime', str(item['resume']['position']))
+    li.setProperty('totaltime', str(item['resume']['total']))
+    li.setArt(item['art'])
+    li.setThumbnailImage(item['art'].get('thumb',''))
+    li.setIconImage('DefaultTVShows.png')
+    li.setProperty('dbid', str(item['episodeid']))
+    li.setProperty('fanart_image', item['art'].get('tvshow.fanart',''))
+    for key, value in item['streamdetails'].iteritems():
+        for stream in value:
+            li.addStreamInfo(key, stream)
+    
+    return li
+
+##### GET NEXTUP EPISODES FOR TAGNAME #####    
+def getNextUpEpisodes(tagname, limit):
+    
+    count = 0
+    # if the addon is called with nextup parameter,
+    # we return the nextepisodes list of the given tagname
+    xbmcplugin.setContent(int(sys.argv[1]), 'episodes')
+    # First we get a list of all the TV shows - filtered by tag
+    query = {
+
+        'jsonrpc': "2.0",
+        'id': "libTvShows",
+        'method': "VideoLibrary.GetTVShows",
+        'params': {
+
+            'sort': {'order': "descending", 'method': "lastplayed"},
+            'filter': {
+                'and': [
+                    {'operator': "true", 'field': "inprogress", 'value': ""},
+                    {'operator': "is", 'field': "tag", 'value': "%s" % tagname}
+                ]},
+            'properties': ['title', 'studio', 'mpaa', 'file', 'art']
+        }
+    }
+    result = xbmc.executeJSONRPC(json.dumps(query))
+    result = json.loads(result)
+    # If we found any, find the oldest unwatched show for each one.
+    try:
+        items = result['result']['tvshows']
+    except (KeyError, TypeError):
+        pass
+    else:
+        for item in items:
+            if utils.settings('ignoreSpecialsNextEpisodes') == "true":
+                query = {
+
+                    'jsonrpc': "2.0",
+                    'id': 1,
+                    'method': "VideoLibrary.GetEpisodes",
+                    'params': {
+
+                        'tvshowid': item['tvshowid'],
+                        'sort': {'method': "episode"},
+                        'filter': {
+                            'and': [
+                                {'operator': "lessthan", 'field': "playcount", 'value': "1"},
+                                {'operator': "greaterthan", 'field': "season", 'value': "0"}
+                        ]},
+                        'properties': [
+                            "title", "playcount", "season", "episode", "showtitle",
+                            "plot", "file", "rating", "resume", "tvshowid", "art",
+                            "streamdetails", "firstaired", "runtime", "writer",
+                            "dateadded", "lastplayed"
+                        ],
+                        'limits': {"end": 1}
+                    }
+                }
+            else:
+                query = {
+
+                    'jsonrpc': "2.0",
+                    'id': 1,
+                    'method': "VideoLibrary.GetEpisodes",
+                    'params': {
+
+                        'tvshowid': item['tvshowid'],
+                        'sort': {'method': "episode"},
+                        'filter': {'operator': "lessthan", 'field': "playcount", 'value': "1"},
+                        'properties': [
+                            "title", "playcount", "season", "episode", "showtitle",
+                            "plot", "file", "rating", "resume", "tvshowid", "art",
+                            "streamdetails", "firstaired", "runtime", "writer",
+                            "dateadded", "lastplayed"
+                        ],
+                        'limits': {"end": 1}
+                    }
+                }
+
+            result = xbmc.executeJSONRPC(json.dumps(query))
+            result = json.loads(result)
+            try:
+                episodes = result['result']['episodes']
+            except (KeyError, TypeError):
+                pass
+            else:
+                for episode in episodes:
+                    li = createListItem(episode)
+                    xbmcplugin.addDirectoryItem(
+                                handle=int(sys.argv[1]),
+                                url=episode['file'],
+                                listitem=li)
+                    count += 1
+
+            if count == limit:
+                break
+
+    xbmcplugin.endOfDirectory(handle=int(sys.argv[1]))
+
+##### GET INPROGRESS EPISODES FOR TAGNAME #####    
+def getInProgressEpisodes(tagname, limit):
+    
+    count = 0
+    # if the addon is called with inprogressepisodes parameter,
+    # we return the inprogressepisodes list of the given tagname
+    xbmcplugin.setContent(int(sys.argv[1]), 'episodes')
+    # First we get a list of all the in-progress TV shows - filtered by tag
+    query = {
+
+        'jsonrpc': "2.0",
+        'id': "libTvShows",
+        'method': "VideoLibrary.GetTVShows",
+        'params': {
+
+            'sort': {'order': "descending", 'method': "lastplayed"},
+            'filter': {
+                'and': [
+                    {'operator': "true", 'field': "inprogress", 'value': ""},
+                    {'operator': "is", 'field': "tag", 'value': "%s" % tagname}
+                ]},
+            'properties': ['title', 'studio', 'mpaa', 'file', 'art']
+        }
+    }
+    result = xbmc.executeJSONRPC(json.dumps(query))
+    result = json.loads(result)
+    # If we found any, find the oldest unwatched show for each one.
+    try:
+        items = result['result']['tvshows']
+    except (KeyError, TypeError):
+        pass
+    else:
+        for item in items:
+            query = {
+
+                'jsonrpc': "2.0",
+                'id': 1,
+                'method': "VideoLibrary.GetEpisodes",
+                'params': {
+
+                    'tvshowid': item['tvshowid'],
+                    'sort': {'method': "episode"},
+                    'filter': {'operator': "true", 'field': "inprogress", 'value': ""},
+                    'properties': [
+                        "title", "playcount", "season", "episode", "showtitle", "plot",
+                        "file", "rating", "resume", "tvshowid", "art", "cast",
+                        "streamdetails", "firstaired", "runtime", "writer",
+                        "dateadded", "lastplayed"
+                    ]
+                }
+            }
+            result = xbmc.executeJSONRPC(json.dumps(query))
+            result = json.loads(result)
+            try:
+                episodes = result['result']['episodes']
+            except (KeyError, TypeError):
+                pass
+            else:
+                for episode in episodes:
+                    li = createListItem(episode)
+                    xbmcplugin.addDirectoryItem(
+                                handle=int(sys.argv[1]),
+                                url=episode['file'],
+                                listitem=li)
+                    count += 1
+
+            if count == limit:
+                break
+
+    xbmcplugin.endOfDirectory(handle=int(sys.argv[1]))
+
+##### GET RECENT EPISODES FOR TAGNAME #####    
+def getRecentEpisodes(tagname, limit):
+    
+    count = 0
+    # if the addon is called with recentepisodes parameter,
+    # we return the recentepisodes list of the given tagname
+    xbmcplugin.setContent(int(sys.argv[1]), 'episodes')
+    # First we get a list of all the TV shows - filtered by tag
+    query = {
+
+        'jsonrpc': "2.0",
+        'id': "libTvShows",
+        'method': "VideoLibrary.GetTVShows",
+        'params': {
+
+            'sort': {'order': "descending", 'method': "dateadded"},
+            'filter': {'operator': "is", 'field': "tag", 'value': "%s" % tagname},
+            'properties': ["title","sorttitle"]
+        }
+    }
+    result = xbmc.executeJSONRPC(json.dumps(query))
+    result = json.loads(result)
+    # If we found any, find the oldest unwatched show for each one.
+    try:
+        items = result['result']['tvshows']
+    except (KeyError, TypeError):
+        pass
+    else:
+        allshowsIds = set()
+        for item in items:
+            allshowsIds.add(item['tvshowid'])
+
+        query = {
+
+            'jsonrpc': "2.0",
+            'id': 1,
+            'method': "VideoLibrary.GetEpisodes",
+            'params': {
+
+                'sort': {'order': "descending", 'method': "dateadded"},
+                'filter': {'operator': "lessthan", 'field': "playcount", 'value': "1"},
+                'properties': [
+                    "title", "playcount", "season", "episode", "showtitle", "plot",
+                    "file", "rating", "resume", "tvshowid", "art", "streamdetails",
+                    "firstaired", "runtime", "cast", "writer", "dateadded", "lastplayed"
+                ],
+                "limits": {"end": limit}
+            }
+        }
+        result = xbmc.executeJSONRPC(json.dumps(query))
+        result = json.loads(result)
+        try:
+            episodes = result['result']['episodes']
+        except (KeyError, TypeError):
+            pass
+        else:
+            for episode in episodes:
+                if episode['tvshowid'] in allshowsIds:
+                    li = createListItem(episode)
+                    xbmcplugin.addDirectoryItem(
+                                handle=int(sys.argv[1]),
+                                url=episode['file'],
+                                listitem=li)
+                    count += 1
+
+                if count == limit:
+                    break
+
+    xbmcplugin.endOfDirectory(handle=int(sys.argv[1]))
+
+##### GET VIDEO EXTRAS FOR LISTITEM #####
+def getVideoFiles(embyId,embyPath):
+    #returns the video files for the item as plugin listing, can be used for browsing the actual files or videoextras etc.
+    emby = embyserver.Read_EmbyServer()
+    if not embyId:
+        if "plugin.video.emby" in embyPath:
+            embyId = embyPath.split("/")[-2]
+    if embyId:
+        item = emby.getItem(embyId)
+        putils = playutils.PlayUtils(item)
+        if putils.isDirectPlay():
+            #only proceed if we can access the files directly. TODO: copy local on the fly if accessed outside
+            filelocation = putils.directPlay()
+            if not filelocation.endswith("/"):
+                filelocation = filelocation.rpartition("/")[0]
+            dirs, files = xbmcvfs.listdir(filelocation)
+            for file in files:
+                file = filelocation + file
+                li = xbmcgui.ListItem(file, path=file)
+                xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=file, listitem=li)
+            for dir in dirs:
+                dir = filelocation + dir
+                li = xbmcgui.ListItem(dir, path=dir)
+                xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=dir, listitem=li, isFolder=True)
+    xbmcplugin.endOfDirectory(int(sys.argv[1]))
+    
+##### GET EXTRAFANART FOR LISTITEM #####
+def getExtraFanArt(embyId,embyPath):
+    
+    emby = embyserver.Read_EmbyServer()
+    art = artwork.Artwork()
+    
+    # Get extrafanart for listitem 
+    # will be called by skinhelper script to get the extrafanart
+    try:
+        # for tvshows we get the embyid just from the path
+        if not embyId:
+            if "plugin.video.emby" in embyPath:
+                embyId = embyPath.split("/")[-2]
+        
+        if embyId:
+            #only proceed if we actually have a emby id
+            utils.logMsg("EMBY", "Requesting extrafanart for Id: %s" % embyId, 0)
+
+            # We need to store the images locally for this to work
+            # because of the caching system in xbmc
+            fanartDir = xbmc.translatePath("special://thumbnails/emby/%s/" % embyId).decode('utf-8')
+            
+            if not xbmcvfs.exists(fanartDir):
+                # Download the images to the cache directory
+                xbmcvfs.mkdirs(fanartDir)
+                item = emby.getItem(embyId)
+                if item:
+                    backdrops = art.getAllArtwork(item)['Backdrop']
+                    tags = item['BackdropImageTags']
+                    count = 0
+                    for backdrop in backdrops:
+                        # Same ordering as in artwork
+                        tag = tags[count]
+                        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]),
+                                            url=fanartFile,
+                                            listitem=li)
+                        xbmcvfs.copy(backdrop, fanartFile) 
+                        count += 1               
+            else:
+                utils.logMsg("EMBY", "Found cached backdrop.", 2)
+                # Use existing cached images
+                dirs, files = xbmcvfs.listdir(fanartDir)
+                for file in files:
+                    fanartFile = os.path.join(fanartDir, file.decode('utf-8'))
+                    li = xbmcgui.ListItem(file, path=fanartFile)
+                    xbmcplugin.addDirectoryItem(
+                                            handle=int(sys.argv[1]),
+                                            url=fanartFile,
+                                            listitem=li)
+    except Exception as e:
+        utils.logMsg("EMBY", "Error getting extrafanart: %s" % e, 0)
+    
+    # Always do endofdirectory to prevent errors in the logs
     xbmcplugin.endOfDirectory(int(sys.argv[1]))
\ No newline at end of file
diff --git a/resources/lib/initialsetup.py b/resources/lib/initialsetup.py
index e23c9001..7bf0dbb9 100644
--- a/resources/lib/initialsetup.py
+++ b/resources/lib/initialsetup.py
@@ -176,8 +176,8 @@ class InitialSetup():
         sock.setsockopt(socket.SOL_IP, socket.IP_MULTICAST_LOOP, 1)
         sock.setsockopt(socket.IPPROTO_IP, socket.SO_REUSEADDR, 1)
         
-        self.logMsg("MultiGroup      : %s" % str(MULTI_GROUP), 2);
-        self.logMsg("Sending UDP Data: %s" % MESSAGE, 2);
+        self.logMsg("MultiGroup      : %s" % str(MULTI_GROUP), 2)
+        self.logMsg("Sending UDP Data: %s" % MESSAGE, 2)
         sock.sendto(MESSAGE, MULTI_GROUP)
     
         try:
diff --git a/resources/lib/itemtypes.py b/resources/lib/itemtypes.py
index f3f03ca6..18c90517 100644
--- a/resources/lib/itemtypes.py
+++ b/resources/lib/itemtypes.py
@@ -1,2470 +1,2433 @@
-# -*- coding: utf-8 -*-
-
-##################################################################################################
-
-import urllib
-from ntpath import dirname
-from datetime import datetime
-
-import xbmc
-import xbmcgui
-import xbmcvfs
-
-import api
-import artwork
-import clientinfo
-import downloadutils
-import utils
-import embydb_functions as embydb
-import kodidb_functions as kodidb
-import read_embyserver as embyserver
-import musicutils
-
-##################################################################################################
-
-
-class Items(object):
-
-
-    def __init__(self, embycursor, kodicursor):
-
-        self.embycursor = embycursor
-        self.kodicursor = kodicursor
-
-        self.clientInfo = clientinfo.ClientInfo()
-        self.addonName = self.clientInfo.getAddonName()
-        self.doUtils = downloadutils.DownloadUtils()
-
-        self.kodiversion = int(xbmc.getInfoLabel("System.BuildVersion")[:2])
-        self.directpath = utils.settings('useDirectPaths') == "1"
-        self.music_enabled = utils.settings('enableMusic') == "true"
-        self.contentmsg = utils.settings('newContent') == "true"
-        self.newvideo_time = int(utils.settings('newvideotime'))*1000
-        self.newmusic_time = int(utils.settings('newmusictime'))*1000
-
-        self.artwork = artwork.Artwork()
-        self.emby = embyserver.Read_EmbyServer()
-        self.emby_db = embydb.Embydb_Functions(embycursor)
-        self.kodi_db = kodidb.Kodidb_Functions(kodicursor)
-
-    def logMsg(self, msg, lvl=1):
-
-        className = self.__class__.__name__
-        utils.logMsg("%s %s" % (self.addonName, className), msg, lvl)
-
-
-    def itemsbyId(self, items, process, pdialog=None):
-        # Process items by itemid. Process can be added, update, userdata, remove
-        emby = self.emby
-        embycursor = self.embycursor
-        kodicursor = self.kodicursor
-        music_enabled = self.music_enabled
-        
-        itemtypes = {
-
-            'Movie': Movies,
-            'BoxSet': Movies,
-            'Series': TVShows,
-            'Season': TVShows,
-            'Episode': TVShows,
-            'MusicAlbum': Music,
-            'MusicArtist': Music,
-            'AlbumArtist': Music,
-            'Audio': Music
-        }
-
-        update_videolibrary = False
-        total = 0
-        for item in items:
-            total += len(items[item])
-
-        if total == 0:
-            return False
-
-        self.logMsg("Processing %s: %s" % (process, items), 1)
-        if pdialog:
-            pdialog.update(heading="Processing %s: %s items" % (process, total))
-
-        count = 0
-        for itemtype in items:
-
-            # Safety check
-            if not itemtypes.get(itemtype):
-                # We don't process this type of item
-                continue
-
-            itemlist = items[itemtype]
-            if not itemlist:
-                # The list to process is empty
-                continue
-
-            musicconn = None
-
-            if itemtype in ('MusicAlbum', 'MusicArtist', 'AlbumArtist', 'Audio'):
-                if music_enabled:
-                    musicconn = utils.kodiSQL('music')
-                    musiccursor = musicconn.cursor()
-                    items_process = itemtypes[itemtype](embycursor, musiccursor)
-                else:
-                    # Music is not enabled, do not proceed with itemtype
-                    continue
-            else:
-                update_videolibrary = True
-                items_process = itemtypes[itemtype](embycursor, kodicursor)
-
-            if itemtype == "Movie":
-                actions = {
-                    'added': items_process.added,
-                    'update': items_process.add_update,
-                    'userdata': items_process.updateUserdata,
-                    'remove': items_process.remove
-                }
-            elif itemtype == "BoxSet":
-                actions = {
-                    'added': items_process.added_boxset,
-                    'update': items_process.add_updateBoxset,
-                    'remove': items_process.remove
-                }
-            elif itemtype == "MusicVideo":
-                actions = {
-                    'added': items_process.added,
-                    'update': items_process.add_update,
-                    'userdata': items_process.updateUserdata,
-                    'remove': items_process.remove
-                }
-            elif itemtype == "Series":
-                actions = {
-                    'added': items_process.added,
-                    'update': items_process.add_update,
-                    'userdata': items_process.updateUserdata,
-                    'remove': items_process.remove
-                }
-            elif itemtype == "Season":
-                actions = {
-                    'added': items_process.added_season,
-                    'update': items_process.add_updateSeason,
-                    'remove': items_process.remove
-                }
-            elif itemtype == "Episode":
-                actions = {
-                    'added': items_process.added_episode,
-                    'update': items_process.add_updateEpisode,
-                    'userdata': items_process.updateUserdata,
-                    'remove': items_process.remove
-                }
-            elif itemtype == "MusicAlbum":
-                actions = {
-                    'added': items_process.added_album,
-                    'update': items_process.add_updateAlbum,
-                    'userdata': items_process.updateUserdata,
-                    'remove': items_process.remove
-                }
-            elif itemtype in ("MusicArtist", "AlbumArtist"):
-                actions = {
-                    'added': items_process.added,
-                    'update': items_process.add_updateArtist,
-                    'remove': items_process.remove
-                }
-            elif itemtype == "Audio":
-                actions = {
-                    'added': items_process.added_song,
-                    'update': items_process.add_updateSong,
-                    'userdata': items_process.updateUserdata,
-                    'remove': items_process.remove
-                }
-            else:
-                self.logMsg("Unsupported itemtype: %s." % itemtype, 1)
-                actions = {}
-
-            if actions.get(process):
-
-                if process == "remove":
-                    for item in itemlist:
-                        actions[process](item)
-
-                elif process == "added":
-                    actions[process](itemlist, pdialog)
-            
-                else:
-                    processItems = emby.getFullItems(itemlist)
-                    for item in processItems:
-
-                        title = item['Name']
-
-                        if itemtype == "Episode":
-                            title = "%s - %s" % (item['SeriesName'], title)
-
-                        if pdialog:
-                            percentage = int((float(count) / float(total))*100)
-                            pdialog.update(percentage, message=title)
-                            count += 1
-
-                        actions[process](item)
-
-
-            if musicconn is not None:
-                # close connection for special types
-                self.logMsg("Updating music database.", 1)
-                musicconn.commit()
-                musiccursor.close()
-
-        return (True, update_videolibrary)
-
-    def contentPop(self, name, time=5000):
-        
-        if time: 
-            # It's possible for the time to be 0. It should be considered disabled in this case.
-            xbmcgui.Dialog().notification(
-                    heading="Emby for Kodi",
-                    message="Added: %s" % name,
-                    icon="special://home/addons/plugin.video.emby/icon.png",
-                    time=time,
-                    sound=False)
-
-
-class Movies(Items):
-
-    
-    def __init__(self, embycursor, kodicursor):
-        Items.__init__(self, embycursor, kodicursor)
-
-    def added(self, items, pdialog):
-
-        total = len(items)
-        count = 0
-        for movie in items:
-                
-            title = movie['Name']
-            if pdialog:
-                percentage = int((float(count) / float(total))*100)
-                pdialog.update(percentage, message=title)
-                count += 1
-            self.add_update(movie)
-            if not pdialog and self.contentmsg:
-                self.contentPop(title, self.newvideo_time)
-
-    def added_boxset(self, items, pdialog):
-
-        total = len(items)
-        count = 0
-        for boxset in items:
-
-            title = boxset['Name']
-            if pdialog:
-                percentage = int((float(count) / float(total))*100)
-                pdialog.update(percentage, message=title)
-                count += 1
-            self.add_updateBoxset(boxset)
-
-
-    def add_update(self, item, viewtag=None, viewid=None):
-        # Process single movie
-        kodicursor = self.kodicursor
-        emby_db = self.emby_db
-        kodi_db = self.kodi_db
-        artwork = self.artwork
-        API = api.API(item)
-
-        # If the item already exist in the local Kodi DB we'll perform a full item update
-        # If the item doesn't exist, we'll add it to the database
-        update_item = True
-        itemid = item['Id']
-        emby_dbitem = emby_db.getItem_byId(itemid)
-        try:
-            movieid = emby_dbitem[0]
-            fileid = emby_dbitem[1]
-            pathid = emby_dbitem[2]
-            self.logMsg("movieid: %s fileid: %s pathid: %s" % (movieid, fileid, pathid), 1)
-        
-        except TypeError:
-            update_item = False
-            self.logMsg("movieid: %s not found." % itemid, 2)
-            # movieid
-            kodicursor.execute("select coalesce(max(idMovie),0) from movie")
-            movieid = kodicursor.fetchone()[0] + 1
-
-        else:
-            # Verification the item is still in Kodi
-            query = "SELECT * FROM movie WHERE idMovie = ?"
-            kodicursor.execute(query, (movieid,))
-            try:
-                kodicursor.fetchone()[0]
-            except TypeError:
-                # item is not found, let's recreate it.
-                update_item = False
-                self.logMsg("movieid: %s missing from Kodi, repairing the entry." % movieid, 1)
-
-        if not viewtag or not viewid:
-            # Get view tag from emby
-            viewtag, viewid, mediatype = self.emby.getView_embyId(itemid)
-            self.logMsg("View tag found: %s" % viewtag, 2)
-
-        # fileId information
-        checksum = API.getChecksum()
-        dateadded = API.getDateCreated()
-        userdata = API.getUserData()
-        playcount = userdata['PlayCount']
-        dateplayed = userdata['LastPlayedDate']
-
-        # item details
-        people = API.getPeople()
-        writer = " / ".join(people['Writer'])
-        director = " / ".join(people['Director'])
-        genres = item['Genres']
-        title = item['Name']
-        plot = API.getOverview()
-        shortplot = item.get('ShortOverview')
-        tagline = API.getTagline()
-        votecount = item.get('VoteCount')
-        rating = item.get('CommunityRating')
-        year = item.get('ProductionYear')
-        imdb = API.getProvider('Imdb')
-        sorttitle = item['SortName']
-        runtime = API.getRuntime()
-        mpaa = API.getMpaa()
-        genre = " / ".join(genres)
-        country = API.getCountry()
-        studios = API.getStudios()
-        try:
-            studio = studios[0]
-        except IndexError:
-            studio = None
-
-        if item.get('LocalTrailerCount'):
-            # There's a local trailer
-            url = (
-                "{server}/emby/Users/{UserId}/Items/%s/LocalTrailers?format=json"
-                % itemid
-            )
-            result = self.doUtils.downloadUrl(url)
-            try:
-                trailer = "plugin://plugin.video.emby/trailer/?id=%s&mode=play" % result[0]['Id']
-            except IndexError:
-                self.logMsg("Failed to process local trailer.", 1)
-                trailer = None
-        else:
-            # Try to get the youtube trailer
-            try:
-                trailer = item['RemoteTrailers'][0]['Url']
-            except (KeyError, IndexError):
-                trailer = None
-            else:    
-                try:
-                    trailerId = trailer.rsplit('=', 1)[1]
-                except IndexError:
-                    self.logMsg("Failed to process trailer: %s" % trailer, 1)
-                    trailer = None
-                else:
-                    trailer = "plugin://plugin.video.youtube/play/?video_id=%s" % trailerId
-
-        
-        ##### GET THE FILE AND PATH #####
-        playurl = API.getFilePath()
-
-        if "\\" in playurl:
-            # Local path
-            filename = playurl.rsplit("\\", 1)[1]
-        else: # Network share
-            filename = playurl.rsplit("/", 1)[1]
-
-        if self.directpath:
-            # Direct paths is set the Kodi way
-            if utils.window('emby_pathverified') != "true" and not xbmcvfs.exists(playurl):
-                # Validate the path is correct with user intervention
-                resp = xbmcgui.Dialog().yesno(
-                                        heading="Can't validate path",
-                                        line1=(
-                                            "Kodi can't locate file: %s. Verify the path. "
-                                            "You may to verify your network credentials in the "
-                                            "add-on settings or use the emby path substitution "
-                                            "to format your path correctly. Stop syncing?"
-                                            % playurl))
-                if resp:
-                    utils.window('emby_shouldStop', value="true")
-                    return False
-            
-            path = playurl.replace(filename, "")
-            utils.window('emby_pathverified', value="true")
-        else:
-            # Set plugin path and media flags using real filename
-            path = "plugin://plugin.video.emby.movies/"
-            params = {
-
-                'filename': filename.encode('utf-8'),
-                'id': itemid,
-                'dbid': movieid,
-                'mode': "play"
-            }
-            filename = "%s?%s" % (path, urllib.urlencode(params))
-
-
-        ##### UPDATE THE MOVIE #####
-        if update_item:
-            self.logMsg("UPDATE movie itemid: %s - Title: %s" % (itemid, title), 1)
-
-            # Update the movie entry
-            query = ' '.join((
-                
-                "UPDATE movie",
-                "SET c00 = ?, c01 = ?, c02 = ?, c03 = ?, c04 = ?, c05 = ?, c06 = ?,",
-                    "c07 = ?, c09 = ?, c10 = ?, c11 = ?, c12 = ?, c14 = ?, c15 = ?,",
-                    "c16 = ?, c18 = ?, c19 = ?, c21 = ?",
-                "WHERE idMovie = ?"
-            ))
-            kodicursor.execute(query, (title, plot, shortplot, tagline, votecount, rating, writer,
-                year, imdb, sorttitle, runtime, mpaa, genre, director, title, studio, trailer,
-                country, movieid))
-
-            # Update the checksum in emby table
-            emby_db.updateReference(itemid, checksum)
-        
-        ##### OR ADD THE MOVIE #####
-        else:
-            self.logMsg("ADD movie itemid: %s - Title: %s" % (itemid, title), 1)
-            
-            # Add path
-            pathid = kodi_db.addPath(path)
-            # Add the file
-            fileid = kodi_db.addFile(filename, pathid)
-            
-            # Create the movie entry
-            query = (
-                '''
-                INSERT INTO movie(
-                    idMovie, idFile, c00, c01, c02, c03, c04, c05, c06, c07, 
-                    c09, c10, c11, c12, c14, c15, c16, c18, c19, c21)
-
-                VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
-                '''
-            )
-            kodicursor.execute(query, (movieid, fileid, title, plot, shortplot, tagline, votecount,
-                rating, writer, year, imdb, sorttitle, runtime, mpaa, genre, director, title,
-                studio, trailer, country))
-
-            # Create the reference in emby table
-            emby_db.addReference(itemid, movieid, "Movie", "movie", fileid, pathid, None, checksum, viewid)
-
-        # Update the path
-        query = ' '.join((
-
-            "UPDATE path",
-            "SET strPath = ?, strContent = ?, strScraper = ?, noUpdate = ?",
-            "WHERE idPath = ?"
-        ))
-        kodicursor.execute(query, (path, "movies", "metadata.local", 1, pathid))
-
-        # Update the file
-        query = ' '.join((
-
-            "UPDATE files",
-            "SET idPath = ?, strFilename = ?, dateAdded = ?",
-            "WHERE idFile = ?"
-        ))
-        kodicursor.execute(query, (pathid, filename, dateadded, fileid))
-        
-        # Process countries
-        kodi_db.addCountries(movieid, item['ProductionLocations'], "movie")
-        # Process cast
-        people = artwork.getPeopleArtwork(item['People'])
-        kodi_db.addPeople(movieid, people, "movie")
-        # Process genres
-        kodi_db.addGenres(movieid, genres, "movie")
-        # Process artwork
-        artwork.addArtwork(artwork.getAllArtwork(item), movieid, "movie", kodicursor)
-        # Process stream details
-        streams = API.getMediaStreams()
-        kodi_db.addStreams(fileid, streams, runtime)
-        # Process studios
-        kodi_db.addStudios(movieid, studios, "movie")
-        # Process tags: view, emby tags
-        tags = [viewtag]
-        tags.extend(item['Tags'])
-        if userdata['Favorite']:
-            tags.append("Favorite movies")
-        kodi_db.addTags(movieid, tags, "movie")
-        # Process playstates
-        resume = API.adjustResume(userdata['Resume'])
-        total = round(float(runtime), 6)
-        kodi_db.addPlaystate(fileid, resume, total, playcount, dateplayed)
-
-    def add_updateBoxset(self, boxset):
-
-        emby = self.emby
-        emby_db = self.emby_db
-        kodi_db = self.kodi_db
-        artwork = self.artwork
-
-        boxsetid = boxset['Id']
-        title = boxset['Name']
-        checksum = boxset['Etag']
-        emby_dbitem = emby_db.getItem_byId(boxsetid)
-        try:
-            setid = emby_dbitem[0]
-
-        except TypeError:
-            setid = kodi_db.createBoxset(title)
-
-        # Process artwork
-        artwork.addArtwork(artwork.getAllArtwork(boxset), setid, "set", self.kodicursor)
-        
-        # Process movies inside boxset
-        current_movies = emby_db.getItemId_byParentId(setid, "movie")
-        process = []
-        try:
-            # Try to convert tuple to dictionary
-            current = dict(current_movies)
-        except ValueError:
-            current = {}
-
-        # Sort current titles
-        for current_movie in current:
-            process.append(current_movie)
-
-        # New list to compare
-        boxsetMovies = emby.getMovies_byBoxset(boxsetid)
-        for movie in boxsetMovies['Items']:
-
-            itemid = movie['Id']
-
-            if not current.get(itemid):
-                # Assign boxset to movie
-                emby_dbitem = emby_db.getItem_byId(itemid)
-                try:
-                    movieid = emby_dbitem[0]
-                except TypeError:
-                    self.logMsg("Failed to add: %s to boxset." % movie['Name'], 1)
-                    continue
-
-                self.logMsg("New addition to boxset %s: %s" % (title, movie['Name']), 1)
-                kodi_db.assignBoxset(setid, movieid)
-                # Update emby reference
-                emby_db.updateParentId(itemid, setid)
-            else:
-                # Remove from process, because the item still belongs
-                process.remove(itemid)
-
-        # Process removals from boxset
-        for movie in process:
-            movieid = current[movie]
-            self.logMsg("Remove from boxset %s: %s" % (title, movieid))
-            kodi_db.removefromBoxset(movieid)
-            # Update emby reference
-            emby_db.updateParentId(movie, None)
-
-        # Update the reference in the emby table
-        emby_db.addReference(boxsetid, setid, "BoxSet", mediatype="set", checksum=checksum)
-
-    def updateUserdata(self, item):
-        # This updates: Favorite, LastPlayedDate, Playcount, PlaybackPositionTicks
-        # Poster with progress bar
-        emby_db = self.emby_db
-        kodi_db = self.kodi_db
-        API = api.API(item)
-        
-        # Get emby information
-        itemid = item['Id']
-        checksum = API.getChecksum()
-        userdata = API.getUserData()
-        runtime = API.getRuntime()
-
-        # Get Kodi information
-        emby_dbitem = emby_db.getItem_byId(itemid)
-        try:
-            movieid = emby_dbitem[0]
-            fileid = emby_dbitem[1]
-            self.logMsg(
-                "Update playstate for movie: %s fileid: %s"
-                % (item['Name'], fileid), 1)
-        except TypeError:
-            return
-
-        # Process favorite tags
-        if userdata['Favorite']:
-            kodi_db.addTag(movieid, "Favorite movies", "movie")
-        else:
-            kodi_db.removeTag(movieid, "Favorite movies", "movie")
-
-        # Process playstates
-        playcount = userdata['PlayCount']
-        dateplayed = userdata['LastPlayedDate']
-        resume = API.adjustResume(userdata['Resume'])
-        total = round(float(runtime), 6)
-
-        self.logMsg("%s New resume point: %s" % (itemid, resume))
-
-        kodi_db.addPlaystate(fileid, resume, total, playcount, dateplayed)
-        emby_db.updateReference(itemid, checksum)
-
-    def remove(self, itemid):
-        # Remove movieid, fileid, emby reference
-        emby_db = self.emby_db
-        kodicursor = self.kodicursor
-        artwork = self.artwork
-
-        emby_dbitem = emby_db.getItem_byId(itemid)
-        try:
-            kodiid = emby_dbitem[0]
-            fileid = emby_dbitem[1]
-            mediatype = emby_dbitem[4]
-            self.logMsg("Removing %sid: %s fileid: %s" % (mediatype, kodiid, fileid), 1)
-        except TypeError:
-            return
-
-        # Remove the emby reference
-        emby_db.removeItem(itemid)
-        # Remove artwork
-        artwork.deleteArtwork(kodiid, mediatype, kodicursor)
-
-        if mediatype == "movie":
-            # Delete kodi movie and file
-            kodicursor.execute("DELETE FROM movie WHERE idMovie = ?", (kodiid,))
-            kodicursor.execute("DELETE FROM files WHERE idFile = ?", (fileid,))
-
-        elif mediatype == "set":
-            # Delete kodi boxset
-            boxset_movies = emby_db.getItem_byParentId(kodiid, "movie")
-            for movie in boxset_movies:
-                embyid = movie[0]
-                movieid = movie[1]
-                self.kodi_db.removefromBoxset(movieid)
-                # Update emby reference
-                emby_db.updateParentId(embyid, None)
-
-            kodicursor.execute("DELETE FROM sets WHERE idSet = ?", (kodiid,))
-
-        self.logMsg("Deleted %s %s from kodi database" % (mediatype, itemid), 1)
-
-class MusicVideos(Items):
-
-    
-    def __init__(self, embycursor, kodicursor):
-        Items.__init__(self, embycursor, kodicursor)
-
-    def added(self, items, pdialog):
-
-        total = len(items)
-        count = 0
-        for mvideo in items:
-
-            title = mvideo['Name']
-            if pdialog:
-                percentage = int((float(count) / float(total))*100)
-                pdialog.update(percentage, message=title)
-                count += 1
-            self.add_update(mvideo)
-            if not pdialog and self.contentmsg:
-                self.contentPop(title, self.newvideo_time)
-
-
-    def add_update(self, item, viewtag=None, viewid=None):
-        # Process single music video
-        kodicursor = self.kodicursor
-        emby_db = self.emby_db
-        kodi_db = self.kodi_db
-        artwork = self.artwork
-        API = api.API(item)
-
-        # If the item already exist in the local Kodi DB we'll perform a full item update
-        # If the item doesn't exist, we'll add it to the database
-        update_item = True
-        itemid = item['Id']
-        emby_dbitem = emby_db.getItem_byId(itemid)
-        try:
-            mvideoid = emby_dbitem[0]
-            fileid = emby_dbitem[1]
-            pathid = emby_dbitem[2]
-            self.logMsg("mvideoid: %s fileid: %s pathid: %s" % (mvideoid, fileid, pathid), 1)
-        
-        except TypeError:
-            update_item = False
-            self.logMsg("mvideoid: %s not found." % itemid, 2)
-            # mvideoid
-            kodicursor.execute("select coalesce(max(idMVideo),0) from musicvideo")
-            mvideoid = kodicursor.fetchone()[0] + 1
-
-        else:
-            # Verification the item is still in Kodi
-            query = "SELECT * FROM musicvideo WHERE idMVideo = ?"
-            kodicursor.execute(query, (mvideoid,))
-            try:
-                kodicursor.fetchone()[0]
-            except TypeError:
-                # item is not found, let's recreate it.
-                update_item = False
-                self.logMsg("mvideoid: %s missing from Kodi, repairing the entry." % mvideoid, 1)
-
-        if not viewtag or not viewid:
-            # Get view tag from emby
-            viewtag, viewid, mediatype = self.emby.getView_embyId(itemid)
-            self.logMsg("View tag found: %s" % viewtag, 2)
-
-        # fileId information
-        checksum = API.getChecksum()
-        dateadded = API.getDateCreated()
-        userdata = API.getUserData()
-        playcount = userdata['PlayCount']
-        dateplayed = userdata['LastPlayedDate']
-
-        # item details
-        runtime = API.getRuntime()
-        plot = API.getOverview()
-        title = item['Name']
-        year = item.get('ProductionYear')
-        genres = item['Genres']
-        genre = " / ".join(genres)
-        studios = API.getStudios()
-        studio = " / ".join(studios)
-        artist = " / ".join(item.get('Artists'))
-        album = item.get('Album')
-        track = item.get('Track')
-        people = API.getPeople()
-        director = " / ".join(people['Director'])
-
-        
-        ##### GET THE FILE AND PATH #####
-        playurl = API.getFilePath()
-
-        if "\\" in playurl:
-            # Local path
-            filename = playurl.rsplit("\\", 1)[1]
-        else: # Network share
-            filename = playurl.rsplit("/", 1)[1]
-
-        if self.directpath:
-            # Direct paths is set the Kodi way
-            if utils.window('emby_pathverified') != "true" and not xbmcvfs.exists(playurl):
-                # Validate the path is correct with user intervention
-                resp = xbmcgui.Dialog().yesno(
-                                        heading="Can't validate path",
-                                        line1=(
-                                            "Kodi can't locate file: %s. Verify the path. "
-                                            "You may to verify your network credentials in the "
-                                            "add-on settings or use the emby path substitution "
-                                            "to format your path correctly. Stop syncing?"
-                                            % playurl))
-                if resp:
-                    utils.window('emby_shouldStop', value="true")
-                    return False
-            
-            path = playurl.replace(filename, "")
-            utils.window('emby_pathverified', value="true")
-        else:
-            # Set plugin path and media flags using real filename
-            path = "plugin://plugin.video.emby.musicvideos/"
-            params = {
-
-                'filename': filename.encode('utf-8'),
-                'id': itemid,
-                'dbid': mvideoid,
-                'mode': "play"
-            }
-            filename = "%s?%s" % (path, urllib.urlencode(params))
-
-
-        ##### UPDATE THE MUSIC VIDEO #####
-        if update_item:
-            self.logMsg("UPDATE mvideo itemid: %s - Title: %s" % (itemid, title), 1)
-            
-            # Update path
-            query = "UPDATE path SET strPath = ? WHERE idPath = ?"
-            kodicursor.execute(query, (path, pathid))
-
-            # Update the filename
-            query = "UPDATE files SET strFilename = ?, dateAdded = ? WHERE idFile = ?"
-            kodicursor.execute(query, (filename, dateadded, fileid))
-
-            # Update the music video entry
-            query = ' '.join((
-                
-                "UPDATE musicvideo",
-                "SET c00 = ?, c04 = ?, c05 = ?, c06 = ?, c07 = ?, c08 = ?, c09 = ?, c10 = ?,",
-                    "c11 = ?, c12 = ?"
-                "WHERE idMVideo = ?"
-            ))
-            kodicursor.execute(query, (title, runtime, director, studio, year, plot, album,
-                artist, genre, track, mvideoid))
-
-            # Update the checksum in emby table
-            emby_db.updateReference(itemid, checksum)
-        
-        ##### OR ADD THE MUSIC VIDEO #####
-        else:
-            self.logMsg("ADD mvideo itemid: %s - Title: %s" % (itemid, title), 1)
-            
-            # Add path
-            query = ' '.join((
-
-                "SELECT idPath",
-                "FROM path",
-                "WHERE strPath = ?"
-            ))
-            kodicursor.execute(query, (path,))
-            try:
-                pathid = kodicursor.fetchone()[0]
-            except TypeError:
-                kodicursor.execute("select coalesce(max(idPath),0) from path")
-                pathid = kodicursor.fetchone()[0] + 1
-                query = (
-                    '''
-                    INSERT OR REPLACE INTO path(
-                        idPath, strPath, strContent, strScraper, noUpdate)
-
-                    VALUES (?, ?, ?, ?, ?)
-                    '''
-                )
-                kodicursor.execute(query, (pathid, path, "musicvideos", "metadata.local", 1))
-
-            # Add the file
-            kodicursor.execute("select coalesce(max(idFile),0) from files")
-            fileid = kodicursor.fetchone()[0] + 1
-            query = (
-                '''
-                INSERT INTO files(
-                    idFile, idPath, strFilename, dateAdded)
-
-                VALUES (?, ?, ?, ?)
-                '''
-            )
-            kodicursor.execute(query, (fileid, pathid, filename, dateadded))
-            
-            # Create the musicvideo entry
-            query = (
-                '''
-                INSERT INTO musicvideo(
-                    idMVideo, idFile, c00, c04, c05, c06, c07, c08, c09, c10, c11, c12)
-
-                VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
-                '''
-            )
-            kodicursor.execute(query, (mvideoid, fileid, title, runtime, director, studio,
-                year, plot, album, artist, genre, track))
-
-            # Create the reference in emby table
-            emby_db.addReference(itemid, mvideoid, "MusicVideo", "musicvideo", fileid, pathid,
-                checksum=checksum, mediafolderid=viewid)
-
-        
-        # Process cast
-        people = item['People']
-        artists = item['ArtistItems']
-        for artist in artists:
-            artist['Type'] = "Artist"
-        people.extend(artists)
-        people = artwork.getPeopleArtwork(people)
-        kodi_db.addPeople(mvideoid, people, "musicvideo")
-        # Process genres
-        kodi_db.addGenres(mvideoid, genres, "musicvideo")
-        # Process artwork
-        artwork.addArtwork(artwork.getAllArtwork(item), mvideoid, "musicvideo", kodicursor)
-        # Process stream details
-        streams = API.getMediaStreams()
-        kodi_db.addStreams(fileid, streams, runtime)
-        # Process studios
-        kodi_db.addStudios(mvideoid, studios, "musicvideo")
-        # Process tags: view, emby tags
-        tags = [viewtag]
-        tags.extend(item['Tags'])
-        if userdata['Favorite']:
-            tags.append("Favorite musicvideos")
-        kodi_db.addTags(mvideoid, tags, "musicvideo")
-        # Process playstates
-        resume = API.adjustResume(userdata['Resume'])
-        total = round(float(runtime), 6)
-        kodi_db.addPlaystate(fileid, resume, total, playcount, dateplayed)
-
-    def updateUserdata(self, item):
-        # This updates: Favorite, LastPlayedDate, Playcount, PlaybackPositionTicks
-        # Poster with progress bar
-        emby_db = self.emby_db
-        kodi_db = self.kodi_db
-        API = api.API(item)
-        
-        # Get emby information
-        itemid = item['Id']
-        checksum = API.getChecksum()
-        userdata = API.getUserData()
-        runtime = API.getRuntime()
-
-        # Get Kodi information
-        emby_dbitem = emby_db.getItem_byId(itemid)
-        try:
-            mvideoid = emby_dbitem[0]
-            fileid = emby_dbitem[1]
-            self.logMsg(
-                "Update playstate for musicvideo: %s fileid: %s"
-                % (item['Name'], fileid), 1)
-        except TypeError:
-            return
-
-        # Process favorite tags
-        if userdata['Favorite']:
-            kodi_db.addTag(mvideoid, "Favorite musicvideos", "musicvideo")
-        else:
-            kodi_db.removeTag(mvideoid, "Favorite musicvideos", "musicvideo")
-
-        # Process playstates
-        playcount = userdata['PlayCount']
-        dateplayed = userdata['LastPlayedDate']
-        resume = API.adjustResume(userdata['Resume'])
-        total = round(float(runtime), 6)
-
-        kodi_db.addPlaystate(fileid, resume, total, playcount, dateplayed)
-        emby_db.updateReference(itemid, checksum)
-
-    def remove(self, itemid):
-        # Remove mvideoid, fileid, pathid, emby reference
-        emby_db = self.emby_db
-        kodicursor = self.kodicursor
-        artwork = self.artwork
-
-        emby_dbitem = emby_db.getItem_byId(itemid)
-        try:
-            mvideoid = emby_dbitem[0]
-            fileid = emby_dbitem[1]
-            pathid = emby_dbitem[2]
-            self.logMsg("Removing mvideoid: %s fileid: %s" % (mvideoid, fileid, pathid), 1)
-        except TypeError:
-            return
-
-        # Remove artwork
-        query = ' '.join((
-
-            "SELECT url, type",
-            "FROM art",
-            "WHERE media_id = ?",
-            "AND media_type = 'musicvideo'"
-        ))
-        kodicursor.execute(query, (mvideoid,))
-        rows = kodicursor.fetchall()
-        for row in rows:
-            
-            url = row[0]
-            imagetype = row[1]
-            if imagetype in ("poster", "fanart"):
-                artwork.deleteCachedArtwork(url)
-
-        kodicursor.execute("DELETE FROM musicvideo WHERE idMVideo = ?", (mvideoid,))
-        kodicursor.execute("DELETE FROM files WHERE idFile = ?", (fileid,))
-        if self.directpath:
-            kodicursor.execute("DELETE FROM path WHERE idPath = ?", (pathid,))
-        self.embycursor.execute("DELETE FROM emby WHERE emby_id = ?", (itemid,))
-
-        self.logMsg("Deleted musicvideo %s from kodi database" % itemid, 1)
-
-class TVShows(Items):
-
-
-    def __init__(self, embycursor, kodicursor):
-        Items.__init__(self, embycursor, kodicursor)
-
-    def added(self, items, pdialog):
-        
-        total = len(items)
-        count = 0
-        for tvshow in items:
-
-            title = tvshow['Name']
-            if pdialog:
-                percentage = int((float(count) / float(total))*100)
-                pdialog.update(percentage, message=title)
-                count += 1
-            self.add_update(tvshow)
-            # Add episodes
-            all_episodes = self.emby.getEpisodesbyShow(tvshow['Id'])
-            self.added_episode(all_episodes['Items'], pdialog)
-
-    def added_season(self, items, pdialog):
-        
-        total = len(items)
-        count = 0
-        for season in items:
-
-            title = "%s - %s" % (season.get('SeriesName', "Unknown"), season['Name'])
-            if pdialog:
-                percentage = int((float(count) / float(total))*100)
-                pdialog.update(percentage, message=title)
-                count += 1
-            self.add_updateSeason(season)
-            # Add episodes
-            all_episodes = self.emby.getEpisodesbySeason(season['Id'])
-            self.added_episode(all_episodes['Items'], pdialog)
-
-    def added_episode(self, items, pdialog):
-        
-        total = len(items)
-        count = 0
-        for episode in items:
-            title = "%s - %s" % (episode.get('SeriesName', "Unknown"), episode['Name'])
-            if pdialog:
-                percentage = int((float(count) / float(total))*100)
-                pdialog.update(percentage, message=title)
-                count += 1
-            self.add_updateEpisode(episode)
-            if not pdialog and self.contentmsg:
-                self.contentPop(title, self.newvideo_time)
-
-
-    def add_update(self, item, viewtag=None, viewid=None):
-        # Process single tvshow
-        kodicursor = self.kodicursor
-        emby = self.emby
-        emby_db = self.emby_db
-        kodi_db = self.kodi_db
-        artwork = self.artwork
-        API = api.API(item)
-
-        if utils.settings('syncEmptyShows') == "false" and not item['RecursiveItemCount']:
-            self.logMsg("Skipping empty show: %s" % item['Name'], 1)
-            return
-        # If the item already exist in the local Kodi DB we'll perform a full item update
-        # If the item doesn't exist, we'll add it to the database
-        update_item = True
-        force_episodes = False
-        itemid = item['Id']
-        emby_dbitem = emby_db.getItem_byId(itemid)
-        try:
-            showid = emby_dbitem[0]
-            pathid = emby_dbitem[2]
-            self.logMsg("showid: %s pathid: %s" % (showid, pathid), 1)
-        
-        except TypeError:
-            update_item = False
-            self.logMsg("showid: %s not found." % itemid, 2)
-            kodicursor.execute("select coalesce(max(idShow),0) from tvshow")
-            showid = kodicursor.fetchone()[0] + 1
-
-        else:
-            # Verification the item is still in Kodi
-            query = "SELECT * FROM tvshow WHERE idShow = ?"
-            kodicursor.execute(query, (showid,))
-            try:
-                kodicursor.fetchone()[0]
-            except TypeError:
-                # item is not found, let's recreate it.
-                update_item = False
-                self.logMsg("showid: %s missing from Kodi, repairing the entry." % showid, 1)
-                # Force re-add episodes after the show is re-created.
-                force_episodes = True
-
-
-        if viewtag is None or viewid is None:
-            # Get view tag from emby
-            viewtag, viewid, mediatype = emby.getView_embyId(itemid)
-            self.logMsg("View tag found: %s" % viewtag, 2)
-
-        # fileId information
-        checksum = API.getChecksum()
-        dateadded = API.getDateCreated()
-        userdata = API.getUserData()
-        playcount = userdata['PlayCount']
-        dateplayed = userdata['LastPlayedDate']
-
-        # item details
-        genres = item['Genres']
-        title = item['Name']
-        plot = API.getOverview()
-        rating = item.get('CommunityRating')
-        premieredate = API.getPremiereDate()
-        tvdb = API.getProvider('Tvdb')
-        sorttitle = item['SortName']
-        mpaa = API.getMpaa()
-        genre = " / ".join(genres)
-        studios = API.getStudios()
-        studio = " / ".join(studios)
-
-        
-        ##### GET THE FILE AND PATH #####
-        playurl = API.getFilePath()
-
-        if self.directpath:
-            # Direct paths is set the Kodi way
-            if "\\" in playurl:
-                # Local path
-                path = "%s\\" % playurl
-                toplevelpath = "%s\\" % dirname(dirname(path))
-            else:
-                # Network path
-                path = "%s/" % playurl
-                toplevelpath = "%s/" % dirname(dirname(path))
-
-            if utils.window('emby_pathverified') != "true" and not xbmcvfs.exists(path):
-                # Validate the path is correct with user intervention
-                resp = xbmcgui.Dialog().yesno(
-                                        heading="Can't validate path",
-                                        line1=(
-                                            "Kodi can't locate file: %s. Verify the path. "
-                                            "You may to verify your network credentials in the "
-                                            "add-on settings or use the emby path substitution "
-                                            "to format your path correctly. Stop syncing?"
-                                            % playurl))
-                if resp:
-                    utils.window('emby_shouldStop', value="true")
-                    return False
-
-            utils.window('emby_pathverified', value="true")
-        else:
-            # Set plugin path
-            toplevelpath = "plugin://plugin.video.emby.tvshows/"
-            path = "%s%s/" % (toplevelpath, itemid)
-
-
-        ##### UPDATE THE TVSHOW #####
-        if update_item:
-            self.logMsg("UPDATE tvshow itemid: %s - Title: %s" % (itemid, title), 1)
-
-            # Update the tvshow entry
-            query = ' '.join((
-                
-                "UPDATE tvshow",
-                "SET c00 = ?, c01 = ?, c04 = ?, c05 = ?, c08 = ?, c09 = ?,",
-                    "c12 = ?, c13 = ?, c14 = ?, c15 = ?",
-                "WHERE idShow = ?"
-            ))
-            kodicursor.execute(query, (title, plot, rating, premieredate, genre, title,
-                tvdb, mpaa, studio, sorttitle, showid))
-
-            # Update the checksum in emby table
-            emby_db.updateReference(itemid, checksum)
-        
-        ##### OR ADD THE TVSHOW #####
-        else:
-            self.logMsg("ADD tvshow itemid: %s - Title: %s" % (itemid, title), 1)
-            
-            # Add top path
-            toppathid = kodi_db.addPath(toplevelpath)
-            query = ' '.join((
-
-                "UPDATE path",
-                "SET strPath = ?, strContent = ?, strScraper = ?, noUpdate = ?",
-                "WHERE idPath = ?"
-            ))
-            kodicursor.execute(query, (toplevelpath, "tvshows", "metadata.local", 1, toppathid))
-            
-            # Add path
-            pathid = kodi_db.addPath(path)
-            
-            # Create the tvshow entry
-            query = (
-                '''
-                INSERT INTO tvshow(
-                    idShow, c00, c01, c04, c05, c08, c09, c12, c13, c14, c15) 
-
-                VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
-                '''
-            )
-            kodicursor.execute(query, (showid, title, plot, rating, premieredate, genre,
-                title, tvdb, mpaa, studio, sorttitle))
-
-            # Link the path
-            query = "INSERT INTO tvshowlinkpath(idShow, idPath) values(?, ?)"
-            kodicursor.execute(query, (showid, pathid))
-
-            # Create the reference in emby table
-            emby_db.addReference(itemid, showid, "Series", "tvshow", pathid=pathid,
-                                checksum=checksum, mediafolderid=viewid)
-
-        # Update the path
-        query = ' '.join((
-
-            "UPDATE path",
-            "SET strPath = ?, strContent = ?, strScraper = ?, noUpdate = ?",
-            "WHERE idPath = ?"
-        ))
-        kodicursor.execute(query, (path, None, None, 1, pathid))
-        
-        # Process cast
-        people = artwork.getPeopleArtwork(item['People'])
-        kodi_db.addPeople(showid, people, "tvshow")
-        # Process genres
-        kodi_db.addGenres(showid, genres, "tvshow")
-        # Process artwork
-        artwork.addArtwork(artwork.getAllArtwork(item), showid, "tvshow", kodicursor)
-        # Process studios
-        kodi_db.addStudios(showid, studios, "tvshow")
-        # Process tags: view, emby tags
-        tags = [viewtag]
-        tags.extend(item['Tags'])
-        if userdata['Favorite']:
-            tags.append("Favorite tvshows")
-        kodi_db.addTags(showid, tags, "tvshow")
-        # Process seasons
-        all_seasons = emby.getSeasons(itemid)
-        for season in all_seasons['Items']:
-            self.add_updateSeason(season, showid=showid)
-        else:
-            # Finally, refresh the all season entry
-            seasonid = kodi_db.addSeason(showid, -1)
-            # Process artwork
-            artwork.addArtwork(artwork.getAllArtwork(item), seasonid, "season", kodicursor)
-
-        if force_episodes:
-            # We needed to recreate the show entry. Re-add episodes now.
-            self.logMsg("Repairing episodes for showid: %s %s" % (showid, title), 1)
-            all_episodes = emby.getEpisodesbyShow(itemid)
-            self.added_episode(all_episodes['Items'], None)
-
-    def add_updateSeason(self, item, showid=None):
-
-        kodicursor = self.kodicursor
-        emby_db = self.emby_db
-        kodi_db = self.kodi_db
-        artwork = self.artwork
-
-        seasonnum = item.get('IndexNumber', 1)
-        itemid = item['Id']
-
-        if showid is None:
-            try:
-                seriesId = item['SeriesId']
-                showid = emby_db.getItem_byId(seriesId)[0]
-            except KeyError:
-                return
-            except TypeError:
-                # Show is missing, update show instead.
-                show = self.emby.getItem(seriesId)
-                self.add_update(show)
-                return
-        
-        seasonid = kodi_db.addSeason(showid, seasonnum)
-        
-        if item['LocationType'] != "Virtual":
-            # Create the reference in emby table
-            emby_db.addReference(itemid, seasonid, "Season", "season", parentid=showid)
-
-        # Process artwork
-        artwork.addArtwork(artwork.getAllArtwork(item), seasonid, "season", kodicursor)
-
-    def add_updateEpisode(self, item):
-        # Process single episode
-        kodiversion = self.kodiversion
-        kodicursor = self.kodicursor
-        emby_db = self.emby_db
-        kodi_db = self.kodi_db
-        artwork = self.artwork
-        API = api.API(item)
-
-        # If the item already exist in the local Kodi DB we'll perform a full item update
-        # If the item doesn't exist, we'll add it to the database
-        update_item = True
-        itemid = item['Id']
-        emby_dbitem = emby_db.getItem_byId(itemid)
-        try:
-            episodeid = emby_dbitem[0]
-            fileid = emby_dbitem[1]
-            pathid = emby_dbitem[2]
-            self.logMsg("episodeid: %s fileid: %s pathid: %s" % (episodeid, fileid, pathid), 1)
-        
-        except TypeError:
-            update_item = False
-            self.logMsg("episodeid: %s not found." % itemid, 2)
-            # episodeid
-            kodicursor.execute("select coalesce(max(idEpisode),0) from episode")
-            episodeid = kodicursor.fetchone()[0] + 1
-
-        else:
-            # Verification the item is still in Kodi
-            query = "SELECT * FROM episode WHERE idEpisode = ?"
-            kodicursor.execute(query, (episodeid,))
-            try:
-                kodicursor.fetchone()[0]
-            except TypeError:
-                # item is not found, let's recreate it.
-                update_item = False
-                self.logMsg("episodeid: %s missing from Kodi, repairing the entry." % episodeid, 1)
-
-        # fileId information
-        checksum = API.getChecksum()
-        dateadded = API.getDateCreated()
-        userdata = API.getUserData()
-        playcount = userdata['PlayCount']
-        dateplayed = userdata['LastPlayedDate']
-
-        # item details
-        people = API.getPeople()
-        writer = " / ".join(people['Writer'])
-        director = " / ".join(people['Director'])
-        title = item['Name']
-        plot = API.getOverview()
-        rating = item.get('CommunityRating')
-        runtime = API.getRuntime()
-        premieredate = API.getPremiereDate()
-
-        # episode details
-        try:
-            seriesId = item['SeriesId']
-        except KeyError:
-            # Missing seriesId, skip
-            self.logMsg("Skipping: %s. SeriesId is missing." % itemid, 1)
-            return False
-            
-        seriesName = item['SeriesName']
-        season = item.get('ParentIndexNumber')
-        episode = item.get('IndexNumber', -1)
-       
-        if season is None:
-            if item.get('AbsoluteEpisodeNumber'):
-                # Anime scenario
-                season = 1
-                episode = item['AbsoluteEpisodeNumber']
-            else:
-                season = -1
-
-        # Specials ordering within season
-        if item.get('AirsAfterSeasonNumber'):
-            airsBeforeSeason = item['AirsAfterSeasonNumber']
-            airsBeforeEpisode = 4096 # Kodi default number for afterseason ordering
-        else:
-            airsBeforeSeason = item.get('AirsBeforeSeasonNumber')
-            airsBeforeEpisode = item.get('AirsBeforeEpisodeNumber')
-
-        # Append multi episodes to title
-        if item.get('IndexNumberEnd'):              
-            title = "| %02d | %s" % (item['IndexNumberEnd'], title)
-
-        # Get season id
-        show = emby_db.getItem_byId(seriesId)
-        try:
-            showid = show[0]
-        except TypeError:
-            # Show is missing from database
-            show = self.emby.getItem(seriesId)
-            self.add_update(show)
-            show = emby_db.getItem_byId(seriesId)
-            try:
-                showid = show[0]
-            except TypeError:
-                self.logMsg("Skipping: %s. Unable to add series: %s." % (itemid, seriesId))
-                return False
-
-        seasonid = kodi_db.addSeason(showid, season)
-
-        
-        ##### GET THE FILE AND PATH #####
-        playurl = API.getFilePath()
-
-        if "\\" in playurl:
-            # Local path
-            filename = playurl.rsplit("\\", 1)[1]
-        else: # Network share
-            filename = playurl.rsplit("/", 1)[1]
-
-        if self.directpath:
-            # Direct paths is set the Kodi way
-            if utils.window('emby_pathverified') != "true" and not xbmcvfs.exists(playurl):
-                # Validate the path is correct with user intervention
-                resp = xbmcgui.Dialog().yesno(
-                                        heading="Can't validate path",
-                                        line1=(
-                                            "Kodi can't locate file: %s. Verify the path. "
-                                            "You may to verify your network credentials in the "
-                                            "add-on settings or use the emby path substitution "
-                                            "to format your path correctly. Stop syncing?"
-                                            % playurl))
-                if resp:
-                    utils.window('emby_shouldStop', value="true")
-                    return False
-            
-            path = playurl.replace(filename, "")
-            utils.window('emby_pathverified', value="true")
-        else:
-            # Set plugin path and media flags using real filename
-            path = "plugin://plugin.video.emby.tvshows/%s/" % seriesId
-            params = {
-
-                'filename': filename.encode('utf-8'),
-                'id': itemid,
-                'dbid': episodeid,
-                'mode': "play"
-            }
-            filename = "%s?%s" % (path, urllib.urlencode(params))
-
-
-        ##### UPDATE THE EPISODE #####
-        if update_item:
-            self.logMsg("UPDATE episode itemid: %s - Title: %s" % (itemid, title), 1)
-
-            # Update the movie entry
-            if kodiversion in (16, 17):
-                # Kodi Jarvis, Krypton
-                query = ' '.join((
-                
-                    "UPDATE episode",
-                    "SET c00 = ?, c01 = ?, c03 = ?, c04 = ?, c05 = ?, c09 = ?, c10 = ?,",
-                        "c12 = ?, c13 = ?, c14 = ?, c15 = ?, c16 = ?, idSeason = ?",
-                    "WHERE idEpisode = ?"
-                ))
-                kodicursor.execute(query, (title, plot, rating, writer, premieredate,
-                    runtime, director, season, episode, title, airsBeforeSeason,
-                    airsBeforeEpisode, seasonid, episodeid))
-            else:
-                query = ' '.join((
-                    
-                    "UPDATE episode",
-                    "SET c00 = ?, c01 = ?, c03 = ?, c04 = ?, c05 = ?, c09 = ?, c10 = ?,",
-                        "c12 = ?, c13 = ?, c14 = ?, c15 = ?, c16 = ?",
-                    "WHERE idEpisode = ?"
-                ))
-                kodicursor.execute(query, (title, plot, rating, writer, premieredate,
-                    runtime, director, season, episode, title, airsBeforeSeason,
-                    airsBeforeEpisode, episodeid))
-
-            # Update the checksum in emby table
-            emby_db.updateReference(itemid, checksum)
-            # Update parentid reference
-            emby_db.updateParentId(itemid, seasonid)
-        
-        ##### OR ADD THE EPISODE #####
-        else:
-            self.logMsg("ADD episode itemid: %s - Title: %s" % (itemid, title), 1)
-            
-            # Add path
-            pathid = kodi_db.addPath(path)
-            # Add the file
-            fileid = kodi_db.addFile(filename, pathid)
-            
-            # Create the episode entry
-            if kodiversion in (16, 17):
-                # Kodi Jarvis, Krypton
-                query = (
-                    '''
-                    INSERT INTO episode(
-                        idEpisode, idFile, c00, c01, c03, c04, c05, c09, c10, c12, c13, c14,
-                        idShow, c15, c16, idSeason)
-
-                    VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
-                    '''
-                )
-                kodicursor.execute(query, (episodeid, fileid, title, plot, rating, writer,
-                    premieredate, runtime, director, season, episode, title, showid,
-                    airsBeforeSeason, airsBeforeEpisode, seasonid))
-            else:
-                query = (
-                    '''
-                    INSERT INTO episode(
-                        idEpisode, idFile, c00, c01, c03, c04, c05, c09, c10, c12, c13, c14,
-                        idShow, c15, c16)
-
-                    VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
-                    '''
-                )
-                kodicursor.execute(query, (episodeid, fileid, title, plot, rating, writer,
-                    premieredate, runtime, director, season, episode, title, showid,
-                    airsBeforeSeason, airsBeforeEpisode))
-
-            # Create the reference in emby table
-            emby_db.addReference(itemid, episodeid, "Episode", "episode", fileid, pathid,
-                seasonid, checksum)
-
-        # Update the path
-        query = ' '.join((
-
-            "UPDATE path",
-            "SET strPath = ?, strContent = ?, strScraper = ?, noUpdate = ?",
-            "WHERE idPath = ?"
-        ))
-        kodicursor.execute(query, (path, None, None, 1, pathid))
-
-        # Update the file
-        query = ' '.join((
-
-            "UPDATE files",
-            "SET idPath = ?, strFilename = ?, dateAdded = ?",
-            "WHERE idFile = ?"
-        ))
-        kodicursor.execute(query, (pathid, filename, dateadded, fileid))
-        
-        # Process cast
-        people = artwork.getPeopleArtwork(item['People'])
-        kodi_db.addPeople(episodeid, people, "episode")
-        # Process artwork
-        artworks = artwork.getAllArtwork(item)
-        artwork.addOrUpdateArt(artworks['Primary'], episodeid, "episode", "thumb", kodicursor)
-        # Process stream details
-        streams = API.getMediaStreams()
-        kodi_db.addStreams(fileid, streams, runtime)
-        # Process playstates
-        resume = API.adjustResume(userdata['Resume'])
-        total = round(float(runtime), 6)
-        kodi_db.addPlaystate(fileid, resume, total, playcount, dateplayed)
-        if not self.directpath and resume:
-            # Create additional entry for widgets. This is only required for plugin/episode.
-            temppathid = kodi_db.getPath("plugin://plugin.video.emby.tvshows/")
-            tempfileid = kodi_db.addFile(filename, temppathid)
-            query = ' '.join((
-
-                "UPDATE files",
-                "SET idPath = ?, strFilename = ?, dateAdded = ?",
-                "WHERE idFile = ?"
-            ))
-            kodicursor.execute(query, (temppathid, filename, dateadded, tempfileid))
-            kodi_db.addPlaystate(tempfileid, resume, total, playcount, dateplayed)
-
-    def updateUserdata(self, item):
-        # This updates: Favorite, LastPlayedDate, Playcount, PlaybackPositionTicks
-        # Poster with progress bar
-        emby_db = self.emby_db
-        kodi_db = self.kodi_db
-        API = api.API(item)
-        
-        # Get emby information
-        itemid = item['Id']
-        checksum = API.getChecksum()
-        userdata = API.getUserData()
-        runtime = API.getRuntime()
-        dateadded = API.getDateCreated()
-
-        # Get Kodi information
-        emby_dbitem = emby_db.getItem_byId(itemid)
-        try:
-            kodiid = emby_dbitem[0]
-            fileid = emby_dbitem[1]
-            mediatype = emby_dbitem[4]
-            self.logMsg(
-                "Update playstate for %s: %s fileid: %s"
-                % (mediatype, item['Name'], fileid), 1)
-        except TypeError:
-            return
-
-        # Process favorite tags
-        if mediatype == "tvshow":
-            if userdata['Favorite']:
-                kodi_db.addTag(kodiid, "Favorite tvshows", "tvshow")
-            else:
-                kodi_db.removeTag(kodiid, "Favorite tvshows", "tvshow")
-
-        # Process playstates
-        if mediatype == "episode":
-            playcount = userdata['PlayCount']
-            dateplayed = userdata['LastPlayedDate']
-            resume = API.adjustResume(userdata['Resume'])
-            total = round(float(runtime), 6)
-
-            self.logMsg("%s New resume point: %s" % (itemid, resume))
-
-            kodi_db.addPlaystate(fileid, resume, total, playcount, dateplayed)
-            if not self.directpath and not resume:
-                # Make sure there's no other bookmarks created by widget.
-                filename = kodi_db.getFile(fileid)
-                kodi_db.removeFile("plugin://plugin.video.emby.tvshows/", filename)
-
-            if not self.directpath and resume:
-                # Create additional entry for widgets. This is only required for plugin/episode.
-                filename = kodi_db.getFile(fileid)
-                temppathid = kodi_db.getPath("plugin://plugin.video.emby.tvshows/")
-                tempfileid = kodi_db.addFile(filename, temppathid)
-                query = ' '.join((
-
-                    "UPDATE files",
-                    "SET idPath = ?, strFilename = ?, dateAdded = ?",
-                    "WHERE idFile = ?"
-                ))
-                self.kodicursor.execute(query, (temppathid, filename, dateadded, tempfileid))
-                kodi_db.addPlaystate(tempfileid, resume, total, playcount, dateplayed)
-
-        emby_db.updateReference(itemid, checksum)
-
-    def remove(self, itemid):
-        # Remove showid, fileid, pathid, emby reference
-        emby_db = self.emby_db
-        embycursor = self.embycursor
-        kodicursor = self.kodicursor
-        artwork = self.artwork
-
-        emby_dbitem = emby_db.getItem_byId(itemid)
-        try:
-            kodiid = emby_dbitem[0]
-            fileid = emby_dbitem[1]
-            pathid = emby_dbitem[2]
-            parentid = emby_dbitem[3]
-            mediatype = emby_dbitem[4]
-            self.logMsg("Removing %s kodiid: %s fileid: %s" % (mediatype, kodiid, fileid), 1)
-        except TypeError:
-            return
-
-        ##### PROCESS ITEM #####
-
-        # Remove the emby reference
-        emby_db.removeItem(itemid)
-
-
-        ##### IF EPISODE #####
-
-        if mediatype == "episode":
-            # Delete kodi episode and file, verify season and tvshow
-            self.removeEpisode(kodiid, fileid)
-
-            # Season verification
-            season = emby_db.getItem_byKodiId(parentid, "season")
-            try:
-                showid = season[1]
-            except TypeError:
-                return
-            
-            season_episodes = emby_db.getItem_byParentId(parentid, "episode")
-            if not season_episodes:
-                self.removeSeason(parentid)
-                emby_db.removeItem(season[0])
-
-            # Show verification
-            show = emby_db.getItem_byKodiId(showid, "tvshow")
-            query = ' '.join((
-
-                "SELECT totalCount",
-                "FROM tvshowcounts",
-                "WHERE idShow = ?"
-            ))
-            kodicursor.execute(query, (showid,))
-            result = kodicursor.fetchone()
-            if result and result[0] is None:
-                # There's no episodes left, delete show and any possible remaining seasons
-                seasons = emby_db.getItem_byParentId(showid, "season")
-                for season in seasons:
-                    self.removeSeason(season[1])
-                else:
-                    # Delete emby season entries
-                    emby_db.removeItems_byParentId(showid, "season")
-                self.removeShow(showid)
-                emby_db.removeItem(show[0])
-
-        ##### IF TVSHOW #####
-
-        elif mediatype == "tvshow":
-            # Remove episodes, seasons, tvshow
-            seasons = emby_db.getItem_byParentId(kodiid, "season")
-            for season in seasons:
-                seasonid = season[1]
-                season_episodes = emby_db.getItem_byParentId(seasonid, "episode")
-                for episode in season_episodes:
-                    self.removeEpisode(episode[1], episode[2])
-                else:
-                    # Remove emby episodes
-                    emby_db.removeItems_byParentId(seasonid, "episode")
-            else:
-                # Remove emby seasons
-                emby_db.removeItems_byParentId(kodiid, "season")
-
-            # Remove tvshow
-            self.removeShow(kodiid)
-
-        ##### IF SEASON #####
-
-        elif mediatype == "season":
-            # Remove episodes, season, verify tvshow
-            season_episodes = emby_db.getItem_byParentId(kodiid, "episode")
-            for episode in season_episodes:
-                self.removeEpisode(episode[1], episode[2])
-            else:
-                # Remove emby episodes
-                emby_db.removeItems_byParentId(kodiid, "episode")
-            
-            # Remove season
-            self.removeSeason(kodiid)
-
-            # Show verification
-            seasons = emby_db.getItem_byParentId(parentid, "season")
-            if not seasons:
-                # There's no seasons, delete the show
-                self.removeShow(parentid)
-                emby_db.removeItem_byKodiId(parentid, "tvshow")
-
-        self.logMsg("Deleted %s: %s from kodi database" % (mediatype, itemid), 1)
-
-    def removeShow(self, kodiid):
-        
-        kodicursor = self.kodicursor
-        artwork = self.artwork
-
-        artwork.deleteArtwork(kodiid, "tvshow", kodicursor)
-        kodicursor.execute("DELETE FROM tvshow WHERE idShow = ?", (kodiid,))
-        self.logMsg("Removed tvshow: %s." % kodiid, 2)
-
-    def removeSeason(self, kodiid):
-        
-        kodicursor = self.kodicursor
-        artwork = self.artwork
-
-        artwork.deleteArtwork(kodiid, "season", kodicursor)
-        kodicursor.execute("DELETE FROM seasons WHERE idSeason = ?", (kodiid,))
-        self.logMsg("Removed season: %s." % kodiid, 2)
-
-    def removeEpisode(self, kodiid, fileid):
-
-        kodicursor = self.kodicursor
-        artwork = self.artwork
-
-        artwork.deleteArtwork(kodiid, "episode", kodicursor)
-        kodicursor.execute("DELETE FROM episode WHERE idEpisode = ?", (kodiid,))
-        kodicursor.execute("DELETE FROM files WHERE idFile = ?", (fileid,))
-        self.logMsg("Removed episode: %s." % kodiid, 2)
-
-class Music(Items):
-
-
-    def __init__(self, embycursor, musiccursor):
-        
-        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)
-
-    def added(self, items, pdialog):
-        
-        total = len(items)
-        count = 0
-        for artist in items:
-
-            title = artist['Name']
-            if pdialog:
-                percentage = int((float(count) / float(total))*100)
-                pdialog.update(percentage, message=title)
-                count += 1
-            self.add_updateArtist(artist)
-            # Add albums
-            all_albums = self.emby.getAlbumsbyArtist(artist['Id'])
-            self.added_album(all_albums['Items'], pdialog)
-
-    def added_album(self, items, pdialog):
-        
-        total = len(items)
-        count = 0
-        for album in items:
-
-            title = album['Name']
-            if pdialog:
-                percentage = int((float(count) / float(total))*100)
-                pdialog.update(percentage, message=title)
-                count += 1
-            self.add_updateAlbum(album)
-            # Add songs
-            all_songs = self.emby.getSongsbyAlbum(album['Id'])
-            self.added_song(all_songs['Items'], pdialog)
-
-    def added_song(self, items, pdialog):
-        
-        total = len(items)
-        count = 0
-        for song in items:
-
-            title = song['Name']
-            if pdialog:
-                percentage = int((float(count) / float(total))*100)
-                pdialog.update(percentage, message=title)
-                count += 1
-            self.add_updateSong(song)
-            if not pdialog and self.contentmsg:
-                self.contentPop(title, self.newmusic_time)
-
-    def add_updateArtist(self, item, artisttype="MusicArtist"):
-        # Process a single artist
-        kodiversion = self.kodiversion
-        kodicursor = self.kodicursor
-        emby_db = self.emby_db
-        kodi_db = self.kodi_db
-        artwork = self.artwork
-        API = api.API(item)
-
-        update_item = True
-        itemid = item['Id']
-        emby_dbitem = emby_db.getItem_byId(itemid)
-        try:
-            artistid = emby_dbitem[0]
-        except TypeError:
-            update_item = False
-            self.logMsg("artistid: %s not found." % itemid, 2)
-
-        ##### The artist details #####
-        lastScraped = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
-        dateadded = API.getDateCreated()
-        checksum = API.getChecksum()
-
-        name = item['Name']
-        musicBrainzId = API.getProvider('MusicBrainzArtist')
-        genres = " / ".join(item.get('Genres'))
-        bio = API.getOverview()
-
-        # Associate artwork
-        artworks = artwork.getAllArtwork(item, parentInfo=True)
-        thumb = artworks['Primary']
-        backdrops = artworks['Backdrop'] # List
-
-        if thumb:
-            thumb = "<thumb>%s</thumb>" % thumb
-        if backdrops:
-            fanart = "<fanart>%s</fanart>" % backdrops[0]
-        else:
-            fanart = ""
-
-
-        ##### UPDATE THE ARTIST #####
-        if update_item:
-            self.logMsg("UPDATE artist itemid: %s - Name: %s" % (itemid, name), 1)
-            # Update the checksum in emby table
-            emby_db.updateReference(itemid, checksum)
-
-        ##### OR ADD THE ARTIST #####
-        else:
-            self.logMsg("ADD artist itemid: %s - Name: %s" % (itemid, name), 1)
-            # safety checks: It looks like Emby supports the same artist multiple times.
-            # Kodi doesn't allow that. In case that happens we just merge the artist entries.
-            artistid = kodi_db.addArtist(name, musicBrainzId)
-            # Create the reference in emby table
-            emby_db.addReference(itemid, artistid, artisttype, "artist", checksum=checksum)
-            
-
-        # Process the artist
-        if self.kodiversion in (16, 17):
-            query = ' '.join((
-
-                "UPDATE artist",
-                "SET strGenres = ?, strBiography = ?, strImage = ?, strFanart = ?,",
-                    "lastScraped = ?",
-                "WHERE idArtist = ?"
-            ))
-            kodicursor.execute(query, (genres, bio, thumb, fanart, lastScraped, artistid))
-        else:
-            query = ' '.join((
-
-                "UPDATE artist",
-                "SET strGenres = ?, strBiography = ?, strImage = ?, strFanart = ?,",
-                    "lastScraped = ?, dateAdded = ?",
-                "WHERE idArtist = ?"
-            ))
-            kodicursor.execute(query, (genres, bio, thumb, fanart, lastScraped,
-                    dateadded, artistid))
-
-
-        # Update artwork
-        artwork.addArtwork(artworks, artistid, "artist", kodicursor)
-
-    def add_updateAlbum(self, item):
-        # Process a single artist
-        emby = self.emby
-        kodiversion = self.kodiversion
-        kodicursor = self.kodicursor
-        emby_db = self.emby_db
-        kodi_db = self.kodi_db
-        artwork = self.artwork
-        API = api.API(item)
-
-        update_item = True
-        itemid = item['Id']
-        emby_dbitem = emby_db.getItem_byId(itemid)
-        try:
-            albumid = emby_dbitem[0]
-        except TypeError:
-            update_item = False
-            self.logMsg("albumid: %s not found." % itemid, 2)
-
-        ##### The album details #####
-        lastScraped = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
-        dateadded = API.getDateCreated()
-        userdata = API.getUserData()
-        checksum = API.getChecksum()
-
-        name = item['Name']
-        musicBrainzId = API.getProvider('MusicBrainzAlbum')
-        year = item.get('ProductionYear')
-        genres = item.get('Genres')
-        genre = " / ".join(genres)
-        bio = API.getOverview()
-        rating = userdata['UserRating']
-        artists = item['AlbumArtists']
-        if not artists:
-            artists = item['ArtistItems']
-        artistname = []
-        for artist in artists:
-            artistname.append(artist['Name'])
-        artistname = " / ".join(artistname)
-
-        # Associate artwork
-        artworks = artwork.getAllArtwork(item, parentInfo=True)
-        thumb = artworks['Primary']
-        if thumb:
-            thumb = "<thumb>%s</thumb>" % thumb
-
-        ##### UPDATE THE ALBUM #####
-        if update_item:
-            self.logMsg("UPDATE album itemid: %s - Name: %s" % (itemid, name), 1)
-            # Update the checksum in emby table
-            emby_db.updateReference(itemid, checksum)
-
-        ##### OR ADD THE ALBUM #####
-        else:
-            self.logMsg("ADD album itemid: %s - Name: %s" % (itemid, name), 1)
-            # safety checks: It looks like Emby supports the same artist multiple times.
-            # Kodi doesn't allow that. In case that happens we just merge the artist entries.
-            albumid = kodi_db.addAlbum(name, musicBrainzId)
-            # Create the reference in emby table
-            emby_db.addReference(itemid, albumid, "MusicAlbum", "album", checksum=checksum)
-
-
-        # Process the album info
-        if kodiversion == 17:
-            # Kodi Krypton
-            query = ' '.join((
-
-                "UPDATE album",
-                "SET strArtists = ?, iYear = ?, strGenres = ?, strReview = ?, strImage = ?,",
-                    "iUserrating = ?, lastScraped = ?, strReleaseType = ?",
-                "WHERE idAlbum = ?"
-            ))
-            kodicursor.execute(query, (artistname, year, genre, bio, thumb, rating, lastScraped,
-                "album", albumid))
-        elif kodiversion == 16:
-            # Kodi Jarvis
-            query = ' '.join((
-
-                "UPDATE album",
-                "SET strArtists = ?, iYear = ?, strGenres = ?, strReview = ?, strImage = ?,",
-                    "iRating = ?, lastScraped = ?, strReleaseType = ?",
-                "WHERE idAlbum = ?"
-            ))
-            kodicursor.execute(query, (artistname, year, genre, bio, thumb, rating, lastScraped,
-                "album", albumid))
-        elif kodiversion == 15:
-            # Kodi Isengard
-            query = ' '.join((
-
-                "UPDATE album",
-                "SET strArtists = ?, iYear = ?, strGenres = ?, strReview = ?, strImage = ?,",
-                    "iRating = ?, lastScraped = ?, dateAdded = ?, strReleaseType = ?",
-                "WHERE idAlbum = ?"
-            ))
-            kodicursor.execute(query, (artistname, year, genre, bio, thumb, rating, lastScraped,
-                dateadded, "album", albumid))
-        else:
-            # Kodi Helix
-            query = ' '.join((
-
-                "UPDATE album",
-                "SET strArtists = ?, iYear = ?, strGenres = ?, strReview = ?, strImage = ?,",
-                    "iRating = ?, lastScraped = ?, dateAdded = ?",
-                "WHERE idAlbum = ?"
-            ))
-            kodicursor.execute(query, (artistname, year, genre, bio, thumb, rating, lastScraped,
-                dateadded, albumid))
-
-        # Associate the parentid for emby reference
-        parentId = item.get('ParentId')
-        if parentId is not None:
-            emby_dbartist = emby_db.getItem_byId(parentId)
-            try:
-                artistid = emby_dbartist[0]
-            except TypeError:
-                # Artist does not exist in emby database.
-                artist = emby.getItem(parentId)
-                # Item may not be an artist, verification necessary.
-                if artist['Type'] == "MusicArtist":
-                    # Update with the parentId, for remove reference
-                    emby_db.addReference(parentId, parentId, "MusicArtist", "artist")
-                    emby_db.updateParentId(itemid, parentId)
-            else:
-                # Update emby reference with the artistid
-                emby_db.updateParentId(itemid, artistid)
-
-        # Assign main artists to album
-        for artist in artists:
-            artistname = artist['Name']
-            artistId = artist['Id']
-            emby_dbartist = emby_db.getItem_byId(artistId)
-            try:
-                artistid = emby_dbartist[0]
-            except TypeError:
-                # Artist does not exist in emby database, create the reference
-                artist = emby.getItem(artistId)
-                self.add_updateArtist(artist, artisttype="AlbumArtist")
-                emby_dbartist = emby_db.getItem_byId(artistId)
-                artistid = emby_dbartist[0]
-            else:
-                # Best take this name over anything else.
-                query = "UPDATE artist SET strArtist = ? WHERE idArtist = ?"
-                kodicursor.execute(query, (artistname, artistid,))
-
-            # Add artist to album
-            query = (
-                '''
-                INSERT OR REPLACE INTO album_artist(idArtist, idAlbum, strArtist)
-
-                VALUES (?, ?, ?)
-                '''
-            )
-            kodicursor.execute(query, (artistid, albumid, artistname))
-            # Update discography
-            query = (
-                '''
-                INSERT OR REPLACE INTO discography(idArtist, strAlbum, strYear)
-
-                VALUES (?, ?, ?)
-                '''
-            )
-            kodicursor.execute(query, (artistid, name, year))
-            # Update emby reference with parentid
-            emby_db.updateParentId(artistId, albumid)
-
-        # Add genres
-        kodi_db.addMusicGenres(albumid, genres, "album")
-        # Update artwork
-        artwork.addArtwork(artworks, albumid, "album", kodicursor)
-
-    def add_updateSong(self, item):
-        # Process single song
-        kodiversion = self.kodiversion
-        kodicursor = self.kodicursor
-        emby = self.emby
-        emby_db = self.emby_db
-        kodi_db = self.kodi_db
-        artwork = self.artwork
-        API = api.API(item)
-
-        update_item = True
-        itemid = item['Id']
-        emby_dbitem = emby_db.getItem_byId(itemid)
-        try:
-            songid = emby_dbitem[0]
-            pathid = emby_dbitem[2]
-            albumid = emby_dbitem[3]
-        except TypeError:
-            update_item = False
-            self.logMsg("songid: %s not found." % itemid, 2)
-            
-        ##### The song details #####
-        checksum = API.getChecksum()
-        dateadded = API.getDateCreated()
-        userdata = API.getUserData()
-        playcount = userdata['PlayCount']
-        dateplayed = userdata['LastPlayedDate']
-
-        # item details
-        title = item['Name']
-        musicBrainzId = API.getProvider('MusicBrainzTrackId')
-        genres = item.get('Genres')
-        genre = " / ".join(genres)
-        artists = " / ".join(item['Artists'])
-        tracknumber = item.get('IndexNumber', 0)
-        disc = item.get('ParentIndexNumber', 1)
-        if disc == 1:
-            track = tracknumber
-        else:
-            track = disc*2**16 + tracknumber
-        year = item.get('ProductionYear')
-        duration = API.getRuntime()
-        rating = userdata['UserRating']
-
-        #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()
-            
-            
-        ##### GET THE FILE AND PATH #####
-        if self.directstream:
-            path = "%s/emby/Audio/%s/" % (self.server, itemid)
-            filename = "stream.mp3"
-        else:
-            playurl = API.getFilePath()
-
-            if "\\" in playurl:
-                # Local path
-                filename = playurl.rsplit("\\", 1)[1]
-            else: # Network share
-                filename = playurl.rsplit("/", 1)[1]
-
-            # Direct paths is set the Kodi way
-            if utils.window('emby_pathverified') != "true" and not xbmcvfs.exists(playurl):
-                # Validate the path is correct with user intervention
-                utils.window('emby_directPath', clear=True)
-                resp = xbmcgui.Dialog().yesno(
-                                        heading="Can't validate path",
-                                        line1=(
-                                            "Kodi can't locate file: %s. Verify the path. "
-                                            "You may to verify your network credentials in the "
-                                            "add-on settings or use the emby path substitution "
-                                            "to format your path correctly. Stop syncing?"
-                                            % playurl))
-                if resp:
-                    utils.window('emby_shouldStop', value="true")
-                    return False
-            
-            path = playurl.replace(filename, "")
-            utils.window('emby_pathverified', value="true")
-
-        ##### UPDATE THE SONG #####
-        if update_item:
-            self.logMsg("UPDATE song itemid: %s - Title: %s" % (itemid, title), 1)
-            
-            # Update path
-            query = "UPDATE path SET strPath = ? WHERE idPath = ?"
-            kodicursor.execute(query, (path, pathid))
-
-            # Update the song entry
-            query = ' '.join((
-                
-                "UPDATE song",
-                "SET idAlbum = ?, strArtists = ?, strGenres = ?, strTitle = ?, iTrack = ?,",
-                    "iDuration = ?, iYear = ?, strFilename = ?, iTimesPlayed = ?, lastplayed = ?,",
-                    "rating = ?, comment = ?",
-                "WHERE idSong = ?"
-            ))
-            kodicursor.execute(query, (albumid, artists, genre, title, track, duration, year,
-                filename, playcount, dateplayed, rating, comment, songid))
-
-            # Update the checksum in emby table
-            emby_db.updateReference(itemid, checksum)
-        
-        ##### OR ADD THE SONG #####
-        else:
-            self.logMsg("ADD song itemid: %s - Title: %s" % (itemid, title), 1)
-            
-            # Add path
-            pathid = kodi_db.addPath(path)
-
-            try:
-                # Get the album
-                emby_dbalbum = emby_db.getItem_byId(item['AlbumId'])
-                albumid = emby_dbalbum[0]
-            except KeyError:
-                # Verify if there's an album associated.
-                album_name = item.get('Album')
-                if album_name:
-                    self.logMsg("Creating virtual music album for song: %s." % itemid, 1)
-                    albumid = kodi_db.addAlbum(album_name, API.getProvider('MusicBrainzAlbum'))
-                    emby_db.addReference("%salbum%s" % (itemid, albumid), albumid, "MusicAlbum_", "album")
-                else:
-                    # No album Id associated to the song.
-                    self.logMsg("Song itemid: %s has no albumId associated." % itemid, 1)
-                    return False
-
-            except TypeError:
-                # No album found. Let's create it
-                self.logMsg("Album database entry missing.", 1)
-                emby_albumId = item['AlbumId']
-                album = emby.getItem(emby_albumId)
-                self.add_updateAlbum(album)
-                emby_dbalbum = emby_db.getItem_byId(emby_albumId)
-                try:
-                    albumid = emby_dbalbum[0]
-                    self.logMsg("Found albumid: %s" % albumid, 1)
-                except TypeError:
-                    # No album found, create a single's album
-                    self.logMsg("Failed to add album. Creating singles.", 1)
-                    kodicursor.execute("select coalesce(max(idAlbum),0) from album")
-                    albumid = kodicursor.fetchone()[0] + 1
-                    if kodiversion == 16:
-                        # Kodi Jarvis
-                        query = (
-                            '''
-                            INSERT INTO album(idAlbum, strGenres, iYear, strReleaseType)
-
-                            VALUES (?, ?, ?, ?)
-                            '''
-                        )
-                        kodicursor.execute(query, (albumid, genre, year, "single"))
-                    elif kodiversion == 15:
-                        # Kodi Isengard
-                        query = (
-                            '''
-                            INSERT INTO album(idAlbum, strGenres, iYear, dateAdded, strReleaseType)
-
-                            VALUES (?, ?, ?, ?, ?)
-                            '''
-                        )
-                        kodicursor.execute(query, (albumid, genre, year, dateadded, "single"))
-                    else:
-                        # Kodi Helix
-                        query = (
-                            '''
-                            INSERT INTO album(idAlbum, strGenres, iYear, dateAdded)
-
-                            VALUES (?, ?, ?, ?)
-                            '''
-                        )
-                        kodicursor.execute(query, (albumid, genre, year, dateadded))
-            
-            # Create the song entry
-            kodicursor.execute("select coalesce(max(idSong),0) from song")
-            songid = kodicursor.fetchone()[0] + 1
-            query = (
-                '''
-                INSERT INTO song(
-                    idSong, idAlbum, idPath, strArtists, strGenres, strTitle, iTrack,
-                    iDuration, iYear, strFileName, strMusicBrainzTrackID, iTimesPlayed, lastplayed,
-                    rating)
-
-                VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
-                '''
-            )
-            kodicursor.execute(query, (songid, albumid, pathid, artists, genre, title, track,
-                duration, year, filename, musicBrainzId, playcount, dateplayed, rating))
-
-            # Create the reference in emby table
-            emby_db.addReference(itemid, songid, "Audio", "song", pathid=pathid, parentid=albumid,
-                checksum=checksum)
-
-        
-        # Link song to album
-        query = (
-            '''
-            INSERT OR REPLACE INTO albuminfosong(
-                idAlbumInfoSong, idAlbumInfo, iTrack, strTitle, iDuration)
-            
-            VALUES (?, ?, ?, ?, ?)
-            '''
-        )
-        kodicursor.execute(query, (songid, albumid, track, title, duration))
-        
-        # Link song to artists
-        for index, artist in enumerate(item['ArtistItems']):
-
-            artist_name = artist['Name']
-            artist_eid = artist['Id']
-            artist_edb = emby_db.getItem_byId(artist_eid)
-            try:
-                artistid = artist_edb[0]
-            except TypeError:
-                # Artist is missing from emby database, add it.
-                artist_full = emby.getItem(artist_eid)
-                self.add_updateArtist(artist_full)
-                artist_edb = emby_db.getItem_byId(artist_eid)
-                artistid = artist_edb[0]
-            finally:
-                query = (
-                    '''
-                    INSERT OR REPLACE INTO song_artist(idArtist, idSong, iOrder, strArtist)
-
-                    VALUES (?, ?, ?, ?)
-                    '''
-                )
-                kodicursor.execute(query, (artistid, songid, index, artist_name))
-
-        # Verify if album artist exists
-        album_artists = []
-        for artist in item['AlbumArtists']:
-
-            artist_name = artist['Name']
-            album_artists.append(artist_name)
-            artist_eid = artist['Id']
-            artist_edb = emby_db.getItem_byId(artist_eid)
-            try:
-                artistid = artist_edb[0]
-            except TypeError:
-                # Artist is missing from emby database, add it.
-                artist_full = emby.getItem(artist_eid)
-                self.add_updateArtist(artist_full)
-                artist_edb = emby_db.getItem_byId(artist_eid)
-                artistid = artist_edb[0]
-            finally:
-                query = (
-                    '''
-                    INSERT OR REPLACE INTO album_artist(idArtist, idAlbum, strArtist)
-
-                    VALUES (?, ?, ?)
-                    '''
-                )
-                kodicursor.execute(query, (artistid, albumid, artist_name))
-                # Update discography
-                if item.get('Album'):
-                    query = (
-                        '''
-                        INSERT OR REPLACE INTO discography(idArtist, strAlbum, strYear)
-
-                        VALUES (?, ?, ?)
-                        '''
-                    )
-                    kodicursor.execute(query, (artistid, item['Album'], 0))
-        else:
-            album_artists = " / ".join(album_artists)
-            query = ' '.join((
-
-                "SELECT strArtists",
-                "FROM album",
-                "WHERE idAlbum = ?"
-            ))
-            kodicursor.execute(query, (albumid,))
-            result = kodicursor.fetchone()
-            if result and result[0] != album_artists:
-                # Field is empty
-                if kodiversion in (16, 17):
-                    # Kodi Jarvis, Krypton
-                    query = "UPDATE album SET strArtists = ? WHERE idAlbum = ?"
-                    kodicursor.execute(query, (album_artists, albumid))
-                elif kodiversion == 15:
-                    # Kodi Isengard
-                    query = "UPDATE album SET strArtists = ? WHERE idAlbum = ?"
-                    kodicursor.execute(query, (album_artists, albumid))
-                else:
-                    # Kodi Helix
-                    query = "UPDATE album SET strArtists = ? WHERE idAlbum = ?"
-                    kodicursor.execute(query, (album_artists, albumid))
-
-        # Add genres
-        kodi_db.addMusicGenres(songid, genres, "song")
-        
-        # Update artwork
-        allart = artwork.getAllArtwork(item, parentInfo=True)
-        if hasEmbeddedCover:
-            allart["Primary"] = "image://music@" + artwork.single_urlencode( playurl )
-        artwork.addArtwork(allart, songid, "song", kodicursor)
-
-        if item.get('AlbumId') is None:
-            # Update album artwork
-            artwork.addArtwork(allart, albumid, "album", kodicursor)
-
-    def updateUserdata(self, item):
-        # This updates: Favorite, LastPlayedDate, Playcount, PlaybackPositionTicks
-        # Poster with progress bar
-        kodicursor = self.kodicursor
-        emby_db = self.emby_db
-        kodi_db = self.kodi_db
-        API = api.API(item)
-
-        # Get emby information
-        itemid = item['Id']
-        checksum = API.getChecksum()
-        userdata = API.getUserData()
-        runtime = API.getRuntime()
-        rating = userdata['UserRating']
-
-        # Get Kodi information
-        emby_dbitem = emby_db.getItem_byId(itemid)
-        try:
-            kodiid = emby_dbitem[0]
-            mediatype = emby_dbitem[4]
-            self.logMsg("Update playstate for %s: %s" % (mediatype, item['Name']), 1)
-        except TypeError:
-            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 = ?"
-            kodicursor.execute(query, (playcount, dateplayed, rating, kodiid))
-
-        elif mediatype == "album":
-            # Process playstates
-            query = "UPDATE album SET iRating = ? WHERE idAlbum = ?"
-            kodicursor.execute(query, (rating, kodiid))
-
-        emby_db.updateReference(itemid, checksum)
-
-    def remove(self, itemid):
-        # Remove kodiid, fileid, pathid, emby reference
-        emby_db = self.emby_db
-        kodicursor = self.kodicursor
-        artwork = self.artwork
-
-        emby_dbitem = emby_db.getItem_byId(itemid)
-        try:
-            kodiid = emby_dbitem[0]
-            mediatype = emby_dbitem[4]
-            self.logMsg("Removing %s kodiid: %s" % (mediatype, kodiid), 1)
-        except TypeError:
-            return
-
-        ##### PROCESS ITEM #####
-
-        # Remove the emby reference
-        emby_db.removeItem(itemid)
-
-
-        ##### IF SONG #####
-
-        if mediatype == "song":
-            # Delete song
-            self.removeSong(kodiid)
-            # This should only address single song scenario, where server doesn't actually
-            # create an album for the song. 
-            customitems = emby_db.getItem_byWildId(itemid)
-            emby_db.removeWildItem(itemid)
-
-            for item in customitems:
-
-                item_kid = item[0]
-                item_mediatype = item[1]
-
-                if item_mediatype == "album":
-                    childs = emby_db.getItem_byParentId(item_kid, "song")
-                    if not childs:
-                        # Delete album
-                        self.removeAlbum(item_kid)
-
-        ##### IF ALBUM #####
-
-        elif mediatype == "album":
-            # Delete songs, album
-            album_songs = emby_db.getItem_byParentId(kodiid, "song")
-            for song in album_songs:
-                self.removeSong(song[1])
-            else:
-                # Remove emby songs
-                emby_db.removeItems_byParentId(kodiid, "song")
-
-            # Remove the album
-            self.removeAlbum(kodiid)
-
-        ##### IF ARTIST #####
-
-        elif mediatype == "artist":
-            # Delete songs, album, artist
-            albums = emby_db.getItem_byParentId(kodiid, "album")
-            for album in albums:
-                albumid = album[1]
-                album_songs = emby_db.getItem_byParentId(albumid, "song")
-                for song in album_songs:
-                    self.removeSong(song[1])
-                else:
-                    # Remove emby song
-                    emby_db.removeItems_byParentId(albumid, "song")
-                    # Remove emby artist
-                    emby_db.removeItems_byParentId(albumid, "artist")
-                    # Remove kodi album
-                    self.removeAlbum(albumid)
-            else:
-                # Remove emby albums
-                emby_db.removeItems_byParentId(kodiid, "album")
-
-            # Remove artist
-            self.removeArtist(kodiid)
-
-        self.logMsg("Deleted %s: %s from kodi database" % (mediatype, itemid), 1)
-
-    def removeSong(self, kodiid):
-
-        kodicursor = self.kodicursor
-        artwork = self.artwork
-
-        artwork.deleteArtwork(kodiid, "song", kodicursor)
-        kodicursor.execute("DELETE FROM song WHERE idSong = ?", (kodiid,))
-
-    def removeAlbum(self, kodiid):
-
-        kodicursor = self.kodicursor
-        artwork = self.artwork
-
-        artwork.deleteArtwork(kodiid, "album", kodicursor)
-        kodicursor.execute("DELETE FROM album WHERE idAlbum = ?", (kodiid,))
-
-    def removeArtist(self, kodiid):
-
-        kodicursor = self.kodicursor
-        artwork = self.artwork
-
-        artwork.deleteArtwork(kodiid, "artist", kodicursor)
-        kodicursor.execute("DELETE FROM artist WHERE idArtist = ?", (kodiid,))
\ No newline at end of file
+# -*- coding: utf-8 -*-
+
+##################################################################################################
+
+import urllib
+from ntpath import dirname
+from datetime import datetime
+
+import xbmc
+import xbmcgui
+import xbmcvfs
+
+import api
+import artwork
+import clientinfo
+import downloadutils
+import utils
+import embydb_functions as embydb
+import kodidb_functions as kodidb
+import read_embyserver as embyserver
+import musicutils
+
+##################################################################################################
+
+
+class Items(object):
+
+
+    def __init__(self, embycursor, kodicursor):
+
+        self.embycursor = embycursor
+        self.kodicursor = kodicursor
+
+        self.clientInfo = clientinfo.ClientInfo()
+        self.addonName = self.clientInfo.getAddonName()
+        self.doUtils = downloadutils.DownloadUtils()
+
+        self.kodiversion = int(xbmc.getInfoLabel("System.BuildVersion")[:2])
+        self.directpath = utils.settings('useDirectPaths') == "1"
+        self.music_enabled = utils.settings('enableMusic') == "true"
+        self.contentmsg = utils.settings('newContent') == "true"
+        self.newvideo_time = int(utils.settings('newvideotime'))*1000
+        self.newmusic_time = int(utils.settings('newmusictime'))*1000
+
+        self.artwork = artwork.Artwork()
+        self.emby = embyserver.Read_EmbyServer()
+        self.emby_db = embydb.Embydb_Functions(embycursor)
+        self.kodi_db = kodidb.Kodidb_Functions(kodicursor)
+
+    def logMsg(self, msg, lvl=1):
+
+        className = self.__class__.__name__
+        utils.logMsg("%s %s" % (self.addonName, className), msg, lvl)
+
+
+    def itemsbyId(self, items, process, pdialog=None):
+        # Process items by itemid. Process can be added, update, userdata, remove
+        emby = self.emby
+        embycursor = self.embycursor
+        kodicursor = self.kodicursor
+        music_enabled = self.music_enabled
+        
+        itemtypes = {
+
+            'Movie': Movies,
+            'BoxSet': Movies,
+            'Series': TVShows,
+            'Season': TVShows,
+            'Episode': TVShows,
+            'MusicAlbum': Music,
+            'MusicArtist': Music,
+            'AlbumArtist': Music,
+            'Audio': Music
+        }
+
+        update_videolibrary = False
+        total = 0
+        for item in items:
+            total += len(items[item])
+
+        if total == 0:
+            return False
+
+        self.logMsg("Processing %s: %s" % (process, items), 1)
+        if pdialog:
+            pdialog.update(heading="Processing %s: %s items" % (process, total))
+
+        count = 0
+        for itemtype in items:
+
+            # Safety check
+            if not itemtypes.get(itemtype):
+                # We don't process this type of item
+                continue
+
+            itemlist = items[itemtype]
+            if not itemlist:
+                # The list to process is empty
+                continue
+
+            musicconn = None
+
+            if itemtype in ('MusicAlbum', 'MusicArtist', 'AlbumArtist', 'Audio'):
+                if music_enabled:
+                    musicconn = utils.kodiSQL('music')
+                    musiccursor = musicconn.cursor()
+                    items_process = itemtypes[itemtype](embycursor, musiccursor)
+                else:
+                    # Music is not enabled, do not proceed with itemtype
+                    continue
+            else:
+                update_videolibrary = True
+                items_process = itemtypes[itemtype](embycursor, kodicursor)
+
+            if itemtype == "Movie":
+                actions = {
+                    'added': items_process.added,
+                    'update': items_process.add_update,
+                    'userdata': items_process.updateUserdata,
+                    'remove': items_process.remove
+                }
+            elif itemtype == "BoxSet":
+                actions = {
+                    'added': items_process.added_boxset,
+                    'update': items_process.add_updateBoxset,
+                    'remove': items_process.remove
+                }
+            elif itemtype == "MusicVideo":
+                actions = {
+                    'added': items_process.added,
+                    'update': items_process.add_update,
+                    'userdata': items_process.updateUserdata,
+                    'remove': items_process.remove
+                }
+            elif itemtype == "Series":
+                actions = {
+                    'added': items_process.added,
+                    'update': items_process.add_update,
+                    'userdata': items_process.updateUserdata,
+                    'remove': items_process.remove
+                }
+            elif itemtype == "Season":
+                actions = {
+                    'added': items_process.added_season,
+                    'update': items_process.add_updateSeason,
+                    'remove': items_process.remove
+                }
+            elif itemtype == "Episode":
+                actions = {
+                    'added': items_process.added_episode,
+                    'update': items_process.add_updateEpisode,
+                    'userdata': items_process.updateUserdata,
+                    'remove': items_process.remove
+                }
+            elif itemtype == "MusicAlbum":
+                actions = {
+                    'added': items_process.added_album,
+                    'update': items_process.add_updateAlbum,
+                    'userdata': items_process.updateUserdata,
+                    'remove': items_process.remove
+                }
+            elif itemtype in ("MusicArtist", "AlbumArtist"):
+                actions = {
+                    'added': items_process.added,
+                    'update': items_process.add_updateArtist,
+                    'remove': items_process.remove
+                }
+            elif itemtype == "Audio":
+                actions = {
+                    'added': items_process.added_song,
+                    'update': items_process.add_updateSong,
+                    'userdata': items_process.updateUserdata,
+                    'remove': items_process.remove
+                }
+            else:
+                self.logMsg("Unsupported itemtype: %s." % itemtype, 1)
+                actions = {}
+
+            if actions.get(process):
+
+                if process == "remove":
+                    for item in itemlist:
+                        actions[process](item)
+
+                elif process == "added":
+                    actions[process](itemlist, pdialog)
+            
+                else:
+                    processItems = emby.getFullItems(itemlist)
+                    for item in processItems:
+
+                        title = item['Name']
+
+                        if itemtype == "Episode":
+                            title = "%s - %s" % (item['SeriesName'], title)
+
+                        if pdialog:
+                            percentage = int((float(count) / float(total))*100)
+                            pdialog.update(percentage, message=title)
+                            count += 1
+
+                        actions[process](item)
+
+
+            if musicconn is not None:
+                # close connection for special types
+                self.logMsg("Updating music database.", 1)
+                musicconn.commit()
+                musiccursor.close()
+
+        return (True, update_videolibrary)
+
+    def contentPop(self, name, time=5000):
+        
+        if time: 
+            # It's possible for the time to be 0. It should be considered disabled in this case.
+            xbmcgui.Dialog().notification(
+                    heading="Emby for Kodi",
+                    message="Added: %s" % name,
+                    icon="special://home/addons/plugin.video.emby/icon.png",
+                    time=time,
+                    sound=False)
+
+
+class Movies(Items):
+
+    
+    def __init__(self, embycursor, kodicursor):
+        Items.__init__(self, embycursor, kodicursor)
+
+    def added(self, items, pdialog):
+
+        total = len(items)
+        count = 0
+        for movie in items:
+                
+            title = movie['Name']
+            if pdialog:
+                percentage = int((float(count) / float(total))*100)
+                pdialog.update(percentage, message=title)
+                count += 1
+            self.add_update(movie)
+            if not pdialog and self.contentmsg:
+                self.contentPop(title, self.newvideo_time)
+
+    def added_boxset(self, items, pdialog):
+
+        total = len(items)
+        count = 0
+        for boxset in items:
+
+            if pdialog:
+                percentage = int((float(count) / float(total))*100)
+                pdialog.update(percentage, message=boxset['Name'])
+                count += 1
+            self.add_updateBoxset(boxset)
+
+
+    def add_update(self, item, viewtag=None, viewid=None):
+        # Process single movie
+        kodicursor = self.kodicursor
+        emby_db = self.emby_db
+        artwork = self.artwork
+        API = api.API(item)
+
+        # If the item already exist in the local Kodi DB we'll perform a full item update
+        # If the item doesn't exist, we'll add it to the database
+        update_item = True
+        itemid = item['Id']
+        emby_dbitem = emby_db.getItem_byId(itemid)
+        try:
+            movieid = emby_dbitem[0]
+            fileid = emby_dbitem[1]
+            pathid = emby_dbitem[2]
+            self.logMsg("movieid: %s fileid: %s pathid: %s" % (movieid, fileid, pathid), 1)
+        
+        except TypeError:
+            update_item = False
+            self.logMsg("movieid: %s not found." % itemid, 2)
+            # movieid
+            kodicursor.execute("select coalesce(max(idMovie),0) from movie")
+            movieid = kodicursor.fetchone()[0] + 1
+
+        else:
+            # Verification the item is still in Kodi
+            query = "SELECT * FROM movie WHERE idMovie = ?"
+            kodicursor.execute(query, (movieid,))
+            try:
+                kodicursor.fetchone()[0]
+            except TypeError:
+                # item is not found, let's recreate it.
+                update_item = False
+                self.logMsg("movieid: %s missing from Kodi, repairing the entry." % movieid, 1)
+
+        if not viewtag or not viewid:
+            # Get view tag from emby
+            viewtag, viewid, mediatype = self.emby.getView_embyId(itemid)
+            self.logMsg("View tag found: %s" % viewtag, 2)
+
+        # fileId information
+        checksum = API.getChecksum()
+        dateadded = API.getDateCreated()
+        userdata = API.getUserData()
+        playcount = userdata['PlayCount']
+        dateplayed = userdata['LastPlayedDate']
+
+        # item details
+        people = API.getPeople()
+        writer = " / ".join(people['Writer'])
+        director = " / ".join(people['Director'])
+        genres = item['Genres']
+        title = item['Name']
+        plot = API.getOverview()
+        shortplot = item.get('ShortOverview')
+        tagline = API.getTagline()
+        votecount = item.get('VoteCount')
+        rating = item.get('CommunityRating')
+        year = item.get('ProductionYear')
+        imdb = API.getProvider('Imdb')
+        sorttitle = item['SortName']
+        runtime = API.getRuntime()
+        mpaa = API.getMpaa()
+        genre = " / ".join(genres)
+        country = API.getCountry()
+        studios = API.getStudios()
+        try:
+            studio = studios[0]
+        except IndexError:
+            studio = None
+
+        if item.get('LocalTrailerCount'):
+            # There's a local trailer
+            url = (
+                "{server}/emby/Users/{UserId}/Items/%s/LocalTrailers?format=json"
+                % itemid
+            )
+            result = self.doUtils.downloadUrl(url)
+            try:
+                trailer = "plugin://plugin.video.emby/trailer/?id=%s&mode=play" % result[0]['Id']
+            except IndexError:
+                self.logMsg("Failed to process local trailer.", 1)
+                trailer = None
+        else:
+            # Try to get the youtube trailer
+            try:
+                trailer = item['RemoteTrailers'][0]['Url']
+            except (KeyError, IndexError):
+                trailer = None
+            else:    
+                try:
+                    trailerId = trailer.rsplit('=', 1)[1]
+                except IndexError:
+                    self.logMsg("Failed to process trailer: %s" % trailer, 1)
+                    trailer = None
+                else:
+                    trailer = "plugin://plugin.video.youtube/play/?video_id=%s" % trailerId
+
+        
+        ##### GET THE FILE AND PATH #####
+        playurl = API.getFilePath()
+
+        if "\\" in playurl:
+            # Local path
+            filename = playurl.rsplit("\\", 1)[1]
+        else: # Network share
+            filename = playurl.rsplit("/", 1)[1]
+
+        if self.directpath:
+            # Direct paths is set the Kodi way
+            if utils.window('emby_pathverified') != "true" and not xbmcvfs.exists(playurl):
+                # Validate the path is correct with user intervention
+                resp = xbmcgui.Dialog().yesno(
+                                        heading="Can't validate path",
+                                        line1=(
+                                            "Kodi can't locate file: %s. Verify the path. "
+                                            "You may to verify your network credentials in the "
+                                            "add-on settings or use the emby path substitution "
+                                            "to format your path correctly. Stop syncing?"
+                                            % playurl))
+                if resp:
+                    utils.window('emby_shouldStop', value="true")
+                    return False
+            
+            path = playurl.replace(filename, "")
+            utils.window('emby_pathverified', value="true")
+        else:
+            # Set plugin path and media flags using real filename
+            path = "plugin://plugin.video.emby.movies/"
+            params = {
+
+                'filename': filename.encode('utf-8'),
+                'id': itemid,
+                'dbid': movieid,
+                'mode': "play"
+            }
+            filename = "%s?%s" % (path, urllib.urlencode(params))
+
+
+        ##### UPDATE THE MOVIE #####
+        if update_item:
+            self.logMsg("UPDATE movie itemid: %s - Title: %s" % (itemid, title), 1)
+
+            # Update the movie entry
+            query = ' '.join((
+                
+                "UPDATE movie",
+                "SET c00 = ?, c01 = ?, c02 = ?, c03 = ?, c04 = ?, c05 = ?, c06 = ?,",
+                    "c07 = ?, c09 = ?, c10 = ?, c11 = ?, c12 = ?, c14 = ?, c15 = ?,",
+                    "c16 = ?, c18 = ?, c19 = ?, c21 = ?",
+                "WHERE idMovie = ?"
+            ))
+            kodicursor.execute(query, (title, plot, shortplot, tagline, votecount, rating, writer,
+                year, imdb, sorttitle, runtime, mpaa, genre, director, title, studio, trailer,
+                country, movieid))
+
+            # Update the checksum in emby table
+            emby_db.updateReference(itemid, checksum)
+        
+        ##### OR ADD THE MOVIE #####
+        else:
+            self.logMsg("ADD movie itemid: %s - Title: %s" % (itemid, title), 1)
+            
+            # Add path
+            pathid = self.kodi_db.addPath(path)
+            # Add the file
+            fileid = self.kodi_db.addFile(filename, pathid)
+            
+            # Create the movie entry
+            query = (
+                '''
+                INSERT INTO movie(
+                    idMovie, idFile, c00, c01, c02, c03, c04, c05, c06, c07, 
+                    c09, c10, c11, c12, c14, c15, c16, c18, c19, c21)
+
+                VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
+                '''
+            )
+            kodicursor.execute(query, (movieid, fileid, title, plot, shortplot, tagline, votecount,
+                rating, writer, year, imdb, sorttitle, runtime, mpaa, genre, director, title,
+                studio, trailer, country))
+
+            # Create the reference in emby table
+            emby_db.addReference(itemid, movieid, "Movie", "movie", fileid, pathid, None, checksum, viewid)
+
+        # Update the path
+        query = ' '.join((
+
+            "UPDATE path",
+            "SET strPath = ?, strContent = ?, strScraper = ?, noUpdate = ?",
+            "WHERE idPath = ?"
+        ))
+        kodicursor.execute(query, (path, "movies", "metadata.local", 1, pathid))
+
+        # Update the file
+        query = ' '.join((
+
+            "UPDATE files",
+            "SET idPath = ?, strFilename = ?, dateAdded = ?",
+            "WHERE idFile = ?"
+        ))
+        kodicursor.execute(query, (pathid, filename, dateadded, fileid))
+        
+        # Process countries
+        self.kodi_db.addCountries(movieid, item['ProductionLocations'], "movie")
+        # Process cast
+        people = artwork.getPeopleArtwork(item['People'])
+        self.kodi_db.addPeople(movieid, people, "movie")
+        # Process genres
+        self.kodi_db.addGenres(movieid, genres, "movie")
+        # Process artwork
+        artwork.addArtwork(artwork.getAllArtwork(item), movieid, "movie", kodicursor)
+        # Process stream details
+        streams = API.getMediaStreams()
+        self.kodi_db.addStreams(fileid, streams, runtime)
+        # Process studios
+        self.kodi_db.addStudios(movieid, studios, "movie")
+        # Process tags: view, emby tags
+        tags = [viewtag]
+        tags.extend(item['Tags'])
+        if userdata['Favorite']:
+            tags.append("Favorite movies")
+        self.kodi_db.addTags(movieid, tags, "movie")
+        # Process playstates
+        resume = API.adjustResume(userdata['Resume'])
+        total = round(float(runtime), 6)
+        self.kodi_db.addPlaystate(fileid, resume, total, playcount, dateplayed)
+
+    def add_updateBoxset(self, boxset):
+
+        emby = self.emby
+        emby_db = self.emby_db
+        artwork = self.artwork
+
+        boxsetid = boxset['Id']
+        title = boxset['Name']
+        checksum = boxset['Etag']
+        emby_dbitem = emby_db.getItem_byId(boxsetid)
+        try:
+            setid = emby_dbitem[0]
+
+        except TypeError:
+            setid = self.kodi_db.createBoxset(title)
+
+        # Process artwork
+        artwork.addArtwork(artwork.getAllArtwork(boxset), setid, "set", self.kodicursor)
+        
+        # Process movies inside boxset
+        current_movies = emby_db.getItemId_byParentId(setid, "movie")
+        process = []
+        try:
+            # Try to convert tuple to dictionary
+            current = dict(current_movies)
+        except ValueError:
+            current = {}
+
+        # Sort current titles
+        for current_movie in current:
+            process.append(current_movie)
+
+        # New list to compare
+        for movie in emby.getMovies_byBoxset(boxsetid)['Items']:
+
+            itemid = movie['Id']
+
+            if not current.get(itemid):
+                # Assign boxset to movie
+                emby_dbitem = emby_db.getItem_byId(itemid)
+                try:
+                    movieid = emby_dbitem[0]
+                except TypeError:
+                    self.logMsg("Failed to add: %s to boxset." % movie['Name'], 1)
+                    continue
+
+                self.logMsg("New addition to boxset %s: %s" % (title, movie['Name']), 1)
+                self.kodi_db.assignBoxset(setid, movieid)
+                # Update emby reference
+                emby_db.updateParentId(itemid, setid)
+            else:
+                # Remove from process, because the item still belongs
+                process.remove(itemid)
+
+        # Process removals from boxset
+        for movie in process:
+            movieid = current[movie]
+            self.logMsg("Remove from boxset %s: %s" % (title, movieid))
+            self.kodi_db.removefromBoxset(movieid)
+            # Update emby reference
+            emby_db.updateParentId(movie, None)
+
+        # Update the reference in the emby table
+        emby_db.addReference(boxsetid, setid, "BoxSet", mediatype="set", checksum=checksum)
+
+    def updateUserdata(self, item):
+        # This updates: Favorite, LastPlayedDate, Playcount, PlaybackPositionTicks
+        # Poster with progress bar
+        emby_db = self.emby_db
+        API = api.API(item)
+        
+        # Get emby information
+        itemid = item['Id']
+        checksum = API.getChecksum()
+        userdata = API.getUserData()
+        runtime = API.getRuntime()
+
+        # Get Kodi information
+        emby_dbitem = emby_db.getItem_byId(itemid)
+        try:
+            movieid = emby_dbitem[0]
+            fileid = emby_dbitem[1]
+            self.logMsg(
+                "Update playstate for movie: %s fileid: %s"
+                % (item['Name'], fileid), 1)
+        except TypeError:
+            return
+
+        # Process favorite tags
+        if userdata['Favorite']:
+            self.kodi_db.addTag(movieid, "Favorite movies", "movie")
+        else:
+            self.kodi_db.removeTag(movieid, "Favorite movies", "movie")
+
+        # Process playstates
+        playcount = userdata['PlayCount']
+        dateplayed = userdata['LastPlayedDate']
+        resume = API.adjustResume(userdata['Resume'])
+        total = round(float(runtime), 6)
+
+        self.logMsg("%s New resume point: %s" % (itemid, resume))
+
+        self.kodi_db.addPlaystate(fileid, resume, total, playcount, dateplayed)
+        emby_db.updateReference(itemid, checksum)
+
+    def remove(self, itemid):
+        # Remove movieid, fileid, emby reference
+        emby_db = self.emby_db
+        kodicursor = self.kodicursor
+        artwork = self.artwork
+
+        emby_dbitem = emby_db.getItem_byId(itemid)
+        try:
+            kodiid = emby_dbitem[0]
+            fileid = emby_dbitem[1]
+            mediatype = emby_dbitem[4]
+            self.logMsg("Removing %sid: %s fileid: %s" % (mediatype, kodiid, fileid), 1)
+        except TypeError:
+            return
+
+        # Remove the emby reference
+        emby_db.removeItem(itemid)
+        # Remove artwork
+        artwork.deleteArtwork(kodiid, mediatype, kodicursor)
+
+        if mediatype == "movie":
+            # Delete kodi movie and file
+            kodicursor.execute("DELETE FROM movie WHERE idMovie = ?", (kodiid,))
+            kodicursor.execute("DELETE FROM files WHERE idFile = ?", (fileid,))
+
+        elif mediatype == "set":
+            # Delete kodi boxset
+            boxset_movies = emby_db.getItem_byParentId(kodiid, "movie")
+            for movie in boxset_movies:
+                embyid = movie[0]
+                movieid = movie[1]
+                self.kodi_db.removefromBoxset(movieid)
+                # Update emby reference
+                emby_db.updateParentId(embyid, None)
+
+            kodicursor.execute("DELETE FROM sets WHERE idSet = ?", (kodiid,))
+
+        self.logMsg("Deleted %s %s from kodi database" % (mediatype, itemid), 1)
+
+class MusicVideos(Items):
+
+    
+    def __init__(self, embycursor, kodicursor):
+        Items.__init__(self, embycursor, kodicursor)
+
+    def added(self, items, pdialog):
+
+        total = len(items)
+        count = 0
+        for mvideo in items:
+
+            title = mvideo['Name']
+            if pdialog:
+                percentage = int((float(count) / float(total))*100)
+                pdialog.update(percentage, message=title)
+                count += 1
+            self.add_update(mvideo)
+            if not pdialog and self.contentmsg:
+                self.contentPop(title, self.newvideo_time)
+
+
+    def add_update(self, item, viewtag=None, viewid=None):
+        # Process single music video
+        kodicursor = self.kodicursor
+        emby_db = self.emby_db
+        artwork = self.artwork
+        API = api.API(item)
+
+        # If the item already exist in the local Kodi DB we'll perform a full item update
+        # If the item doesn't exist, we'll add it to the database
+        update_item = True
+        itemid = item['Id']
+        emby_dbitem = emby_db.getItem_byId(itemid)
+        try:
+            mvideoid = emby_dbitem[0]
+            fileid = emby_dbitem[1]
+            pathid = emby_dbitem[2]
+            self.logMsg("mvideoid: %s fileid: %s pathid: %s" % (mvideoid, fileid, pathid), 1)
+        
+        except TypeError:
+            update_item = False
+            self.logMsg("mvideoid: %s not found." % itemid, 2)
+            # mvideoid
+            kodicursor.execute("select coalesce(max(idMVideo),0) from musicvideo")
+            mvideoid = kodicursor.fetchone()[0] + 1
+
+        else:
+            # Verification the item is still in Kodi
+            query = "SELECT * FROM musicvideo WHERE idMVideo = ?"
+            kodicursor.execute(query, (mvideoid,))
+            try:
+                kodicursor.fetchone()[0]
+            except TypeError:
+                # item is not found, let's recreate it.
+                update_item = False
+                self.logMsg("mvideoid: %s missing from Kodi, repairing the entry." % mvideoid, 1)
+
+        if not viewtag or not viewid:
+            # Get view tag from emby
+            viewtag, viewid, mediatype = self.emby.getView_embyId(itemid)
+            self.logMsg("View tag found: %s" % viewtag, 2)
+
+        # fileId information
+        checksum = API.getChecksum()
+        dateadded = API.getDateCreated()
+        userdata = API.getUserData()
+        playcount = userdata['PlayCount']
+        dateplayed = userdata['LastPlayedDate']
+
+        # item details
+        runtime = API.getRuntime()
+        plot = API.getOverview()
+        title = item['Name']
+        year = item.get('ProductionYear')
+        genres = item['Genres']
+        genre = " / ".join(genres)
+        studios = API.getStudios()
+        studio = " / ".join(studios)
+        artist = " / ".join(item.get('Artists'))
+        album = item.get('Album')
+        track = item.get('Track')
+        people = API.getPeople()
+        director = " / ".join(people['Director'])
+
+        
+        ##### GET THE FILE AND PATH #####
+        playurl = API.getFilePath()
+
+        if "\\" in playurl:
+            # Local path
+            filename = playurl.rsplit("\\", 1)[1]
+        else: # Network share
+            filename = playurl.rsplit("/", 1)[1]
+
+        if self.directpath:
+            # Direct paths is set the Kodi way
+            if utils.window('emby_pathverified') != "true" and not xbmcvfs.exists(playurl):
+                # Validate the path is correct with user intervention
+                resp = xbmcgui.Dialog().yesno(
+                                        heading="Can't validate path",
+                                        line1=(
+                                            "Kodi can't locate file: %s. Verify the path. "
+                                            "You may to verify your network credentials in the "
+                                            "add-on settings or use the emby path substitution "
+                                            "to format your path correctly. Stop syncing?"
+                                            % playurl))
+                if resp:
+                    utils.window('emby_shouldStop', value="true")
+                    return False
+            
+            path = playurl.replace(filename, "")
+            utils.window('emby_pathverified', value="true")
+        else:
+            # Set plugin path and media flags using real filename
+            path = "plugin://plugin.video.emby.musicvideos/"
+            params = {
+
+                'filename': filename.encode('utf-8'),
+                'id': itemid,
+                'dbid': mvideoid,
+                'mode': "play"
+            }
+            filename = "%s?%s" % (path, urllib.urlencode(params))
+
+
+        ##### UPDATE THE MUSIC VIDEO #####
+        if update_item:
+            self.logMsg("UPDATE mvideo itemid: %s - Title: %s" % (itemid, title), 1)
+            
+            # Update path
+            query = "UPDATE path SET strPath = ? WHERE idPath = ?"
+            kodicursor.execute(query, (path, pathid))
+
+            # Update the filename
+            query = "UPDATE files SET strFilename = ?, dateAdded = ? WHERE idFile = ?"
+            kodicursor.execute(query, (filename, dateadded, fileid))
+
+            # Update the music video entry
+            query = ' '.join((
+                
+                "UPDATE musicvideo",
+                "SET c00 = ?, c04 = ?, c05 = ?, c06 = ?, c07 = ?, c08 = ?, c09 = ?, c10 = ?,",
+                    "c11 = ?, c12 = ?"
+                "WHERE idMVideo = ?"
+            ))
+            kodicursor.execute(query, (title, runtime, director, studio, year, plot, album,
+                artist, genre, track, mvideoid))
+
+            # Update the checksum in emby table
+            emby_db.updateReference(itemid, checksum)
+        
+        ##### OR ADD THE MUSIC VIDEO #####
+        else:
+            self.logMsg("ADD mvideo itemid: %s - Title: %s" % (itemid, title), 1)
+            
+            # Add path
+            query = ' '.join((
+
+                "SELECT idPath",
+                "FROM path",
+                "WHERE strPath = ?"
+            ))
+            kodicursor.execute(query, (path,))
+            try:
+                pathid = kodicursor.fetchone()[0]
+            except TypeError:
+                kodicursor.execute("select coalesce(max(idPath),0) from path")
+                pathid = kodicursor.fetchone()[0] + 1
+                query = (
+                    '''
+                    INSERT OR REPLACE INTO path(
+                        idPath, strPath, strContent, strScraper, noUpdate)
+
+                    VALUES (?, ?, ?, ?, ?)
+                    '''
+                )
+                kodicursor.execute(query, (pathid, path, "musicvideos", "metadata.local", 1))
+
+            # Add the file
+            kodicursor.execute("select coalesce(max(idFile),0) from files")
+            fileid = kodicursor.fetchone()[0] + 1
+            query = (
+                '''
+                INSERT INTO files(
+                    idFile, idPath, strFilename, dateAdded)
+
+                VALUES (?, ?, ?, ?)
+                '''
+            )
+            kodicursor.execute(query, (fileid, pathid, filename, dateadded))
+            
+            # Create the musicvideo entry
+            query = (
+                '''
+                INSERT INTO musicvideo(
+                    idMVideo, idFile, c00, c04, c05, c06, c07, c08, c09, c10, c11, c12)
+
+                VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
+                '''
+            )
+            kodicursor.execute(query, (mvideoid, fileid, title, runtime, director, studio,
+                year, plot, album, artist, genre, track))
+
+            # Create the reference in emby table
+            emby_db.addReference(itemid, mvideoid, "MusicVideo", "musicvideo", fileid, pathid,
+                checksum=checksum, mediafolderid=viewid)
+
+        
+        # Process cast
+        people = item['People']
+        artists = item['ArtistItems']
+        for artist in artists:
+            artist['Type'] = "Artist"
+        people.extend(artists)
+        people = artwork.getPeopleArtwork(people)
+        self.kodi_db.addPeople(mvideoid, people, "musicvideo")
+        # Process genres
+        self.kodi_db.addGenres(mvideoid, genres, "musicvideo")
+        # Process artwork
+        artwork.addArtwork(artwork.getAllArtwork(item), mvideoid, "musicvideo", kodicursor)
+        # Process stream details
+        streams = API.getMediaStreams()
+        self.kodi_db.addStreams(fileid, streams, runtime)
+        # Process studios
+        self.kodi_db.addStudios(mvideoid, studios, "musicvideo")
+        # Process tags: view, emby tags
+        tags = [viewtag]
+        tags.extend(item['Tags'])
+        if userdata['Favorite']:
+            tags.append("Favorite musicvideos")
+        self.kodi_db.addTags(mvideoid, tags, "musicvideo")
+        # Process playstates
+        resume = API.adjustResume(userdata['Resume'])
+        total = round(float(runtime), 6)
+        self.kodi_db.addPlaystate(fileid, resume, total, playcount, dateplayed)
+
+    def updateUserdata(self, item):
+        # This updates: Favorite, LastPlayedDate, Playcount, PlaybackPositionTicks
+        # Poster with progress bar
+        emby_db = self.emby_db
+        API = api.API(item)
+        
+        # Get emby information
+        itemid = item['Id']
+        checksum = API.getChecksum()
+        userdata = API.getUserData()
+        runtime = API.getRuntime()
+
+        # Get Kodi information
+        emby_dbitem = emby_db.getItem_byId(itemid)
+        try:
+            mvideoid = emby_dbitem[0]
+            fileid = emby_dbitem[1]
+            self.logMsg(
+                "Update playstate for musicvideo: %s fileid: %s"
+                % (item['Name'], fileid), 1)
+        except TypeError:
+            return
+
+        # Process favorite tags
+        if userdata['Favorite']:
+            self.kodi_db.addTag(mvideoid, "Favorite musicvideos", "musicvideo")
+        else:
+            self.kodi_db.removeTag(mvideoid, "Favorite musicvideos", "musicvideo")
+
+        # Process playstates
+        playcount = userdata['PlayCount']
+        dateplayed = userdata['LastPlayedDate']
+        resume = API.adjustResume(userdata['Resume'])
+        total = round(float(runtime), 6)
+
+        self.kodi_db.addPlaystate(fileid, resume, total, playcount, dateplayed)
+        emby_db.updateReference(itemid, checksum)
+
+    def remove(self, itemid):
+        # Remove mvideoid, fileid, pathid, emby reference
+        emby_db = self.emby_db
+        kodicursor = self.kodicursor
+        artwork = self.artwork
+
+        emby_dbitem = emby_db.getItem_byId(itemid)
+        try:
+            mvideoid = emby_dbitem[0]
+            fileid = emby_dbitem[1]
+            pathid = emby_dbitem[2]
+            self.logMsg("Removing mvideoid: %s fileid: %s" % (mvideoid, fileid, pathid), 1)
+        except TypeError:
+            return
+
+        # Remove artwork
+        query = ' '.join((
+
+            "SELECT url, type",
+            "FROM art",
+            "WHERE media_id = ?",
+            "AND media_type = 'musicvideo'"
+        ))
+        kodicursor.execute(query, (mvideoid,))
+        for row in kodicursor.fetchall():
+            
+            url = row[0]
+            imagetype = row[1]
+            if imagetype in ("poster", "fanart"):
+                artwork.deleteCachedArtwork(url)
+
+        kodicursor.execute("DELETE FROM musicvideo WHERE idMVideo = ?", (mvideoid,))
+        kodicursor.execute("DELETE FROM files WHERE idFile = ?", (fileid,))
+        if self.directpath:
+            kodicursor.execute("DELETE FROM path WHERE idPath = ?", (pathid,))
+        self.embycursor.execute("DELETE FROM emby WHERE emby_id = ?", (itemid,))
+
+        self.logMsg("Deleted musicvideo %s from kodi database" % itemid, 1)
+
+class TVShows(Items):
+
+
+    def __init__(self, embycursor, kodicursor):
+        Items.__init__(self, embycursor, kodicursor)
+
+    def added(self, items, pdialog):
+        
+        total = len(items)
+        count = 0
+        for tvshow in items:
+
+            title = tvshow['Name']
+            if pdialog:
+                percentage = int((float(count) / float(total))*100)
+                pdialog.update(percentage, message=title)
+                count += 1
+            self.add_update(tvshow)
+            # Add episodes
+            all_episodes = self.emby.getEpisodesbyShow(tvshow['Id'])
+            self.added_episode(all_episodes['Items'], pdialog)
+
+    def added_season(self, items, pdialog):
+        
+        total = len(items)
+        count = 0
+        for season in items:
+
+            title = "%s - %s" % (season.get('SeriesName', "Unknown"), season['Name'])
+            if pdialog:
+                percentage = int((float(count) / float(total))*100)
+                pdialog.update(percentage, message=title)
+                count += 1
+            self.add_updateSeason(season)
+            # Add episodes
+            all_episodes = self.emby.getEpisodesbySeason(season['Id'])
+            self.added_episode(all_episodes['Items'], pdialog)
+
+    def added_episode(self, items, pdialog):
+        
+        total = len(items)
+        count = 0
+        for episode in items:
+            title = "%s - %s" % (episode.get('SeriesName', "Unknown"), episode['Name'])
+            if pdialog:
+                percentage = int((float(count) / float(total))*100)
+                pdialog.update(percentage, message=title)
+                count += 1
+            self.add_updateEpisode(episode)
+            if not pdialog and self.contentmsg:
+                self.contentPop(title, self.newvideo_time)
+
+
+    def add_update(self, item, viewtag=None, viewid=None):
+        # Process single tvshow
+        kodicursor = self.kodicursor
+        emby = self.emby
+        emby_db = self.emby_db
+        artwork = self.artwork
+        API = api.API(item)
+
+        if utils.settings('syncEmptyShows') == "false" and not item['RecursiveItemCount']:
+            self.logMsg("Skipping empty show: %s" % item['Name'], 1)
+            return
+        # If the item already exist in the local Kodi DB we'll perform a full item update
+        # If the item doesn't exist, we'll add it to the database
+        update_item = True
+        force_episodes = False
+        itemid = item['Id']
+        emby_dbitem = emby_db.getItem_byId(itemid)
+        try:
+            showid = emby_dbitem[0]
+            pathid = emby_dbitem[2]
+            self.logMsg("showid: %s pathid: %s" % (showid, pathid), 1)
+        
+        except TypeError:
+            update_item = False
+            self.logMsg("showid: %s not found." % itemid, 2)
+            kodicursor.execute("select coalesce(max(idShow),0) from tvshow")
+            showid = kodicursor.fetchone()[0] + 1
+
+        else:
+            # Verification the item is still in Kodi
+            query = "SELECT * FROM tvshow WHERE idShow = ?"
+            kodicursor.execute(query, (showid,))
+            try:
+                kodicursor.fetchone()[0]
+            except TypeError:
+                # item is not found, let's recreate it.
+                update_item = False
+                self.logMsg("showid: %s missing from Kodi, repairing the entry." % showid, 1)
+                # Force re-add episodes after the show is re-created.
+                force_episodes = True
+
+
+        if viewtag is None or viewid is None:
+            # Get view tag from emby
+            viewtag, viewid, mediatype = emby.getView_embyId(itemid)
+            self.logMsg("View tag found: %s" % viewtag, 2)
+
+        # fileId information
+        checksum = API.getChecksum()
+        dateadded = API.getDateCreated()
+        userdata = API.getUserData()
+        playcount = userdata['PlayCount']
+        dateplayed = userdata['LastPlayedDate']
+
+        # item details
+        genres = item['Genres']
+        title = item['Name']
+        plot = API.getOverview()
+        rating = item.get('CommunityRating')
+        premieredate = API.getPremiereDate()
+        tvdb = API.getProvider('Tvdb')
+        sorttitle = item['SortName']
+        mpaa = API.getMpaa()
+        genre = " / ".join(genres)
+        studios = API.getStudios()
+        studio = " / ".join(studios)
+
+        
+        ##### GET THE FILE AND PATH #####
+        playurl = API.getFilePath()
+
+        if self.directpath:
+            # Direct paths is set the Kodi way
+            if "\\" in playurl:
+                # Local path
+                path = "%s\\" % playurl
+                toplevelpath = "%s\\" % dirname(dirname(path))
+            else:
+                # Network path
+                path = "%s/" % playurl
+                toplevelpath = "%s/" % dirname(dirname(path))
+
+            if utils.window('emby_pathverified') != "true" and not xbmcvfs.exists(path):
+                # Validate the path is correct with user intervention
+                resp = xbmcgui.Dialog().yesno(
+                                        heading="Can't validate path",
+                                        line1=(
+                                            "Kodi can't locate file: %s. Verify the path. "
+                                            "You may to verify your network credentials in the "
+                                            "add-on settings or use the emby path substitution "
+                                            "to format your path correctly. Stop syncing?"
+                                            % playurl))
+                if resp:
+                    utils.window('emby_shouldStop', value="true")
+                    return False
+
+            utils.window('emby_pathverified', value="true")
+        else:
+            # Set plugin path
+            toplevelpath = "plugin://plugin.video.emby.tvshows/"
+            path = "%s%s/" % (toplevelpath, itemid)
+
+
+        ##### UPDATE THE TVSHOW #####
+        if update_item:
+            self.logMsg("UPDATE tvshow itemid: %s - Title: %s" % (itemid, title), 1)
+
+            # Update the tvshow entry
+            query = ' '.join((
+                
+                "UPDATE tvshow",
+                "SET c00 = ?, c01 = ?, c04 = ?, c05 = ?, c08 = ?, c09 = ?,",
+                    "c12 = ?, c13 = ?, c14 = ?, c15 = ?",
+                "WHERE idShow = ?"
+            ))
+            kodicursor.execute(query, (title, plot, rating, premieredate, genre, title,
+                tvdb, mpaa, studio, sorttitle, showid))
+
+            # Update the checksum in emby table
+            emby_db.updateReference(itemid, checksum)
+        
+        ##### OR ADD THE TVSHOW #####
+        else:
+            self.logMsg("ADD tvshow itemid: %s - Title: %s" % (itemid, title), 1)
+            
+            # Add top path
+            toppathid = self.kodi_db.addPath(toplevelpath)
+            query = ' '.join((
+
+                "UPDATE path",
+                "SET strPath = ?, strContent = ?, strScraper = ?, noUpdate = ?",
+                "WHERE idPath = ?"
+            ))
+            kodicursor.execute(query, (toplevelpath, "tvshows", "metadata.local", 1, toppathid))
+            
+            # Add path
+            pathid = self.kodi_db.addPath(path)
+            
+            # Create the tvshow entry
+            query = (
+                '''
+                INSERT INTO tvshow(
+                    idShow, c00, c01, c04, c05, c08, c09, c12, c13, c14, c15) 
+
+                VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
+                '''
+            )
+            kodicursor.execute(query, (showid, title, plot, rating, premieredate, genre,
+                title, tvdb, mpaa, studio, sorttitle))
+
+            # Link the path
+            query = "INSERT INTO tvshowlinkpath(idShow, idPath) values(?, ?)"
+            kodicursor.execute(query, (showid, pathid))
+
+            # Create the reference in emby table
+            emby_db.addReference(itemid, showid, "Series", "tvshow", pathid=pathid,
+                                checksum=checksum, mediafolderid=viewid)
+
+        # Update the path
+        query = ' '.join((
+
+            "UPDATE path",
+            "SET strPath = ?, strContent = ?, strScraper = ?, noUpdate = ?",
+            "WHERE idPath = ?"
+        ))
+        kodicursor.execute(query, (path, None, None, 1, pathid))
+        
+        # Process cast
+        people = artwork.getPeopleArtwork(item['People'])
+        self.kodi_db.addPeople(showid, people, "tvshow")
+        # Process genres
+        self.kodi_db.addGenres(showid, genres, "tvshow")
+        # Process artwork
+        artwork.addArtwork(artwork.getAllArtwork(item), showid, "tvshow", kodicursor)
+        # Process studios
+        self.kodi_db.addStudios(showid, studios, "tvshow")
+        # Process tags: view, emby tags
+        tags = [viewtag]
+        tags.extend(item['Tags'])
+        if userdata['Favorite']:
+            tags.append("Favorite tvshows")
+        self.kodi_db.addTags(showid, tags, "tvshow")
+        # Process seasons
+        all_seasons = emby.getSeasons(itemid)
+        for season in all_seasons['Items']:
+            self.add_updateSeason(season, showid=showid)
+        else:
+            # Finally, refresh the all season entry
+            seasonid = self.kodi_db.addSeason(showid, -1)
+            # Process artwork
+            artwork.addArtwork(artwork.getAllArtwork(item), seasonid, "season", kodicursor)
+
+        if force_episodes:
+            # We needed to recreate the show entry. Re-add episodes now.
+            self.logMsg("Repairing episodes for showid: %s %s" % (showid, title), 1)
+            all_episodes = emby.getEpisodesbyShow(itemid)
+            self.added_episode(all_episodes['Items'], None)
+
+    def add_updateSeason(self, item, showid=None):
+
+        kodicursor = self.kodicursor
+        emby_db = self.emby_db
+        artwork = self.artwork
+
+        seasonnum = item.get('IndexNumber', 1)
+
+        if showid is None:
+            try:
+                seriesId = item['SeriesId']
+                showid = emby_db.getItem_byId(seriesId)[0]
+            except KeyError:
+                return
+            except TypeError:
+                # Show is missing, update show instead.
+                show = self.emby.getItem(seriesId)
+                self.add_update(show)
+                return
+        
+        seasonid = self.kodi_db.addSeason(showid, seasonnum)
+        
+        if item['LocationType'] != "Virtual":
+            # Create the reference in emby table
+            emby_db.addReference(item['Id'], seasonid, "Season", "season", parentid=showid)
+
+        # Process artwork
+        artwork.addArtwork(artwork.getAllArtwork(item), seasonid, "season", kodicursor)
+
+    def add_updateEpisode(self, item):
+        # Process single episode
+        kodicursor = self.kodicursor
+        emby_db = self.emby_db
+        artwork = self.artwork
+        API = api.API(item)
+
+        # If the item already exist in the local Kodi DB we'll perform a full item update
+        # If the item doesn't exist, we'll add it to the database
+        update_item = True
+        itemid = item['Id']
+        emby_dbitem = emby_db.getItem_byId(itemid)
+        try:
+            episodeid = emby_dbitem[0]
+            fileid = emby_dbitem[1]
+            pathid = emby_dbitem[2]
+            self.logMsg("episodeid: %s fileid: %s pathid: %s" % (episodeid, fileid, pathid), 1)
+        
+        except TypeError:
+            update_item = False
+            self.logMsg("episodeid: %s not found." % itemid, 2)
+            # episodeid
+            kodicursor.execute("select coalesce(max(idEpisode),0) from episode")
+            episodeid = kodicursor.fetchone()[0] + 1
+
+        else:
+            # Verification the item is still in Kodi
+            query = "SELECT * FROM episode WHERE idEpisode = ?"
+            kodicursor.execute(query, (episodeid,))
+            try:
+                kodicursor.fetchone()[0]
+            except TypeError:
+                # item is not found, let's recreate it.
+                update_item = False
+                self.logMsg("episodeid: %s missing from Kodi, repairing the entry." % episodeid, 1)
+
+        # fileId information
+        checksum = API.getChecksum()
+        dateadded = API.getDateCreated()
+        userdata = API.getUserData()
+        playcount = userdata['PlayCount']
+        dateplayed = userdata['LastPlayedDate']
+
+        # item details
+        people = API.getPeople()
+        writer = " / ".join(people['Writer'])
+        director = " / ".join(people['Director'])
+        title = item['Name']
+        plot = API.getOverview()
+        rating = item.get('CommunityRating')
+        runtime = API.getRuntime()
+        premieredate = API.getPremiereDate()
+
+        # episode details
+        try:
+            seriesId = item['SeriesId']
+        except KeyError:
+            # Missing seriesId, skip
+            self.logMsg("Skipping: %s. SeriesId is missing." % itemid, 1)
+            return False
+            
+        seriesName = item['SeriesName']
+        season = item.get('ParentIndexNumber')
+        episode = item.get('IndexNumber', -1)
+       
+        if season is None:
+            if item.get('AbsoluteEpisodeNumber'):
+                # Anime scenario
+                season = 1
+                episode = item['AbsoluteEpisodeNumber']
+            else:
+                season = -1
+
+        # Specials ordering within season
+        if item.get('AirsAfterSeasonNumber'):
+            airsBeforeSeason = item['AirsAfterSeasonNumber']
+            airsBeforeEpisode = 4096 # Kodi default number for afterseason ordering
+        else:
+            airsBeforeSeason = item.get('AirsBeforeSeasonNumber')
+            airsBeforeEpisode = item.get('AirsBeforeEpisodeNumber')
+
+        # Append multi episodes to title
+        if item.get('IndexNumberEnd'):              
+            title = "| %02d | %s" % (item['IndexNumberEnd'], title)
+
+        # Get season id
+        show = emby_db.getItem_byId(seriesId)
+        try:
+            showid = show[0]
+        except TypeError:
+            # Show is missing from database
+            show = self.emby.getItem(seriesId)
+            self.add_update(show)
+            show = emby_db.getItem_byId(seriesId)
+            try:
+                showid = show[0]
+            except TypeError:
+                self.logMsg("Skipping: %s. Unable to add series: %s." % (itemid, seriesId))
+                return False
+
+        seasonid = self.kodi_db.addSeason(showid, season)
+
+        
+        ##### GET THE FILE AND PATH #####
+        playurl = API.getFilePath()
+
+        if "\\" in playurl:
+            # Local path
+            filename = playurl.rsplit("\\", 1)[1]
+        else: # Network share
+            filename = playurl.rsplit("/", 1)[1]
+
+        if self.directpath:
+            # Direct paths is set the Kodi way
+            if utils.window('emby_pathverified') != "true" and not xbmcvfs.exists(playurl):
+                # Validate the path is correct with user intervention
+                resp = xbmcgui.Dialog().yesno(
+                                        heading="Can't validate path",
+                                        line1=(
+                                            "Kodi can't locate file: %s. Verify the path. "
+                                            "You may to verify your network credentials in the "
+                                            "add-on settings or use the emby path substitution "
+                                            "to format your path correctly. Stop syncing?"
+                                            % playurl))
+                if resp:
+                    utils.window('emby_shouldStop', value="true")
+                    return False
+            
+            path = playurl.replace(filename, "")
+            utils.window('emby_pathverified', value="true")
+        else:
+            # Set plugin path and media flags using real filename
+            path = "plugin://plugin.video.emby.tvshows/%s/" % seriesId
+            params = {
+
+                'filename': filename.encode('utf-8'),
+                'id': itemid,
+                'dbid': episodeid,
+                'mode': "play"
+            }
+            filename = "%s?%s" % (path, urllib.urlencode(params))
+
+
+        ##### UPDATE THE EPISODE #####
+        if update_item:
+            self.logMsg("UPDATE episode itemid: %s - Title: %s" % (itemid, title), 1)
+
+            # Update the movie entry
+            if self.kodiversion in (16, 17):
+                # Kodi Jarvis, Krypton
+                query = ' '.join((
+                
+                    "UPDATE episode",
+                    "SET c00 = ?, c01 = ?, c03 = ?, c04 = ?, c05 = ?, c09 = ?, c10 = ?,",
+                        "c12 = ?, c13 = ?, c14 = ?, c15 = ?, c16 = ?, idSeason = ?",
+                    "WHERE idEpisode = ?"
+                ))
+                kodicursor.execute(query, (title, plot, rating, writer, premieredate,
+                    runtime, director, season, episode, title, airsBeforeSeason,
+                    airsBeforeEpisode, seasonid, episodeid))
+            else:
+                query = ' '.join((
+                    
+                    "UPDATE episode",
+                    "SET c00 = ?, c01 = ?, c03 = ?, c04 = ?, c05 = ?, c09 = ?, c10 = ?,",
+                        "c12 = ?, c13 = ?, c14 = ?, c15 = ?, c16 = ?",
+                    "WHERE idEpisode = ?"
+                ))
+                kodicursor.execute(query, (title, plot, rating, writer, premieredate,
+                    runtime, director, season, episode, title, airsBeforeSeason,
+                    airsBeforeEpisode, episodeid))
+
+            # Update the checksum in emby table
+            emby_db.updateReference(itemid, checksum)
+            # Update parentid reference
+            emby_db.updateParentId(itemid, seasonid)
+        
+        ##### OR ADD THE EPISODE #####
+        else:
+            self.logMsg("ADD episode itemid: %s - Title: %s" % (itemid, title), 1)
+            
+            # Add path
+            pathid = self.kodi_db.addPath(path)
+            # Add the file
+            fileid = self.kodi_db.addFile(filename, pathid)
+            
+            # Create the episode entry
+            if self.kodiversion in (16, 17):
+                # Kodi Jarvis, Krypton
+                query = (
+                    '''
+                    INSERT INTO episode(
+                        idEpisode, idFile, c00, c01, c03, c04, c05, c09, c10, c12, c13, c14,
+                        idShow, c15, c16, idSeason)
+
+                    VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
+                    '''
+                )
+                kodicursor.execute(query, (episodeid, fileid, title, plot, rating, writer,
+                    premieredate, runtime, director, season, episode, title, showid,
+                    airsBeforeSeason, airsBeforeEpisode, seasonid))
+            else:
+                query = (
+                    '''
+                    INSERT INTO episode(
+                        idEpisode, idFile, c00, c01, c03, c04, c05, c09, c10, c12, c13, c14,
+                        idShow, c15, c16)
+
+                    VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
+                    '''
+                )
+                kodicursor.execute(query, (episodeid, fileid, title, plot, rating, writer,
+                    premieredate, runtime, director, season, episode, title, showid,
+                    airsBeforeSeason, airsBeforeEpisode))
+
+            # Create the reference in emby table
+            emby_db.addReference(itemid, episodeid, "Episode", "episode", fileid, pathid,
+                seasonid, checksum)
+
+        # Update the path
+        query = ' '.join((
+
+            "UPDATE path",
+            "SET strPath = ?, strContent = ?, strScraper = ?, noUpdate = ?",
+            "WHERE idPath = ?"
+        ))
+        kodicursor.execute(query, (path, None, None, 1, pathid))
+
+        # Update the file
+        query = ' '.join((
+
+            "UPDATE files",
+            "SET idPath = ?, strFilename = ?, dateAdded = ?",
+            "WHERE idFile = ?"
+        ))
+        kodicursor.execute(query, (pathid, filename, dateadded, fileid))
+        
+        # Process cast
+        people = artwork.getPeopleArtwork(item['People'])
+        self.kodi_db.addPeople(episodeid, people, "episode")
+        # Process artwork
+        artworks = artwork.getAllArtwork(item)
+        artwork.addOrUpdateArt(artworks['Primary'], episodeid, "episode", "thumb", kodicursor)
+        # Process stream details
+        streams = API.getMediaStreams()
+        self.kodi_db.addStreams(fileid, streams, runtime)
+        # Process playstates
+        resume = API.adjustResume(userdata['Resume'])
+        total = round(float(runtime), 6)
+        self.kodi_db.addPlaystate(fileid, resume, total, playcount, dateplayed)
+        if not self.directpath and resume:
+            # Create additional entry for widgets. This is only required for plugin/episode.
+            temppathid = self.kodi_db.getPath("plugin://plugin.video.emby.tvshows/")
+            tempfileid = self.kodi_db.addFile(filename, temppathid)
+            query = ' '.join((
+
+                "UPDATE files",
+                "SET idPath = ?, strFilename = ?, dateAdded = ?",
+                "WHERE idFile = ?"
+            ))
+            kodicursor.execute(query, (temppathid, filename, dateadded, tempfileid))
+            self.kodi_db.addPlaystate(tempfileid, resume, total, playcount, dateplayed)
+
+    def updateUserdata(self, item):
+        # This updates: Favorite, LastPlayedDate, Playcount, PlaybackPositionTicks
+        # Poster with progress bar
+        emby_db = self.emby_db
+        API = api.API(item)
+        
+        # Get emby information
+        itemid = item['Id']
+        checksum = API.getChecksum()
+        userdata = API.getUserData()
+        runtime = API.getRuntime()
+        dateadded = API.getDateCreated()
+
+        # Get Kodi information
+        emby_dbitem = emby_db.getItem_byId(itemid)
+        try:
+            kodiid = emby_dbitem[0]
+            fileid = emby_dbitem[1]
+            mediatype = emby_dbitem[4]
+            self.logMsg(
+                "Update playstate for %s: %s fileid: %s"
+                % (mediatype, item['Name'], fileid), 1)
+        except TypeError:
+            return
+
+        # Process favorite tags
+        if mediatype == "tvshow":
+            if userdata['Favorite']:
+                self.kodi_db.addTag(kodiid, "Favorite tvshows", "tvshow")
+            else:
+                self.kodi_db.removeTag(kodiid, "Favorite tvshows", "tvshow")
+        elif mediatype == "episode":
+            # Process playstates
+            playcount = userdata['PlayCount']
+            dateplayed = userdata['LastPlayedDate']
+            resume = API.adjustResume(userdata['Resume'])
+            total = round(float(runtime), 6)
+
+            self.logMsg("%s New resume point: %s" % (itemid, resume))
+
+            self.kodi_db.addPlaystate(fileid, resume, total, playcount, dateplayed)
+            if not self.directpath and not resume:
+                # Make sure there's no other bookmarks created by widget.
+                filename = self.kodi_db.getFile(fileid)
+                self.kodi_db.removeFile("plugin://plugin.video.emby.tvshows/", filename)
+
+            if not self.directpath and resume:
+                # Create additional entry for widgets. This is only required for plugin/episode.
+                filename = self.kodi_db.getFile(fileid)
+                temppathid = self.kodi_db.getPath("plugin://plugin.video.emby.tvshows/")
+                tempfileid = self.kodi_db.addFile(filename, temppathid)
+                query = ' '.join((
+
+                    "UPDATE files",
+                    "SET idPath = ?, strFilename = ?, dateAdded = ?",
+                    "WHERE idFile = ?"
+                ))
+                self.kodicursor.execute(query, (temppathid, filename, dateadded, tempfileid))
+                self.kodi_db.addPlaystate(tempfileid, resume, total, playcount, dateplayed)
+
+        emby_db.updateReference(itemid, checksum)
+
+    def remove(self, itemid):
+        # Remove showid, fileid, pathid, emby reference
+        emby_db = self.emby_db
+        embycursor = self.embycursor
+        kodicursor = self.kodicursor
+        artwork = self.artwork
+
+        emby_dbitem = emby_db.getItem_byId(itemid)
+        try:
+            kodiid = emby_dbitem[0]
+            fileid = emby_dbitem[1]
+            pathid = emby_dbitem[2]
+            parentid = emby_dbitem[3]
+            mediatype = emby_dbitem[4]
+            self.logMsg("Removing %s kodiid: %s fileid: %s" % (mediatype, kodiid, fileid), 1)
+        except TypeError:
+            return
+
+        ##### PROCESS ITEM #####
+
+        # Remove the emby reference
+        emby_db.removeItem(itemid)
+
+
+        ##### IF EPISODE #####
+
+        if mediatype == "episode":
+            # Delete kodi episode and file, verify season and tvshow
+            self.removeEpisode(kodiid, fileid)
+
+            # Season verification
+            season = emby_db.getItem_byKodiId(parentid, "season")
+            try:
+                showid = season[1]
+            except TypeError:
+                return
+            
+            season_episodes = emby_db.getItem_byParentId(parentid, "episode")
+            if not season_episodes:
+                self.removeSeason(parentid)
+                emby_db.removeItem(season[0])
+
+            # Show verification
+            show = emby_db.getItem_byKodiId(showid, "tvshow")
+            query = ' '.join((
+
+                "SELECT totalCount",
+                "FROM tvshowcounts",
+                "WHERE idShow = ?"
+            ))
+            kodicursor.execute(query, (showid,))
+            result = kodicursor.fetchone()
+            if result and result[0] is None:
+                # There's no episodes left, delete show and any possible remaining seasons
+                seasons = emby_db.getItem_byParentId(showid, "season")
+                for season in seasons:
+                    self.removeSeason(season[1])
+                else:
+                    # Delete emby season entries
+                    emby_db.removeItems_byParentId(showid, "season")
+                self.removeShow(showid)
+                emby_db.removeItem(show[0])
+
+        ##### IF TVSHOW #####
+
+        elif mediatype == "tvshow":
+            # Remove episodes, seasons, tvshow
+            seasons = emby_db.getItem_byParentId(kodiid, "season")
+            for season in seasons:
+                seasonid = season[1]
+                season_episodes = emby_db.getItem_byParentId(seasonid, "episode")
+                for episode in season_episodes:
+                    self.removeEpisode(episode[1], episode[2])
+                else:
+                    # Remove emby episodes
+                    emby_db.removeItems_byParentId(seasonid, "episode")
+            else:
+                # Remove emby seasons
+                emby_db.removeItems_byParentId(kodiid, "season")
+
+            # Remove tvshow
+            self.removeShow(kodiid)
+
+        ##### IF SEASON #####
+
+        elif mediatype == "season":
+            # Remove episodes, season, verify tvshow
+            season_episodes = emby_db.getItem_byParentId(kodiid, "episode")
+            for episode in season_episodes:
+                self.removeEpisode(episode[1], episode[2])
+            else:
+                # Remove emby episodes
+                emby_db.removeItems_byParentId(kodiid, "episode")
+            
+            # Remove season
+            self.removeSeason(kodiid)
+
+            # Show verification
+            seasons = emby_db.getItem_byParentId(parentid, "season")
+            if not seasons:
+                # There's no seasons, delete the show
+                self.removeShow(parentid)
+                emby_db.removeItem_byKodiId(parentid, "tvshow")
+
+        self.logMsg("Deleted %s: %s from kodi database" % (mediatype, itemid), 1)
+
+    def removeShow(self, kodiid):
+        
+        kodicursor = self.kodicursor
+        self.artwork.deleteArtwork(kodiid, "tvshow", kodicursor)
+        kodicursor.execute("DELETE FROM tvshow WHERE idShow = ?", (kodiid,))
+        self.logMsg("Removed tvshow: %s." % kodiid, 2)
+
+    def removeSeason(self, kodiid):
+        
+        kodicursor = self.kodicursor
+
+        self.artwork.deleteArtwork(kodiid, "season", kodicursor)
+        kodicursor.execute("DELETE FROM seasons WHERE idSeason = ?", (kodiid,))
+        self.logMsg("Removed season: %s." % kodiid, 2)
+
+    def removeEpisode(self, kodiid, fileid):
+
+        kodicursor = self.kodicursor
+
+        self.artwork.deleteArtwork(kodiid, "episode", kodicursor)
+        kodicursor.execute("DELETE FROM episode WHERE idEpisode = ?", (kodiid,))
+        kodicursor.execute("DELETE FROM files WHERE idFile = ?", (fileid,))
+        self.logMsg("Removed episode: %s." % kodiid, 2)
+
+class Music(Items):
+
+
+    def __init__(self, embycursor, musiccursor):
+        
+        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)
+
+    def added(self, items, pdialog):
+        
+        total = len(items)
+        count = 0
+        for artist in items:
+
+            if pdialog:
+                percentage = int((float(count) / float(total))*100)
+                pdialog.update(percentage, message=artist['Name'])
+                count += 1
+            self.add_updateArtist(artist)
+            # Add albums
+            all_albums = self.emby.getAlbumsbyArtist(artist['Id'])
+            self.added_album(all_albums['Items'], pdialog)
+
+    def added_album(self, items, pdialog):
+        
+        total = len(items)
+        count = 0
+        for album in items:
+
+            if pdialog:
+                percentage = int((float(count) / float(total))*100)
+                pdialog.update(percentage, message=album['Name'])
+                count += 1
+            self.add_updateAlbum(album)
+            # Add songs
+            all_songs = self.emby.getSongsbyAlbum(album['Id'])
+            self.added_song(all_songs['Items'], pdialog)
+
+    def added_song(self, items, pdialog):
+        
+        total = len(items)
+        count = 0
+        for song in items:
+
+            if pdialog:
+                percentage = int((float(count) / float(total))*100)
+                pdialog.update(percentage, message=song['Name'])
+                count += 1
+            self.add_updateSong(song)
+            if not pdialog and self.contentmsg:
+                self.contentPop(song['Name'], self.newmusic_time)
+
+    def add_updateArtist(self, item, artisttype="MusicArtist"):
+        # Process a single artist
+        kodicursor = self.kodicursor
+        emby_db = self.emby_db
+        artwork = self.artwork
+        API = api.API(item)
+
+        update_item = True
+        itemid = item['Id']
+        emby_dbitem = emby_db.getItem_byId(itemid)
+        try:
+            artistid = emby_dbitem[0]
+        except TypeError:
+            update_item = False
+            self.logMsg("artistid: %s not found." % itemid, 2)
+
+        ##### The artist details #####
+        lastScraped = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
+        dateadded = API.getDateCreated()
+        checksum = API.getChecksum()
+
+        name = item['Name']
+        musicBrainzId = API.getProvider('MusicBrainzArtist')
+        genres = " / ".join(item.get('Genres'))
+        bio = API.getOverview()
+
+        # Associate artwork
+        artworks = artwork.getAllArtwork(item, parentInfo=True)
+        thumb = artworks['Primary']
+        backdrops = artworks['Backdrop'] # List
+
+        if thumb:
+            thumb = "<thumb>%s</thumb>" % thumb
+        if backdrops:
+            fanart = "<fanart>%s</fanart>" % backdrops[0]
+        else:
+            fanart = ""
+
+
+        ##### UPDATE THE ARTIST #####
+        if update_item:
+            self.logMsg("UPDATE artist itemid: %s - Name: %s" % (itemid, name), 1)
+            # Update the checksum in emby table
+            emby_db.updateReference(itemid, checksum)
+
+        ##### OR ADD THE ARTIST #####
+        else:
+            self.logMsg("ADD artist itemid: %s - Name: %s" % (itemid, name), 1)
+            # safety checks: It looks like Emby supports the same artist multiple times.
+            # Kodi doesn't allow that. In case that happens we just merge the artist entries.
+            artistid = self.kodi_db.addArtist(name, musicBrainzId)
+            # Create the reference in emby table
+            emby_db.addReference(itemid, artistid, artisttype, "artist", checksum=checksum)
+            
+
+        # Process the artist
+        if self.kodiversion in (16, 17):
+            query = ' '.join((
+
+                "UPDATE artist",
+                "SET strGenres = ?, strBiography = ?, strImage = ?, strFanart = ?,",
+                    "lastScraped = ?",
+                "WHERE idArtist = ?"
+            ))
+            kodicursor.execute(query, (genres, bio, thumb, fanart, lastScraped, artistid))
+        else:
+            query = ' '.join((
+
+                "UPDATE artist",
+                "SET strGenres = ?, strBiography = ?, strImage = ?, strFanart = ?,",
+                    "lastScraped = ?, dateAdded = ?",
+                "WHERE idArtist = ?"
+            ))
+            kodicursor.execute(query, (genres, bio, thumb, fanart, lastScraped,
+                    dateadded, artistid))
+
+
+        # Update artwork
+        artwork.addArtwork(artworks, artistid, "artist", kodicursor)
+
+    def add_updateAlbum(self, item):
+        # Process a single artist
+        emby = self.emby
+        kodicursor = self.kodicursor
+        emby_db = self.emby_db
+        artwork = self.artwork
+        API = api.API(item)
+
+        update_item = True
+        itemid = item['Id']
+        emby_dbitem = emby_db.getItem_byId(itemid)
+        try:
+            albumid = emby_dbitem[0]
+        except TypeError:
+            update_item = False
+            self.logMsg("albumid: %s not found." % itemid, 2)
+
+        ##### The album details #####
+        lastScraped = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
+        dateadded = API.getDateCreated()
+        userdata = API.getUserData()
+        checksum = API.getChecksum()
+
+        name = item['Name']
+        musicBrainzId = API.getProvider('MusicBrainzAlbum')
+        year = item.get('ProductionYear')
+        genres = item.get('Genres')
+        genre = " / ".join(genres)
+        bio = API.getOverview()
+        rating = userdata['UserRating']
+        artists = item['AlbumArtists']
+        if not artists:
+            artists = item['ArtistItems']
+        artistname = []
+        for artist in artists:
+            artistname.append(artist['Name'])
+        artistname = " / ".join(artistname)
+
+        # Associate artwork
+        artworks = artwork.getAllArtwork(item, parentInfo=True)
+        thumb = artworks['Primary']
+        if thumb:
+            thumb = "<thumb>%s</thumb>" % thumb
+
+        ##### UPDATE THE ALBUM #####
+        if update_item:
+            self.logMsg("UPDATE album itemid: %s - Name: %s" % (itemid, name), 1)
+            # Update the checksum in emby table
+            emby_db.updateReference(itemid, checksum)
+
+        ##### OR ADD THE ALBUM #####
+        else:
+            self.logMsg("ADD album itemid: %s - Name: %s" % (itemid, name), 1)
+            # safety checks: It looks like Emby supports the same artist multiple times.
+            # Kodi doesn't allow that. In case that happens we just merge the artist entries.
+            albumid = self.kodi_db.addAlbum(name, musicBrainzId)
+            # Create the reference in emby table
+            emby_db.addReference(itemid, albumid, "MusicAlbum", "album", checksum=checksum)
+
+
+        # Process the album info
+        if self.kodiversion == 17:
+            # Kodi Krypton
+            query = ' '.join((
+
+                "UPDATE album",
+                "SET strArtists = ?, iYear = ?, strGenres = ?, strReview = ?, strImage = ?,",
+                    "iUserrating = ?, lastScraped = ?, strReleaseType = ?",
+                "WHERE idAlbum = ?"
+            ))
+            kodicursor.execute(query, (artistname, year, genre, bio, thumb, rating, lastScraped,
+                "album", albumid))
+        elif self.kodiversion == 16:
+            # Kodi Jarvis
+            query = ' '.join((
+
+                "UPDATE album",
+                "SET strArtists = ?, iYear = ?, strGenres = ?, strReview = ?, strImage = ?,",
+                    "iRating = ?, lastScraped = ?, strReleaseType = ?",
+                "WHERE idAlbum = ?"
+            ))
+            kodicursor.execute(query, (artistname, year, genre, bio, thumb, rating, lastScraped,
+                "album", albumid))
+        elif self.kodiversion == 15:
+            # Kodi Isengard
+            query = ' '.join((
+
+                "UPDATE album",
+                "SET strArtists = ?, iYear = ?, strGenres = ?, strReview = ?, strImage = ?,",
+                    "iRating = ?, lastScraped = ?, dateAdded = ?, strReleaseType = ?",
+                "WHERE idAlbum = ?"
+            ))
+            kodicursor.execute(query, (artistname, year, genre, bio, thumb, rating, lastScraped,
+                dateadded, "album", albumid))
+        else:
+            # Kodi Helix
+            query = ' '.join((
+
+                "UPDATE album",
+                "SET strArtists = ?, iYear = ?, strGenres = ?, strReview = ?, strImage = ?,",
+                    "iRating = ?, lastScraped = ?, dateAdded = ?",
+                "WHERE idAlbum = ?"
+            ))
+            kodicursor.execute(query, (artistname, year, genre, bio, thumb, rating, lastScraped,
+                dateadded, albumid))
+
+        # Associate the parentid for emby reference
+        parentId = item.get('ParentId')
+        if parentId is not None:
+            emby_dbartist = emby_db.getItem_byId(parentId)
+            try:
+                artistid = emby_dbartist[0]
+            except TypeError:
+                # Artist does not exist in emby database.
+                artist = emby.getItem(parentId)
+                # Item may not be an artist, verification necessary.
+                if artist['Type'] == "MusicArtist":
+                    # Update with the parentId, for remove reference
+                    emby_db.addReference(parentId, parentId, "MusicArtist", "artist")
+                    emby_db.updateParentId(itemid, parentId)
+            else:
+                # Update emby reference with the artistid
+                emby_db.updateParentId(itemid, artistid)
+
+        # Assign main artists to album
+        for artist in artists:
+            artistname = artist['Name']
+            artistId = artist['Id']
+            emby_dbartist = emby_db.getItem_byId(artistId)
+            try:
+                artistid = emby_dbartist[0]
+            except TypeError:
+                # Artist does not exist in emby database, create the reference
+                artist = emby.getItem(artistId)
+                self.add_updateArtist(artist, artisttype="AlbumArtist")
+                emby_dbartist = emby_db.getItem_byId(artistId)
+                artistid = emby_dbartist[0]
+            else:
+                # Best take this name over anything else.
+                query = "UPDATE artist SET strArtist = ? WHERE idArtist = ?"
+                kodicursor.execute(query, (artistname, artistid,))
+
+            # Add artist to album
+            query = (
+                '''
+                INSERT OR REPLACE INTO album_artist(idArtist, idAlbum, strArtist)
+
+                VALUES (?, ?, ?)
+                '''
+            )
+            kodicursor.execute(query, (artistid, albumid, artistname))
+            # Update discography
+            query = (
+                '''
+                INSERT OR REPLACE INTO discography(idArtist, strAlbum, strYear)
+
+                VALUES (?, ?, ?)
+                '''
+            )
+            kodicursor.execute(query, (artistid, name, year))
+            # Update emby reference with parentid
+            emby_db.updateParentId(artistId, albumid)
+
+        # Add genres
+        self.kodi_db.addMusicGenres(albumid, genres, "album")
+        # Update artwork
+        artwork.addArtwork(artworks, albumid, "album", kodicursor)
+
+    def add_updateSong(self, item):
+        # Process single song
+        kodicursor = self.kodicursor
+        emby = self.emby
+        emby_db = self.emby_db
+        artwork = self.artwork
+        API = api.API(item)
+
+        update_item = True
+        itemid = item['Id']
+        emby_dbitem = emby_db.getItem_byId(itemid)
+        try:
+            songid = emby_dbitem[0]
+            pathid = emby_dbitem[2]
+            albumid = emby_dbitem[3]
+        except TypeError:
+            update_item = False
+            self.logMsg("songid: %s not found." % itemid, 2)
+            
+        ##### The song details #####
+        checksum = API.getChecksum()
+        dateadded = API.getDateCreated()
+        userdata = API.getUserData()
+        playcount = userdata['PlayCount']
+        dateplayed = userdata['LastPlayedDate']
+
+        # item details
+        title = item['Name']
+        musicBrainzId = API.getProvider('MusicBrainzTrackId')
+        genres = item.get('Genres')
+        genre = " / ".join(genres)
+        artists = " / ".join(item['Artists'])
+        tracknumber = item.get('IndexNumber', 0)
+        disc = item.get('ParentIndexNumber', 1)
+        if disc == 1:
+            track = tracknumber
+        else:
+            track = disc*2**16 + tracknumber
+        year = item.get('ProductionYear')
+        duration = API.getRuntime()
+        rating = userdata['UserRating']
+
+        #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()
+            
+            
+        ##### GET THE FILE AND PATH #####
+        if self.directstream:
+            path = "%s/emby/Audio/%s/" % (self.server, itemid)
+            filename = "stream.mp3"
+        else:
+            playurl = API.getFilePath()
+
+            if "\\" in playurl:
+                # Local path
+                filename = playurl.rsplit("\\", 1)[1]
+            else: # Network share
+                filename = playurl.rsplit("/", 1)[1]
+
+            # Direct paths is set the Kodi way
+            if utils.window('emby_pathverified') != "true" and not xbmcvfs.exists(playurl):
+                # Validate the path is correct with user intervention
+                utils.window('emby_directPath', clear=True)
+                resp = xbmcgui.Dialog().yesno(
+                                        heading="Can't validate path",
+                                        line1=(
+                                            "Kodi can't locate file: %s. Verify the path. "
+                                            "You may to verify your network credentials in the "
+                                            "add-on settings or use the emby path substitution "
+                                            "to format your path correctly. Stop syncing?"
+                                            % playurl))
+                if resp:
+                    utils.window('emby_shouldStop', value="true")
+                    return False
+            
+            path = playurl.replace(filename, "")
+            utils.window('emby_pathverified', value="true")
+
+        ##### UPDATE THE SONG #####
+        if update_item:
+            self.logMsg("UPDATE song itemid: %s - Title: %s" % (itemid, title), 1)
+            
+            # Update path
+            query = "UPDATE path SET strPath = ? WHERE idPath = ?"
+            kodicursor.execute(query, (path, pathid))
+
+            # Update the song entry
+            query = ' '.join((
+                
+                "UPDATE song",
+                "SET idAlbum = ?, strArtists = ?, strGenres = ?, strTitle = ?, iTrack = ?,",
+                    "iDuration = ?, iYear = ?, strFilename = ?, iTimesPlayed = ?, lastplayed = ?,",
+                    "rating = ?, comment = ?",
+                "WHERE idSong = ?"
+            ))
+            kodicursor.execute(query, (albumid, artists, genre, title, track, duration, year,
+                filename, playcount, dateplayed, rating, comment, songid))
+
+            # Update the checksum in emby table
+            emby_db.updateReference(itemid, checksum)
+        
+        ##### OR ADD THE SONG #####
+        else:
+            self.logMsg("ADD song itemid: %s - Title: %s" % (itemid, title), 1)
+            
+            # Add path
+            pathid = self.kodi_db.addPath(path)
+
+            try:
+                # Get the album
+                emby_dbalbum = emby_db.getItem_byId(item['AlbumId'])
+                albumid = emby_dbalbum[0]
+            except KeyError:
+                # Verify if there's an album associated.
+                album_name = item.get('Album')
+                if album_name:
+                    self.logMsg("Creating virtual music album for song: %s." % itemid, 1)
+                    albumid = self.kodi_db.addAlbum(album_name, API.getProvider('MusicBrainzAlbum'))
+                    emby_db.addReference("%salbum%s" % (itemid, albumid), albumid, "MusicAlbum_", "album")
+                else:
+                    # No album Id associated to the song.
+                    self.logMsg("Song itemid: %s has no albumId associated." % itemid, 1)
+                    return False
+
+            except TypeError:
+                # No album found. Let's create it
+                self.logMsg("Album database entry missing.", 1)
+                emby_albumId = item['AlbumId']
+                album = emby.getItem(emby_albumId)
+                self.add_updateAlbum(album)
+                emby_dbalbum = emby_db.getItem_byId(emby_albumId)
+                try:
+                    albumid = emby_dbalbum[0]
+                    self.logMsg("Found albumid: %s" % albumid, 1)
+                except TypeError:
+                    # No album found, create a single's album
+                    self.logMsg("Failed to add album. Creating singles.", 1)
+                    kodicursor.execute("select coalesce(max(idAlbum),0) from album")
+                    albumid = kodicursor.fetchone()[0] + 1
+                    if self.kodiversion == 16:
+                        # Kodi Jarvis
+                        query = (
+                            '''
+                            INSERT INTO album(idAlbum, strGenres, iYear, strReleaseType)
+
+                            VALUES (?, ?, ?, ?)
+                            '''
+                        )
+                        kodicursor.execute(query, (albumid, genre, year, "single"))
+                    elif self.kodiversion == 15:
+                        # Kodi Isengard
+                        query = (
+                            '''
+                            INSERT INTO album(idAlbum, strGenres, iYear, dateAdded, strReleaseType)
+
+                            VALUES (?, ?, ?, ?, ?)
+                            '''
+                        )
+                        kodicursor.execute(query, (albumid, genre, year, dateadded, "single"))
+                    else:
+                        # Kodi Helix
+                        query = (
+                            '''
+                            INSERT INTO album(idAlbum, strGenres, iYear, dateAdded)
+
+                            VALUES (?, ?, ?, ?)
+                            '''
+                        )
+                        kodicursor.execute(query, (albumid, genre, year, dateadded))
+            
+            # Create the song entry
+            kodicursor.execute("select coalesce(max(idSong),0) from song")
+            songid = kodicursor.fetchone()[0] + 1
+            query = (
+                '''
+                INSERT INTO song(
+                    idSong, idAlbum, idPath, strArtists, strGenres, strTitle, iTrack,
+                    iDuration, iYear, strFileName, strMusicBrainzTrackID, iTimesPlayed, lastplayed,
+                    rating)
+
+                VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
+                '''
+            )
+            kodicursor.execute(query, (songid, albumid, pathid, artists, genre, title, track,
+                duration, year, filename, musicBrainzId, playcount, dateplayed, rating))
+
+            # Create the reference in emby table
+            emby_db.addReference(itemid, songid, "Audio", "song", pathid=pathid, parentid=albumid,
+                checksum=checksum)
+
+        
+        # Link song to album
+        query = (
+            '''
+            INSERT OR REPLACE INTO albuminfosong(
+                idAlbumInfoSong, idAlbumInfo, iTrack, strTitle, iDuration)
+            
+            VALUES (?, ?, ?, ?, ?)
+            '''
+        )
+        kodicursor.execute(query, (songid, albumid, track, title, duration))
+        
+        # Link song to artists
+        for index, artist in enumerate(item['ArtistItems']):
+
+            artist_name = artist['Name']
+            artist_eid = artist['Id']
+            artist_edb = emby_db.getItem_byId(artist_eid)
+            try:
+                artistid = artist_edb[0]
+            except TypeError:
+                # Artist is missing from emby database, add it.
+                artist_full = emby.getItem(artist_eid)
+                self.add_updateArtist(artist_full)
+                artist_edb = emby_db.getItem_byId(artist_eid)
+                artistid = artist_edb[0]
+            finally:
+                query = (
+                    '''
+                    INSERT OR REPLACE INTO song_artist(idArtist, idSong, iOrder, strArtist)
+
+                    VALUES (?, ?, ?, ?)
+                    '''
+                )
+                kodicursor.execute(query, (artistid, songid, index, artist_name))
+
+        # Verify if album artist exists
+        album_artists = []
+        for artist in item['AlbumArtists']:
+
+            artist_name = artist['Name']
+            album_artists.append(artist_name)
+            artist_eid = artist['Id']
+            artist_edb = emby_db.getItem_byId(artist_eid)
+            try:
+                artistid = artist_edb[0]
+            except TypeError:
+                # Artist is missing from emby database, add it.
+                artist_full = emby.getItem(artist_eid)
+                self.add_updateArtist(artist_full)
+                artist_edb = emby_db.getItem_byId(artist_eid)
+                artistid = artist_edb[0]
+            finally:
+                query = (
+                    '''
+                    INSERT OR REPLACE INTO album_artist(idArtist, idAlbum, strArtist)
+
+                    VALUES (?, ?, ?)
+                    '''
+                )
+                kodicursor.execute(query, (artistid, albumid, artist_name))
+                # Update discography
+                if item.get('Album'):
+                    query = (
+                        '''
+                        INSERT OR REPLACE INTO discography(idArtist, strAlbum, strYear)
+
+                        VALUES (?, ?, ?)
+                        '''
+                    )
+                    kodicursor.execute(query, (artistid, item['Album'], 0))
+        else:
+            album_artists = " / ".join(album_artists)
+            query = ' '.join((
+
+                "SELECT strArtists",
+                "FROM album",
+                "WHERE idAlbum = ?"
+            ))
+            kodicursor.execute(query, (albumid,))
+            result = kodicursor.fetchone()
+            if result and result[0] != album_artists:
+                # Field is empty
+                if self.kodiversion in (16, 17):
+                    # Kodi Jarvis, Krypton
+                    query = "UPDATE album SET strArtists = ? WHERE idAlbum = ?"
+                    kodicursor.execute(query, (album_artists, albumid))
+                elif self.kodiversion == 15:
+                    # Kodi Isengard
+                    query = "UPDATE album SET strArtists = ? WHERE idAlbum = ?"
+                    kodicursor.execute(query, (album_artists, albumid))
+                else:
+                    # Kodi Helix
+                    query = "UPDATE album SET strArtists = ? WHERE idAlbum = ?"
+                    kodicursor.execute(query, (album_artists, albumid))
+
+        # Add genres
+        self.kodi_db.addMusicGenres(songid, genres, "song")
+        
+        # Update artwork
+        allart = artwork.getAllArtwork(item, parentInfo=True)
+        if hasEmbeddedCover:
+            allart["Primary"] = "image://music@" + artwork.single_urlencode( playurl )
+        artwork.addArtwork(allart, songid, "song", kodicursor)
+
+        if item.get('AlbumId') is None:
+            # Update album artwork
+            artwork.addArtwork(allart, albumid, "album", kodicursor)
+
+    def updateUserdata(self, item):
+        # This updates: Favorite, LastPlayedDate, Playcount, PlaybackPositionTicks
+        # Poster with progress bar
+        kodicursor = self.kodicursor
+        emby_db = self.emby_db
+        API = api.API(item)
+
+        # Get emby information
+        itemid = item['Id']
+        checksum = API.getChecksum()
+        userdata = API.getUserData()
+        runtime = API.getRuntime()
+        rating = userdata['UserRating']
+
+        # Get Kodi information
+        emby_dbitem = emby_db.getItem_byId(itemid)
+        try:
+            kodiid = emby_dbitem[0]
+            mediatype = emby_dbitem[4]
+            self.logMsg("Update playstate for %s: %s" % (mediatype, item['Name']), 1)
+        except TypeError:
+            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 = ?"
+            kodicursor.execute(query, (playcount, dateplayed, rating, kodiid))
+
+        elif mediatype == "album":
+            # Process playstates
+            query = "UPDATE album SET iRating = ? WHERE idAlbum = ?"
+            kodicursor.execute(query, (rating, kodiid))
+
+        emby_db.updateReference(itemid, checksum)
+
+    def remove(self, itemid):
+        # Remove kodiid, fileid, pathid, emby reference
+        emby_db = self.emby_db
+        kodicursor = self.kodicursor
+        artwork = self.artwork
+
+        emby_dbitem = emby_db.getItem_byId(itemid)
+        try:
+            kodiid = emby_dbitem[0]
+            mediatype = emby_dbitem[4]
+            self.logMsg("Removing %s kodiid: %s" % (mediatype, kodiid), 1)
+        except TypeError:
+            return
+
+        ##### PROCESS ITEM #####
+
+        # Remove the emby reference
+        emby_db.removeItem(itemid)
+
+
+        ##### IF SONG #####
+
+        if mediatype == "song":
+            # Delete song
+            self.removeSong(kodiid)
+            # This should only address single song scenario, where server doesn't actually
+            # create an album for the song. 
+            emby_db.removeWildItem(itemid)
+
+            for item in emby_db.getItem_byWildId(itemid):
+
+                item_kid = item[0]
+                item_mediatype = item[1]
+
+                if item_mediatype == "album":
+                    childs = emby_db.getItem_byParentId(item_kid, "song")
+                    if not childs:
+                        # Delete album
+                        self.removeAlbum(item_kid)
+
+        ##### IF ALBUM #####
+
+        elif mediatype == "album":
+            # Delete songs, album
+            album_songs = emby_db.getItem_byParentId(kodiid, "song")
+            for song in album_songs:
+                self.removeSong(song[1])
+            else:
+                # Remove emby songs
+                emby_db.removeItems_byParentId(kodiid, "song")
+
+            # Remove the album
+            self.removeAlbum(kodiid)
+
+        ##### IF ARTIST #####
+
+        elif mediatype == "artist":
+            # Delete songs, album, artist
+            albums = emby_db.getItem_byParentId(kodiid, "album")
+            for album in albums:
+                albumid = album[1]
+                album_songs = emby_db.getItem_byParentId(albumid, "song")
+                for song in album_songs:
+                    self.removeSong(song[1])
+                else:
+                    # Remove emby song
+                    emby_db.removeItems_byParentId(albumid, "song")
+                    # Remove emby artist
+                    emby_db.removeItems_byParentId(albumid, "artist")
+                    # Remove kodi album
+                    self.removeAlbum(albumid)
+            else:
+                # Remove emby albums
+                emby_db.removeItems_byParentId(kodiid, "album")
+
+            # Remove artist
+            self.removeArtist(kodiid)
+
+        self.logMsg("Deleted %s: %s from kodi database" % (mediatype, itemid), 1)
+
+    def removeSong(self, kodiid):
+
+        kodicursor = self.kodicursor
+
+        self.artwork.deleteArtwork(kodiid, "song", self.kodicursor)
+        self.kodicursor.execute("DELETE FROM song WHERE idSong = ?", (kodiid,))
+
+    def removeAlbum(self, kodiid):
+
+        self.artwork.deleteArtwork(kodiid, "album", self.kodicursor)
+        self.kodicursor.execute("DELETE FROM album WHERE idAlbum = ?", (kodiid,))
+
+    def removeArtist(self, kodiid):
+
+        self.artwork.deleteArtwork(kodiid, "artist", self.kodicursor)
+        self.kodicursor.execute("DELETE FROM artist WHERE idArtist = ?", (kodiid,))
diff --git a/resources/lib/kodidb_functions.py b/resources/lib/kodidb_functions.py
index a7d82a3e..6c3dd8b1 100644
--- a/resources/lib/kodidb_functions.py
+++ b/resources/lib/kodidb_functions.py
@@ -1,1190 +1,1149 @@
-# -*- coding: utf-8 -*-
-
-##################################################################################################
-
-import xbmc
-
-import api
-import artwork
-import clientinfo
-import utils
-
-##################################################################################################
-
-
-class Kodidb_Functions():
-
-    kodiversion = int(xbmc.getInfoLabel("System.BuildVersion")[:2])
-    
-
-    def __init__(self, cursor):
-
-        self.cursor = cursor
-        
-        self.clientInfo = clientinfo.ClientInfo()
-        self.addonName = self.clientInfo.getAddonName()
-        self.artwork = artwork.Artwork()
-
-    def logMsg(self, msg, lvl=1):
-
-        className = self.__class__.__name__
-        utils.logMsg("%s %s" % (self.addonName, className), msg, lvl)
-        
-
-    def addPath(self, path):
-
-        cursor = self.cursor
-
-        query = ' '.join((
-
-            "SELECT idPath",
-            "FROM path",
-            "WHERE strPath = ?"
-        ))
-        cursor.execute(query, (path,))
-        try:
-            pathid = cursor.fetchone()[0]
-        except TypeError:
-            cursor.execute("select coalesce(max(idPath),0) from path")
-            pathid = cursor.fetchone()[0] + 1
-            query = (
-                '''
-                INSERT INTO path(
-                    idPath, strPath)
-
-                VALUES (?, ?)
-                '''
-            )
-            cursor.execute(query, (pathid, path))
-
-        return pathid
-
-    def getPath(self, path):
-
-        cursor = self.cursor
-
-        query = ' '.join((
-
-            "SELECT idPath",
-            "FROM path",
-            "WHERE strPath = ?"
-        ))
-        cursor.execute(query, (path,))
-        try:
-            pathid = cursor.fetchone()[0]
-        except TypeError:
-            pathid = None
-
-        return pathid
-
-    def addFile(self, filename, pathid):
-
-        cursor = self.cursor
-
-        query = ' '.join((
-
-            "SELECT idFile",
-            "FROM files",
-            "WHERE strFilename = ?",
-            "AND idPath = ?"
-        ))
-        cursor.execute(query, (filename, pathid,))
-        try:
-            fileid = cursor.fetchone()[0]
-        except TypeError:
-            cursor.execute("select coalesce(max(idFile),0) from files")
-            fileid = cursor.fetchone()[0] + 1
-            query = (
-                '''
-                INSERT INTO files(
-                    idFile, strFilename)
-
-                VALUES (?, ?)
-                '''
-            )
-            cursor.execute(query, (fileid, filename))
-
-        return fileid
-
-    def getFile(self, fileid):
-
-        cursor = self.cursor
-
-        query = ' '.join((
-
-            "SELECT strFilename",
-            "FROM files",
-            "WHERE idFile = ?"
-        ))
-        cursor.execute(query, (fileid,))
-        try:
-            filename = cursor.fetchone()[0]
-        except TypeError:
-            filename = ""
-
-        return filename
-
-    def removeFile(self, path, filename):
-        
-        pathid = self.getPath(path)
-
-        if pathid is not None:
-            query = ' '.join((
-
-                "DELETE FROM files",
-                "WHERE idPath = ?",
-                "AND strFilename = ?"
-            ))
-            self.cursor.execute(query, (pathid, filename,))
-
-    def addCountries(self, kodiid, countries, mediatype):
-        
-        cursor = self.cursor
-        
-        if self.kodiversion in (15, 16, 17):
-            # Kodi Isengard, Jarvis, Krypton
-            for country in countries:
-                query = ' '.join((
-
-                    "SELECT country_id",
-                    "FROM country",
-                    "WHERE name = ?",
-                    "COLLATE NOCASE"
-                ))
-                cursor.execute(query, (country,))
-
-                try:
-                    country_id = cursor.fetchone()[0]
-
-                except TypeError:
-                    # Country entry does not exists
-                    cursor.execute("select coalesce(max(country_id),0) from country")
-                    country_id = cursor.fetchone()[0] + 1
-
-                    query = "INSERT INTO country(country_id, name) values(?, ?)"
-                    cursor.execute(query, (country_id, country))
-                    self.logMsg("Add country to media, processing: %s" % country, 2)
-
-                finally: # Assign country to content
-                    query = (
-                        '''
-                        INSERT OR REPLACE INTO country_link(
-                            country_id, media_id, media_type)
-                        
-                        VALUES (?, ?, ?)
-                        '''
-                    )
-                    cursor.execute(query, (country_id, kodiid, mediatype))
-        else:
-            # Kodi Helix
-            for country in countries:
-                query = ' '.join((
-
-                    "SELECT idCountry",
-                    "FROM country",
-                    "WHERE strCountry = ?",
-                    "COLLATE NOCASE"
-                ))
-                cursor.execute(query, (country,))
-
-                try:
-                    idCountry = cursor.fetchone()[0]
-                
-                except TypeError:
-                    # Country entry does not exists
-                    cursor.execute("select coalesce(max(idCountry),0) from country")
-                    idCountry = cursor.fetchone()[0] + 1
-
-                    query = "INSERT INTO country(idCountry, strCountry) values(?, ?)"
-                    cursor.execute(query, (idCountry, country))
-                    self.logMsg("Add country to media, processing: %s" % country, 2)
-                
-                finally:
-                    # Only movies have a country field
-                    if "movie" in mediatype:
-                        query = (
-                            '''
-                            INSERT OR REPLACE INTO countrylinkmovie(
-                                idCountry, idMovie)
-
-                            VALUES (?, ?)
-                            '''
-                        )
-                        cursor.execute(query, (idCountry, kodiid))
-
-    def addPeople(self, kodiid, people, mediatype):
-        
-        cursor = self.cursor
-        artwork = self.artwork
-        kodiversion = self.kodiversion
-
-        castorder = 1
-        for person in people:
-
-            name = person['Name']
-            type = person['Type']
-            thumb = person['imageurl']
-            
-            # Kodi Isengard, Jarvis, Krypton
-            if kodiversion in (15, 16, 17):
-                query = ' '.join((
-
-                    "SELECT actor_id",
-                    "FROM actor",
-                    "WHERE name = ?",
-                    "COLLATE NOCASE"
-                ))
-                cursor.execute(query, (name,))
-                
-                try:
-                    actorid = cursor.fetchone()[0]
-
-                except TypeError:
-                    # Cast entry does not exists
-                    cursor.execute("select coalesce(max(actor_id),0) from actor")
-                    actorid = cursor.fetchone()[0] + 1
-
-                    query = "INSERT INTO actor(actor_id, name) values(?, ?)"
-                    cursor.execute(query, (actorid, name))
-                    self.logMsg("Add people to media, processing: %s" % name, 2)
-
-                finally:
-                    # Link person to content
-                    if "Actor" in type:
-                        role = person.get('Role')
-                        query = (
-                            '''
-                            INSERT OR REPLACE INTO actor_link(
-                                actor_id, media_id, media_type, role, cast_order)
-
-                            VALUES (?, ?, ?, ?, ?)
-                            '''
-                        )
-                        cursor.execute(query, (actorid, kodiid, mediatype, role, castorder))
-                        castorder += 1
-                    
-                    elif "Director" in type:
-                        query = (
-                            '''
-                            INSERT OR REPLACE INTO director_link(
-                                actor_id, media_id, media_type)
-
-                            VALUES (?, ?, ?)
-                            '''
-                        )
-                        cursor.execute(query, (actorid, kodiid, mediatype))
-                    
-                    elif type in ("Writing", "Writer"):
-                        query = (
-                            '''
-                            INSERT OR REPLACE INTO writer_link(
-                                actor_id, media_id, media_type)
-
-                            VALUES (?, ?, ?)
-                            '''
-                        )
-                        cursor.execute(query, (actorid, kodiid, mediatype))
-
-                    elif "Artist" in type:
-                        query = (
-                            '''
-                            INSERT OR REPLACE INTO actor_link(
-                                actor_id, media_id, media_type)
-                            
-                            VALUES (?, ?, ?)
-                            '''
-                        )
-                        cursor.execute(query, (actorid, kodiid, mediatype))
-            # Kodi Helix
-            else:
-                query = ' '.join((
-
-                    "SELECT idActor",
-                    "FROM actors",
-                    "WHERE strActor = ?",
-                    "COLLATE NOCASE"
-                ))
-                cursor.execute(query, (name,))
-                
-                try:
-                    actorid = cursor.fetchone()[0]
-
-                except TypeError:
-                    # Cast entry does not exists
-                    cursor.execute("select coalesce(max(idActor),0) from actors")
-                    actorid = cursor.fetchone()[0] + 1
-
-                    query = "INSERT INTO actors(idActor, strActor) values(?, ?)"
-                    cursor.execute(query, (actorid, name))
-                    self.logMsg("Add people to media, processing: %s" % name, 2)
-
-                finally:
-                    # Link person to content
-                    if "Actor" in type:
-                        role = person.get('Role')
-
-                        if "movie" in mediatype:
-                            query = (
-                                '''
-                                INSERT OR REPLACE INTO actorlinkmovie(
-                                    idActor, idMovie, strRole, iOrder)
-
-                                VALUES (?, ?, ?, ?)
-                                '''
-                            )
-                        elif "tvshow" in mediatype:
-                            query = (
-                                '''
-                                INSERT OR REPLACE INTO actorlinktvshow(
-                                    idActor, idShow, strRole, iOrder)
-
-                                VALUES (?, ?, ?, ?)
-                                '''
-                            )
-                        elif "episode" in mediatype:
-                            query = (
-                                '''
-                                INSERT OR REPLACE INTO actorlinkepisode(
-                                    idActor, idEpisode, strRole, iOrder)
-
-                                VALUES (?, ?, ?, ?)
-                                '''
-                            )
-                        else: return # Item is invalid
-                            
-                        cursor.execute(query, (actorid, kodiid, role, castorder))
-                        castorder += 1
-
-                    elif "Director" in type:
-                        if "movie" in mediatype:
-                            query = (
-                                '''
-                                INSERT OR REPLACE INTO directorlinkmovie(
-                                    idDirector, idMovie)
-
-                                VALUES (?, ?)
-                                '''
-                            )
-                        elif "tvshow" in mediatype:
-                            query = (
-                                '''
-                                INSERT OR REPLACE INTO directorlinktvshow(
-                                    idDirector, idShow)
-
-                                VALUES (?, ?)
-                                '''
-                            )
-                        elif "musicvideo" in mediatype:
-                            query = (
-                                '''
-                                INSERT OR REPLACE INTO directorlinkmusicvideo(
-                                    idDirector, idMVideo)
-
-                                VALUES (?, ?)
-                                '''
-                            )
-
-                        elif "episode" in mediatype:
-                            query = (
-                                '''
-                                INSERT OR REPLACE INTO directorlinkepisode(
-                                    idDirector, idEpisode)
-
-                                VALUES (?, ?)
-                                '''
-                            )
-                        else: return # Item is invalid
-
-                        cursor.execute(query, (actorid, kodiid))
-
-                    elif type in ("Writing", "Writer"):
-                        if "movie" in mediatype:
-                            query = (
-                                '''
-                                INSERT OR REPLACE INTO writerlinkmovie(
-                                    idWriter, idMovie)
-
-                                VALUES (?, ?)
-                                '''
-                            )
-                        elif "episode" in mediatype:
-                            query = (
-                                '''
-                                INSERT OR REPLACE INTO writerlinkepisode(
-                                    idWriter, idEpisode)
-
-                                VALUES (?, ?)
-                                '''
-                            )
-                        else: return # Item is invalid
-                            
-                        cursor.execute(query, (actorid, kodiid))
-
-                    elif "Artist" in type:
-                        query = (
-                            '''
-                            INSERT OR REPLACE INTO artistlinkmusicvideo(
-                                idArtist, idMVideo)
-                            
-                            VALUES (?, ?)
-                            '''
-                        )
-                        cursor.execute(query, (actorid, kodiid))
-
-            # Add person image to art table
-            if thumb:
-                arttype = type.lower()
-
-                if "writing" in arttype:
-                    arttype = "writer"
-
-                artwork.addOrUpdateArt(thumb, actorid, arttype, "thumb", cursor)
-
-    def addGenres(self, kodiid, genres, mediatype):
-
-        cursor = self.cursor
-        
-        # Kodi Isengard, Jarvis, Krypton
-        if self.kodiversion in (15, 16, 17):
-            # Delete current genres for clean slate
-            query = ' '.join((
-
-                "DELETE FROM genre_link",
-                "WHERE media_id = ?",
-                "AND media_type = ?"
-            ))
-            cursor.execute(query, (kodiid, mediatype,))
-
-            # Add genres
-            for genre in genres:
-                
-                query = ' '.join((
-
-                    "SELECT genre_id",
-                    "FROM genre",
-                    "WHERE name = ?",
-                    "COLLATE NOCASE"
-                ))
-                cursor.execute(query, (genre,))
-                
-                try:
-                    genre_id = cursor.fetchone()[0]
-                
-                except TypeError:
-                    # Create genre in database
-                    cursor.execute("select coalesce(max(genre_id),0) from genre")
-                    genre_id = cursor.fetchone()[0] + 1
-                    
-                    query = "INSERT INTO genre(genre_id, name) values(?, ?)"
-                    cursor.execute(query, (genre_id, genre))
-                    self.logMsg("Add Genres to media, processing: %s" % genre, 2)
-                
-                finally:
-                    # Assign genre to item
-                    query = (
-                        '''
-                        INSERT OR REPLACE INTO genre_link(
-                            genre_id, media_id, media_type)
-
-                        VALUES (?, ?, ?)
-                        '''
-                    )
-                    cursor.execute(query, (genre_id, kodiid, mediatype))
-        else:
-            # Kodi Helix
-            # Delete current genres for clean slate
-            if "movie" in mediatype:
-                cursor.execute("DELETE FROM genrelinkmovie WHERE idMovie = ?", (kodiid,))
-            elif "tvshow" in mediatype:
-                cursor.execute("DELETE FROM genrelinktvshow WHERE idShow = ?", (kodiid,))
-            elif "musicvideo" in mediatype:
-                cursor.execute("DELETE FROM genrelinkmusicvideo WHERE idMVideo = ?", (kodiid,))
-
-            # Add genres
-            for genre in genres:
-
-                query = ' '.join((
-
-                    "SELECT idGenre",
-                    "FROM genre",
-                    "WHERE strGenre = ?",
-                    "COLLATE NOCASE"
-                ))
-                cursor.execute(query, (genre,))
-                
-                try:
-                    idGenre = cursor.fetchone()[0]
-                
-                except TypeError:
-                    # Create genre in database
-                    cursor.execute("select coalesce(max(idGenre),0) from genre")
-                    idGenre = cursor.fetchone()[0] + 1
-
-                    query = "INSERT INTO genre(idGenre, strGenre) values(?, ?)"
-                    cursor.execute(query, (idGenre, genre))
-                    self.logMsg("Add Genres to media, processing: %s" % genre, 2)
-                
-                finally:
-                    # Assign genre to item
-                    if "movie" in mediatype:
-                        query = (
-                            '''
-                            INSERT OR REPLACE into genrelinkmovie(
-                                idGenre, idMovie)
-
-                            VALUES (?, ?)
-                            '''
-                        )
-                    elif "tvshow" in mediatype:
-                        query = (
-                            '''
-                            INSERT OR REPLACE into genrelinktvshow(
-                                idGenre, idShow)
-
-                            VALUES (?, ?)
-                            '''
-                        )
-                    elif "musicvideo" in mediatype:
-                        query = (
-                            '''
-                            INSERT OR REPLACE into genrelinkmusicvideo(
-                                idGenre, idMVideo)
-
-                            VALUES (?, ?)
-                            '''
-                        )
-                    else: return # Item is invalid
-                        
-                    cursor.execute(query, (idGenre, kodiid))
-
-    def addStudios(self, kodiid, studios, mediatype):
-
-        cursor = self.cursor
-        kodiversion = self.kodiversion
-
-        for studio in studios:
-
-            if kodiversion in (15, 16, 17):
-                # Kodi Isengard, Jarvis, Krypton
-                query = ' '.join((
-
-                    "SELECT studio_id",
-                    "FROM studio",
-                    "WHERE name = ?",
-                    "COLLATE NOCASE"
-                ))
-                cursor.execute(query, (studio,))
-                try:
-                    studioid = cursor.fetchone()[0]
-                
-                except TypeError:
-                    # Studio does not exists.
-                    cursor.execute("select coalesce(max(studio_id),0) from studio")
-                    studioid = cursor.fetchone()[0] + 1
-
-                    query = "INSERT INTO studio(studio_id, name) values(?, ?)"
-                    cursor.execute(query, (studioid, studio))
-                    self.logMsg("Add Studios to media, processing: %s" % studio, 2)
-
-                finally: # Assign studio to item
-                    query = (
-                        '''
-                        INSERT OR REPLACE INTO studio_link(
-                            studio_id, media_id, media_type)
-                        
-                        VALUES (?, ?, ?)
-                        ''')
-                    cursor.execute(query, (studioid, kodiid, mediatype))
-            else:
-                # Kodi Helix
-                query = ' '.join((
-
-                    "SELECT idstudio",
-                    "FROM studio",
-                    "WHERE strstudio = ?",
-                    "COLLATE NOCASE"
-                ))
-                cursor.execute(query, (studio,))
-                try:
-                    studioid = cursor.fetchone()[0]
-
-                except TypeError:
-                    # Studio does not exists.
-                    cursor.execute("select coalesce(max(idstudio),0) from studio")
-                    studioid = cursor.fetchone()[0] + 1
-
-                    query = "INSERT INTO studio(idstudio, strstudio) values(?, ?)"
-                    cursor.execute(query, (studioid, studio))
-                    self.logMsg("Add Studios to media, processing: %s" % studio, 2)
-
-                finally: # Assign studio to item
-                    if "movie" in mediatype:
-                        query = (
-                            '''
-                            INSERT OR REPLACE INTO studiolinkmovie(idstudio, idMovie) 
-                            VALUES (?, ?)
-                            ''')
-                    elif "musicvideo" in mediatype:
-                        query = (
-                            '''
-                            INSERT OR REPLACE INTO studiolinkmusicvideo(idstudio, idMVideo) 
-                            VALUES (?, ?)
-                            ''')
-                    elif "tvshow" in mediatype:
-                        query = (
-                            '''
-                            INSERT OR REPLACE INTO studiolinktvshow(idstudio, idShow) 
-                            VALUES (?, ?)
-                            ''')
-                    elif "episode" in mediatype:
-                        query = (
-                            '''
-                            INSERT OR REPLACE INTO studiolinkepisode(idstudio, idEpisode) 
-                            VALUES (?, ?)
-                            ''')
-                    cursor.execute(query, (studioid, kodiid))
-
-    def addStreams(self, fileid, streamdetails, runtime):
-        
-        cursor = self.cursor
-
-        # First remove any existing entries
-        cursor.execute("DELETE FROM streamdetails WHERE idFile = ?", (fileid,))
-        if streamdetails:
-            # Video details
-            for videotrack in streamdetails['video']:
-                query = (
-                    '''
-                    INSERT INTO streamdetails(
-                        idFile, iStreamType, strVideoCodec, fVideoAspect, 
-                        iVideoWidth, iVideoHeight, iVideoDuration ,strStereoMode)
-                    
-                    VALUES (?, ?, ?, ?, ?, ?, ?, ?)
-                    '''
-                )
-                cursor.execute(query, (fileid, 0, videotrack['codec'],
-                    videotrack['aspect'], videotrack['width'], videotrack['height'],
-                    runtime ,videotrack['video3DFormat']))
-            
-            # Audio details
-            for audiotrack in streamdetails['audio']:
-                query = (
-                    '''
-                    INSERT INTO streamdetails(
-                        idFile, iStreamType, strAudioCodec, iAudioChannels, strAudioLanguage)
-                    
-                    VALUES (?, ?, ?, ?, ?)
-                    '''
-                )
-                cursor.execute(query, (fileid, 1, audiotrack['codec'],
-                    audiotrack['channels'], audiotrack['language']))
-
-            # Subtitles details
-            for subtitletrack in streamdetails['subtitle']:
-                query = (
-                    '''
-                    INSERT INTO streamdetails(
-                        idFile, iStreamType, strSubtitleLanguage)
-
-                    VALUES (?, ?, ?)
-                    '''
-                )
-                cursor.execute(query, (fileid, 2, subtitletrack))
-
-    def addPlaystate(self, fileid, resume_seconds, total_seconds, playcount, dateplayed):
-        
-        cursor = self.cursor
-
-        # Delete existing resume point
-        query = ' '.join((
-
-            "DELETE FROM bookmark",
-            "WHERE idFile = ?"
-        ))
-        cursor.execute(query, (fileid,))
-        
-        # Set watched count
-        query = ' '.join((
-
-            "UPDATE files",
-            "SET playCount = ?, lastPlayed = ?",
-            "WHERE idFile = ?"
-        ))
-        cursor.execute(query, (playcount, dateplayed, fileid))
-        
-        # Set the resume bookmark
-        if resume_seconds:
-            cursor.execute("select coalesce(max(idBookmark),0) from bookmark")
-            bookmarkId =  cursor.fetchone()[0] + 1
-            query = (
-                '''
-                INSERT INTO bookmark(
-                    idBookmark, idFile, timeInSeconds, totalTimeInSeconds, player, type)
-                
-                VALUES (?, ?, ?, ?, ?, ?)
-                '''
-            )
-            cursor.execute(query, (bookmarkId, fileid, resume_seconds, total_seconds,
-                "DVDPlayer", 1))
-
-    def addTags(self, kodiid, tags, mediatype):
-
-        cursor = self.cursor
-        
-        # First, delete any existing tags associated to the id
-        if self.kodiversion in (15, 16, 17):
-            # Kodi Isengard, Jarvis, Krypton
-            query = ' '.join((
-
-                "DELETE FROM tag_link",
-                "WHERE media_id = ?",
-                "AND media_type = ?"
-            ))
-            cursor.execute(query, (kodiid, mediatype))
-        else:
-            # Kodi Helix
-            query = ' '.join((
-
-                "DELETE FROM taglinks",
-                "WHERE idMedia = ?",
-                "AND media_type = ?"
-            ))
-            cursor.execute(query, (kodiid, mediatype))
-    
-        # Add tags
-        self.logMsg("Adding Tags: %s" % tags, 2)
-        for tag in tags:
-            self.addTag(kodiid, tag, mediatype)
-
-    def addTag(self, kodiid, tag, mediatype):
-
-        cursor = self.cursor
-
-        if self.kodiversion in (15, 16, 17):
-            # Kodi Isengard, Jarvis, Krypton
-            query = ' '.join((
-
-                "SELECT tag_id",
-                "FROM tag",
-                "WHERE name = ?",
-                "COLLATE NOCASE"
-            ))
-            cursor.execute(query, (tag,))
-            try:
-                tag_id = cursor.fetchone()[0]
-            
-            except TypeError:
-                # Create the tag, because it does not exist
-                tag_id = self.createTag(tag)
-                self.logMsg("Adding tag: %s" % tag, 2)
-
-            finally:
-                # Assign tag to item
-                query = (
-                    '''
-                    INSERT OR REPLACE INTO tag_link(
-                        tag_id, media_id, media_type)
-                    
-                    VALUES (?, ?, ?)
-                    '''
-                )
-                cursor.execute(query, (tag_id, kodiid, mediatype))
-        else:
-            # Kodi Helix
-            query = ' '.join((
-
-                "SELECT idTag",
-                "FROM tag",
-                "WHERE strTag = ?",
-                "COLLATE NOCASE"
-            ))
-            cursor.execute(query, (tag,))
-            try:
-                tag_id = cursor.fetchone()[0]
-            
-            except TypeError:
-                # Create the tag
-                tag_id = self.createTag(tag)
-                self.logMsg("Adding tag: %s" % tag, 2)
-            
-            finally:
-                # Assign tag to item
-                query = (
-                    '''
-                    INSERT OR REPLACE INTO taglinks(
-                        idTag, idMedia, media_type)
-                    
-                    VALUES (?, ?, ?)
-                    '''
-                )
-                cursor.execute(query, (tag_id, kodiid, mediatype))
-
-    def createTag(self, name):
-        
-        cursor = self.cursor
-
-        # This will create and return the tag_id
-        if self.kodiversion in (15, 16, 17):
-            # Kodi Isengard, Jarvis, Krypton
-            query = ' '.join((
-
-                "SELECT tag_id",
-                "FROM tag",
-                "WHERE name = ?",
-                "COLLATE NOCASE"
-            ))
-            cursor.execute(query, (name,))
-            try:
-                tag_id = cursor.fetchone()[0]
-            
-            except TypeError:
-                cursor.execute("select coalesce(max(tag_id),0) from tag")
-                tag_id = cursor.fetchone()[0] + 1
-
-                query = "INSERT INTO tag(tag_id, name) values(?, ?)"
-                cursor.execute(query, (tag_id, name))
-                self.logMsg("Create tag_id: %s name: %s" % (tag_id, name), 2)
-        else:
-            # Kodi Helix
-            query = ' '.join((
-
-                "SELECT idTag",
-                "FROM tag",
-                "WHERE strTag = ?",
-                "COLLATE NOCASE"
-            ))
-            cursor.execute(query, (name,))
-            try:
-                tag_id = cursor.fetchone()[0]
-
-            except TypeError:
-                cursor.execute("select coalesce(max(idTag),0) from tag")
-                tag_id = cursor.fetchone()[0] + 1
-
-                query = "INSERT INTO tag(idTag, strTag) values(?, ?)"
-                cursor.execute(query, (tag_id, name))
-                self.logMsg("Create idTag: %s name: %s" % (tag_id, name), 2)
-
-        return tag_id
-
-    def updateTag(self, oldtag, newtag, kodiid, mediatype):
-
-        cursor = self.cursor
-        self.logMsg("Updating: %s with %s for %s: %s" % (oldtag, newtag, mediatype, kodiid), 2)
-        
-        if self.kodiversion in (15, 16, 17):
-            # Kodi Isengard, Jarvis, Krypton
-            try: 
-                query = ' '.join((
-
-                    "UPDATE tag_link",
-                    "SET tag_id = ?",
-                    "WHERE media_id = ?",
-                    "AND media_type = ?",
-                    "AND tag_id = ?"
-                ))
-                cursor.execute(query, (newtag, kodiid, mediatype, oldtag,))
-            except Exception as e:
-                # The new tag we are going to apply already exists for this item
-                # delete current tag instead
-                self.logMsg("Exception: %s" % e, 1)
-                query = ' '.join((
-
-                    "DELETE FROM tag_link",
-                    "WHERE media_id = ?",
-                    "AND media_type = ?",
-                    "AND tag_id = ?"
-                ))
-                cursor.execute(query, (kodiid, mediatype, oldtag,))
-        else:
-            # Kodi Helix
-            try:
-                query = ' '.join((
-
-                    "UPDATE taglinks",
-                    "SET idTag = ?",
-                    "WHERE idMedia = ?",
-                    "AND media_type = ?",
-                    "AND idTag = ?"
-                ))
-                cursor.execute(query, (newtag, kodiid, mediatype, oldtag,))
-            except Exception as e:
-                # The new tag we are going to apply already exists for this item
-                # delete current tag instead
-                self.logMsg("Exception: %s" % e, 1)
-                query = ' '.join((
-
-                    "DELETE FROM taglinks",
-                    "WHERE idMedia = ?",
-                    "AND media_type = ?",
-                    "AND idTag = ?"
-                ))
-                cursor.execute(query, (kodiid, mediatype, oldtag,))
-
-    def removeTag(self, kodiid, tagname, mediatype):
-
-        cursor = self.cursor
-
-        if self.kodiversion in (15, 16, 17):
-            # Kodi Isengard, Jarvis, Krypton
-            query = ' '.join((
-
-                "SELECT tag_id",
-                "FROM tag",
-                "WHERE name = ?",
-                "COLLATE NOCASE"
-            ))
-            cursor.execute(query, (tagname,))
-            try:
-                tag_id = cursor.fetchone()[0]
-            except TypeError:
-                return
-            else:
-                query = ' '.join((
-
-                    "DELETE FROM tag_link",
-                    "WHERE media_id = ?",
-                    "AND media_type = ?",
-                    "AND tag_id = ?"
-                ))
-                cursor.execute(query, (kodiid, mediatype, tag_id,))
-        else:
-            # Kodi Helix
-            query = ' '.join((
-
-                "SELECT idTag",
-                "FROM tag",
-                "WHERE strTag = ?",
-                "COLLATE NOCASE"
-            ))
-            cursor.execute(query, (tagname,))
-            try:
-                tag_id = cursor.fetchone()[0]
-            except TypeError:
-                return
-            else:
-                query = ' '.join((
-
-                    "DELETE FROM taglinks",
-                    "WHERE idMedia = ?",
-                    "AND media_type = ?",
-                    "AND idTag = ?"
-                ))
-                cursor.execute(query, (kodiid, mediatype, tag_id,))
-
-    def createBoxset(self, boxsetname):
-
-        cursor = self.cursor
-        self.logMsg("Adding boxset: %s" % boxsetname, 2)
-        query = ' '.join((
-
-            "SELECT idSet",
-            "FROM sets",
-            "WHERE strSet = ?",
-            "COLLATE NOCASE"
-        ))
-        cursor.execute(query, (boxsetname,))
-        try:
-            setid = cursor.fetchone()[0]
-
-        except TypeError:
-            cursor.execute("select coalesce(max(idSet),0) from sets")
-            setid = cursor.fetchone()[0] + 1
-
-            query = "INSERT INTO sets(idSet, strSet) values(?, ?)"
-            cursor.execute(query, (setid, boxsetname))
-
-        return setid
-
-    def assignBoxset(self, setid, movieid):
-        
-        query = ' '.join((
-
-            "UPDATE movie",
-            "SET idSet = ?",
-            "WHERE idMovie = ?"
-        ))
-        self.cursor.execute(query, (setid, movieid,))
-
-    def removefromBoxset(self, movieid):
-
-        query = ' '.join((
-
-            "UPDATE movie",
-            "SET idSet = null",
-            "WHERE idMovie = ?"
-        ))
-        self.cursor.execute(query, (movieid,))
-
-    def addSeason(self, showid, seasonnumber):
-
-        cursor = self.cursor
-
-        query = ' '.join((
-
-            "SELECT idSeason",
-            "FROM seasons",
-            "WHERE idShow = ?",
-            "AND season = ?"
-        ))
-        cursor.execute(query, (showid, seasonnumber,))
-        try:
-            seasonid = cursor.fetchone()[0]
-        except TypeError:
-            cursor.execute("select coalesce(max(idSeason),0) from seasons")
-            seasonid = cursor.fetchone()[0] + 1
-            query = "INSERT INTO seasons(idSeason, idShow, season) values(?, ?, ?)"
-            cursor.execute(query, (seasonid, showid, seasonnumber))
-
-        return seasonid
-
-    def addArtist(self, name, musicbrainz):
-
-        cursor = self.cursor
-
-        query = ' '.join((
-
-            "SELECT idArtist, strArtist",
-            "FROM artist",
-            "WHERE strMusicBrainzArtistID = ?"
-        ))
-        cursor.execute(query, (musicbrainz,))
-        try:
-            result = cursor.fetchone()
-            artistid = result[0]
-            artistname = result[1]
-
-        except TypeError:
-
-            query = ' '.join((
-
-                "SELECT idArtist",
-                "FROM artist",
-                "WHERE strArtist = ?",
-                "COLLATE NOCASE"
-            ))
-            cursor.execute(query, (name,))
-            try:
-                artistid = cursor.fetchone()[0]
-            except TypeError:
-                cursor.execute("select coalesce(max(idArtist),0) from artist")
-                artistid = cursor.fetchone()[0] + 1
-                query = (
-                    '''
-                    INSERT INTO artist(idArtist, strArtist, strMusicBrainzArtistID)
-
-                    VALUES (?, ?, ?)
-                    '''
-                )
-                cursor.execute(query, (artistid, name, musicbrainz))
-        else:
-            if artistname != name:
-                query = "UPDATE artist SET strArtist = ? WHERE idArtist = ?"
-                cursor.execute(query, (name, artistid,))
-
-        return artistid
-
-    def addAlbum(self, name, musicbrainz):
-
-        kodiversion = self.kodiversion
-        cursor = self.cursor
-
-        query = ' '.join((
-
-            "SELECT idAlbum",
-            "FROM album",
-            "WHERE strMusicBrainzAlbumID = ?"
-        ))
-        cursor.execute(query, (musicbrainz,))
-        try:
-            albumid = cursor.fetchone()[0]
-        except TypeError:
-            # Create the album
-            cursor.execute("select coalesce(max(idAlbum),0) from album")
-            albumid = cursor.fetchone()[0] + 1
-            if kodiversion in (15, 16, 17):
-                query = (
-                    '''
-                    INSERT INTO album(idAlbum, strAlbum, strMusicBrainzAlbumID, strReleaseType)
-
-                    VALUES (?, ?, ?, ?)
-                    '''
-                )
-                cursor.execute(query, (albumid, name, musicbrainz, "album"))
-            else: # Helix
-                query = (
-                    '''
-                    INSERT INTO album(idAlbum, strAlbum, strMusicBrainzAlbumID)
-
-                    VALUES (?, ?, ?)
-                    '''
-                )
-                cursor.execute(query, (albumid, name, musicbrainz))
-
-        return albumid
-
-    def addMusicGenres(self, kodiid, genres, mediatype):
-
-        cursor = self.cursor
-
-        if mediatype == "album":
-
-            # Delete current genres for clean slate
-            query = ' '.join((
-
-                "DELETE FROM album_genre",
-                "WHERE idAlbum = ?"
-            ))
-            cursor.execute(query, (kodiid,))
-
-            for genre in genres:
-                query = ' '.join((
-
-                    "SELECT idGenre",
-                    "FROM genre",
-                    "WHERE strGenre = ?",
-                    "COLLATE NOCASE"
-                ))
-                cursor.execute(query, (genre,))
-                try:
-                    genreid = cursor.fetchone()[0]
-                except TypeError:
-                    # Create the genre
-                    cursor.execute("select coalesce(max(idGenre),0) from genre")
-                    genreid = cursor.fetchone()[0] + 1
-                    query = "INSERT INTO genre(idGenre, strGenre) values(?, ?)"
-                    cursor.execute(query, (genreid, genre))
-
-                query = "INSERT OR REPLACE INTO album_genre(idGenre, idAlbum) values(?, ?)"
-                cursor.execute(query, (genreid, kodiid))
-
-        elif mediatype == "song":
-            
-            # Delete current genres for clean slate
-            query = ' '.join((
-
-                "DELETE FROM song_genre",
-                "WHERE idSong = ?"
-            ))
-            cursor.execute(query, (kodiid,))
-
-            for genre in genres:
-                query = ' '.join((
-
-                    "SELECT idGenre",
-                    "FROM genre",
-                    "WHERE strGenre = ?",
-                    "COLLATE NOCASE"
-                ))
-                cursor.execute(query, (genre,))
-                try:
-                    genreid = cursor.fetchone()[0]
-                except TypeError:
-                    # Create the genre
-                    cursor.execute("select coalesce(max(idGenre),0) from genre")
-                    genreid = cursor.fetchone()[0] + 1
-                    query = "INSERT INTO genre(idGenre, strGenre) values(?, ?)"
-                    cursor.execute(query, (genreid, genre))
-
-                query = "INSERT OR REPLACE INTO song_genre(idGenre, idSong) values(?, ?)"
-                cursor.execute(query, (genreid, kodiid))
\ No newline at end of file
+# -*- coding: utf-8 -*-
+
+##################################################################################################
+
+import xbmc
+
+import api
+import artwork
+import clientinfo
+import utils
+
+##################################################################################################
+
+
+class Kodidb_Functions():
+
+    kodiversion = int(xbmc.getInfoLabel("System.BuildVersion")[:2])
+    
+
+    def __init__(self, cursor):
+
+        self.cursor = cursor
+        
+        self.clientInfo = clientinfo.ClientInfo()
+        self.addonName = self.clientInfo.getAddonName()
+        self.artwork = artwork.Artwork()
+
+    def logMsg(self, msg, lvl=1):
+
+        className = self.__class__.__name__
+        utils.logMsg("%s %s" % (self.addonName, className), msg, lvl)
+        
+
+    def addPath(self, path):
+
+        query = ' '.join((
+
+            "SELECT idPath",
+            "FROM path",
+            "WHERE strPath = ?"
+        ))
+        self.cursor.execute(query, (path,))
+        try:
+            pathid = self.cursor.fetchone()[0]
+        except TypeError:
+            self.cursor.execute("select coalesce(max(idPath),0) from path")
+            pathid = self.cursor.fetchone()[0] + 1
+            query = (
+                '''
+                INSERT INTO path(
+                    idPath, strPath)
+
+                VALUES (?, ?)
+                '''
+            )
+            self.cursor.execute(query, (pathid, path))
+
+        return pathid
+
+    def getPath(self, path):
+
+        query = ' '.join((
+
+            "SELECT idPath",
+            "FROM path",
+            "WHERE strPath = ?"
+        ))
+        self.cursor.execute(query, (path,))
+        try:
+            pathid = self.cursor.fetchone()[0]
+        except TypeError:
+            pathid = None
+
+        return pathid
+
+    def addFile(self, filename, pathid):
+
+        query = ' '.join((
+
+            "SELECT idFile",
+            "FROM files",
+            "WHERE strFilename = ?",
+            "AND idPath = ?"
+        ))
+        self.cursor.execute(query, (filename, pathid,))
+        try:
+            fileid = self.cursor.fetchone()[0]
+        except TypeError:
+            self.cursor.execute("select coalesce(max(idFile),0) from files")
+            fileid = self.cursor.fetchone()[0] + 1
+            query = (
+                '''
+                INSERT INTO files(
+                    idFile, strFilename)
+
+                VALUES (?, ?)
+                '''
+            )
+            self.cursor.execute(query, (fileid, filename))
+
+        return fileid
+
+    def getFile(self, fileid):
+
+        query = ' '.join((
+
+            "SELECT strFilename",
+            "FROM files",
+            "WHERE idFile = ?"
+        ))
+        self.cursor.execute(query, (fileid,))
+        try:
+            filename = self.cursor.fetchone()[0]
+        except TypeError:
+            filename = ""
+
+        return filename
+
+    def removeFile(self, path, filename):
+        
+        pathid = self.getPath(path)
+
+        if pathid is not None:
+            query = ' '.join((
+
+                "DELETE FROM files",
+                "WHERE idPath = ?",
+                "AND strFilename = ?"
+            ))
+            self.cursor.execute(query, (pathid, filename,))
+
+    def addCountries(self, kodiid, countries, mediatype):
+        
+        if self.kodiversion in (15, 16, 17):
+            # Kodi Isengard, Jarvis, Krypton
+            for country in countries:
+                query = ' '.join((
+
+                    "SELECT country_id",
+                    "FROM country",
+                    "WHERE name = ?",
+                    "COLLATE NOCASE"
+                ))
+                self.cursor.execute(query, (country,))
+
+                try:
+                    country_id = self.cursor.fetchone()[0]
+
+                except TypeError:
+                    # Country entry does not exists
+                    self.cursor.execute("select coalesce(max(country_id),0) from country")
+                    country_id = self.cursor.fetchone()[0] + 1
+
+                    query = "INSERT INTO country(country_id, name) values(?, ?)"
+                    self.cursor.execute(query, (country_id, country))
+                    self.logMsg("Add country to media, processing: %s" % country, 2)
+
+                finally: # Assign country to content
+                    query = (
+                        '''
+                        INSERT OR REPLACE INTO country_link(
+                            country_id, media_id, media_type)
+                        
+                        VALUES (?, ?, ?)
+                        '''
+                    )
+                    self.cursor.execute(query, (country_id, kodiid, mediatype))
+        else:
+            # Kodi Helix
+            for country in countries:
+                query = ' '.join((
+
+                    "SELECT idCountry",
+                    "FROM country",
+                    "WHERE strCountry = ?",
+                    "COLLATE NOCASE"
+                ))
+                self.cursor.execute(query, (country,))
+
+                try:
+                    idCountry = self.cursor.fetchone()[0]
+                
+                except TypeError:
+                    # Country entry does not exists
+                    self.cursor.execute("select coalesce(max(idCountry),0) from country")
+                    idCountry = self.cursor.fetchone()[0] + 1
+
+                    query = "INSERT INTO country(idCountry, strCountry) values(?, ?)"
+                    self.cursor.execute(query, (idCountry, country))
+                    self.logMsg("Add country to media, processing: %s" % country, 2)
+                
+                finally:
+                    # Only movies have a country field
+                    if "movie" in mediatype:
+                        query = (
+                            '''
+                            INSERT OR REPLACE INTO countrylinkmovie(
+                                idCountry, idMovie)
+
+                            VALUES (?, ?)
+                            '''
+                        )
+                        self.cursor.execute(query, (idCountry, kodiid))
+
+    def addPeople(self, kodiid, people, mediatype):
+        
+        castorder = 1
+        for person in people:
+
+            name = person['Name']
+            person_type = person['Type']
+            thumb = person['imageurl']
+            
+            # Kodi Isengard, Jarvis, Krypton
+            if self.kodiversion in (15, 16, 17):
+                query = ' '.join((
+
+                    "SELECT actor_id",
+                    "FROM actor",
+                    "WHERE name = ?",
+                    "COLLATE NOCASE"
+                ))
+                self.cursor.execute(query, (name,))
+                
+                try:
+                    actorid = self.cursor.fetchone()[0]
+
+                except TypeError:
+                    # Cast entry does not exists
+                    self.cursor.execute("select coalesce(max(actor_id),0) from actor")
+                    actorid = self.cursor.fetchone()[0] + 1
+
+                    query = "INSERT INTO actor(actor_id, name) values(?, ?)"
+                    self.cursor.execute(query, (actorid, name))
+                    self.logMsg("Add people to media, processing: %s" % name, 2)
+
+                finally:
+                    # Link person to content
+                    if "Actor" in person_type:
+                        role = person.get('Role')
+                        query = (
+                            '''
+                            INSERT OR REPLACE INTO actor_link(
+                                actor_id, media_id, media_type, role, cast_order)
+
+                            VALUES (?, ?, ?, ?, ?)
+                            '''
+                        )
+                        self.cursor.execute(query, (actorid, kodiid, mediatype, role, castorder))
+                        castorder += 1
+                    
+                    elif "Director" in person_type:
+                        query = (
+                            '''
+                            INSERT OR REPLACE INTO director_link(
+                                actor_id, media_id, media_type)
+
+                            VALUES (?, ?, ?)
+                            '''
+                        )
+                        self.cursor.execute(query, (actorid, kodiid, mediatype))
+                    
+                    elif person_type in ("Writing", "Writer"):
+                        query = (
+                            '''
+                            INSERT OR REPLACE INTO writer_link(
+                                actor_id, media_id, media_type)
+
+                            VALUES (?, ?, ?)
+                            '''
+                        )
+                        self.cursor.execute(query, (actorid, kodiid, mediatype))
+
+                    elif "Artist" in person_type:
+                        query = (
+                            '''
+                            INSERT OR REPLACE INTO actor_link(
+                                actor_id, media_id, media_type)
+                            
+                            VALUES (?, ?, ?)
+                            '''
+                        )
+                        self.cursor.execute(query, (actorid, kodiid, mediatype))
+            # Kodi Helix
+            else:
+                query = ' '.join((
+
+                    "SELECT idActor",
+                    "FROM actors",
+                    "WHERE strActor = ?",
+                    "COLLATE NOCASE"
+                ))
+                self.cursor.execute(query, (name,))
+                
+                try:
+                    actorid = self.cursor.fetchone()[0]
+
+                except TypeError:
+                    # Cast entry does not exists
+                    self.cursor.execute("select coalesce(max(idActor),0) from actors")
+                    actorid = self.cursor.fetchone()[0] + 1
+
+                    query = "INSERT INTO actors(idActor, strActor) values(?, ?)"
+                    self.cursor.execute(query, (actorid, name))
+                    self.logMsg("Add people to media, processing: %s" % name, 2)
+
+                finally:
+                    # Link person to content
+                    if "Actor" in person_type:
+                        role = person.get('Role')
+
+                        if "movie" in mediatype:
+                            query = (
+                                '''
+                                INSERT OR REPLACE INTO actorlinkmovie(
+                                    idActor, idMovie, strRole, iOrder)
+
+                                VALUES (?, ?, ?, ?)
+                                '''
+                            )
+                        elif "tvshow" in mediatype:
+                            query = (
+                                '''
+                                INSERT OR REPLACE INTO actorlinktvshow(
+                                    idActor, idShow, strRole, iOrder)
+
+                                VALUES (?, ?, ?, ?)
+                                '''
+                            )
+                        elif "episode" in mediatype:
+                            query = (
+                                '''
+                                INSERT OR REPLACE INTO actorlinkepisode(
+                                    idActor, idEpisode, strRole, iOrder)
+
+                                VALUES (?, ?, ?, ?)
+                                '''
+                            )
+                        else: return # Item is invalid
+                            
+                        self.cursor.execute(query, (actorid, kodiid, role, castorder))
+                        castorder += 1
+
+                    elif "Director" in person_type:
+                        if "movie" in mediatype:
+                            query = (
+                                '''
+                                INSERT OR REPLACE INTO directorlinkmovie(
+                                    idDirector, idMovie)
+
+                                VALUES (?, ?)
+                                '''
+                            )
+                        elif "tvshow" in mediatype:
+                            query = (
+                                '''
+                                INSERT OR REPLACE INTO directorlinktvshow(
+                                    idDirector, idShow)
+
+                                VALUES (?, ?)
+                                '''
+                            )
+                        elif "musicvideo" in mediatype:
+                            query = (
+                                '''
+                                INSERT OR REPLACE INTO directorlinkmusicvideo(
+                                    idDirector, idMVideo)
+
+                                VALUES (?, ?)
+                                '''
+                            )
+
+                        elif "episode" in mediatype:
+                            query = (
+                                '''
+                                INSERT OR REPLACE INTO directorlinkepisode(
+                                    idDirector, idEpisode)
+
+                                VALUES (?, ?)
+                                '''
+                            )
+                        else: return # Item is invalid
+
+                        self.cursor.execute(query, (actorid, kodiid))
+
+                    elif person_type in ("Writing", "Writer"):
+                        if "movie" in mediatype:
+                            query = (
+                                '''
+                                INSERT OR REPLACE INTO writerlinkmovie(
+                                    idWriter, idMovie)
+
+                                VALUES (?, ?)
+                                '''
+                            )
+                        elif "episode" in mediatype:
+                            query = (
+                                '''
+                                INSERT OR REPLACE INTO writerlinkepisode(
+                                    idWriter, idEpisode)
+
+                                VALUES (?, ?)
+                                '''
+                            )
+                        else: return # Item is invalid
+                            
+                        self.cursor.execute(query, (actorid, kodiid))
+
+                    elif "Artist" in person_type:
+                        query = (
+                            '''
+                            INSERT OR REPLACE INTO artistlinkmusicvideo(
+                                idArtist, idMVideo)
+                            
+                            VALUES (?, ?)
+                            '''
+                        )
+                        self.cursor.execute(query, (actorid, kodiid))
+
+            # Add person image to art table
+            if thumb:
+                arttype = person_type.lower()
+
+                if "writing" in arttype:
+                    arttype = "writer"
+
+                self.artwork.addOrUpdateArt(thumb, actorid, arttype, "thumb", self.cursor)
+
+    def addGenres(self, kodiid, genres, mediatype):
+
+        
+        # Kodi Isengard, Jarvis, Krypton
+        if self.kodiversion in (15, 16, 17):
+            # Delete current genres for clean slate
+            query = ' '.join((
+
+                "DELETE FROM genre_link",
+                "WHERE media_id = ?",
+                "AND media_type = ?"
+            ))
+            self.cursor.execute(query, (kodiid, mediatype,))
+
+            # Add genres
+            for genre in genres:
+                
+                query = ' '.join((
+
+                    "SELECT genre_id",
+                    "FROM genre",
+                    "WHERE name = ?",
+                    "COLLATE NOCASE"
+                ))
+                self.cursor.execute(query, (genre,))
+                
+                try:
+                    genre_id = self.cursor.fetchone()[0]
+                
+                except TypeError:
+                    # Create genre in database
+                    self.cursor.execute("select coalesce(max(genre_id),0) from genre")
+                    genre_id = self.cursor.fetchone()[0] + 1
+                    
+                    query = "INSERT INTO genre(genre_id, name) values(?, ?)"
+                    self.cursor.execute(query, (genre_id, genre))
+                    self.logMsg("Add Genres to media, processing: %s" % genre, 2)
+                
+                finally:
+                    # Assign genre to item
+                    query = (
+                        '''
+                        INSERT OR REPLACE INTO genre_link(
+                            genre_id, media_id, media_type)
+
+                        VALUES (?, ?, ?)
+                        '''
+                    )
+                    self.cursor.execute(query, (genre_id, kodiid, mediatype))
+        else:
+            # Kodi Helix
+            # Delete current genres for clean slate
+            if "movie" in mediatype:
+                self.cursor.execute("DELETE FROM genrelinkmovie WHERE idMovie = ?", (kodiid,))
+            elif "tvshow" in mediatype:
+                self.cursor.execute("DELETE FROM genrelinktvshow WHERE idShow = ?", (kodiid,))
+            elif "musicvideo" in mediatype:
+                self.cursor.execute("DELETE FROM genrelinkmusicvideo WHERE idMVideo = ?", (kodiid,))
+
+            # Add genres
+            for genre in genres:
+
+                query = ' '.join((
+
+                    "SELECT idGenre",
+                    "FROM genre",
+                    "WHERE strGenre = ?",
+                    "COLLATE NOCASE"
+                ))
+                self.cursor.execute(query, (genre,))
+                
+                try:
+                    idGenre = self.cursor.fetchone()[0]
+                
+                except TypeError:
+                    # Create genre in database
+                    self.cursor.execute("select coalesce(max(idGenre),0) from genre")
+                    idGenre = self.cursor.fetchone()[0] + 1
+
+                    query = "INSERT INTO genre(idGenre, strGenre) values(?, ?)"
+                    self.cursor.execute(query, (idGenre, genre))
+                    self.logMsg("Add Genres to media, processing: %s" % genre, 2)
+                
+                finally:
+                    # Assign genre to item
+                    if "movie" in mediatype:
+                        query = (
+                            '''
+                            INSERT OR REPLACE into genrelinkmovie(
+                                idGenre, idMovie)
+
+                            VALUES (?, ?)
+                            '''
+                        )
+                    elif "tvshow" in mediatype:
+                        query = (
+                            '''
+                            INSERT OR REPLACE into genrelinktvshow(
+                                idGenre, idShow)
+
+                            VALUES (?, ?)
+                            '''
+                        )
+                    elif "musicvideo" in mediatype:
+                        query = (
+                            '''
+                            INSERT OR REPLACE into genrelinkmusicvideo(
+                                idGenre, idMVideo)
+
+                            VALUES (?, ?)
+                            '''
+                        )
+                    else: return # Item is invalid
+                        
+                    self.cursor.execute(query, (idGenre, kodiid))
+
+    def addStudios(self, kodiid, studios, mediatype):
+
+        for studio in studios:
+
+            if self.kodiversion in (15, 16, 17):
+                # Kodi Isengard, Jarvis, Krypton
+                query = ' '.join((
+
+                    "SELECT studio_id",
+                    "FROM studio",
+                    "WHERE name = ?",
+                    "COLLATE NOCASE"
+                ))
+                self.cursor.execute(query, (studio,))
+                try:
+                    studioid = self.cursor.fetchone()[0]
+                
+                except TypeError:
+                    # Studio does not exists.
+                    self.cursor.execute("select coalesce(max(studio_id),0) from studio")
+                    studioid = self.cursor.fetchone()[0] + 1
+
+                    query = "INSERT INTO studio(studio_id, name) values(?, ?)"
+                    self.cursor.execute(query, (studioid, studio))
+                    self.logMsg("Add Studios to media, processing: %s" % studio, 2)
+
+                finally: # Assign studio to item
+                    query = (
+                        '''
+                        INSERT OR REPLACE INTO studio_link(
+                            studio_id, media_id, media_type)
+                        
+                        VALUES (?, ?, ?)
+                        ''')
+                    self.cursor.execute(query, (studioid, kodiid, mediatype))
+            else:
+                # Kodi Helix
+                query = ' '.join((
+
+                    "SELECT idstudio",
+                    "FROM studio",
+                    "WHERE strstudio = ?",
+                    "COLLATE NOCASE"
+                ))
+                self.cursor.execute(query, (studio,))
+                try:
+                    studioid = self.cursor.fetchone()[0]
+
+                except TypeError:
+                    # Studio does not exists.
+                    self.cursor.execute("select coalesce(max(idstudio),0) from studio")
+                    studioid = self.cursor.fetchone()[0] + 1
+
+                    query = "INSERT INTO studio(idstudio, strstudio) values(?, ?)"
+                    self.cursor.execute(query, (studioid, studio))
+                    self.logMsg("Add Studios to media, processing: %s" % studio, 2)
+
+                finally: # Assign studio to item
+                    if "movie" in mediatype:
+                        query = (
+                            '''
+                            INSERT OR REPLACE INTO studiolinkmovie(idstudio, idMovie) 
+                            VALUES (?, ?)
+                            ''')
+                    elif "musicvideo" in mediatype:
+                        query = (
+                            '''
+                            INSERT OR REPLACE INTO studiolinkmusicvideo(idstudio, idMVideo) 
+                            VALUES (?, ?)
+                            ''')
+                    elif "tvshow" in mediatype:
+                        query = (
+                            '''
+                            INSERT OR REPLACE INTO studiolinktvshow(idstudio, idShow) 
+                            VALUES (?, ?)
+                            ''')
+                    elif "episode" in mediatype:
+                        query = (
+                            '''
+                            INSERT OR REPLACE INTO studiolinkepisode(idstudio, idEpisode) 
+                            VALUES (?, ?)
+                            ''')
+                    self.cursor.execute(query, (studioid, kodiid))
+
+    def addStreams(self, fileid, streamdetails, runtime):
+        
+        # First remove any existing entries
+        self.cursor.execute("DELETE FROM streamdetails WHERE idFile = ?", (fileid,))
+        if streamdetails:
+            # Video details
+            for videotrack in streamdetails['video']:
+                query = (
+                    '''
+                    INSERT INTO streamdetails(
+                        idFile, iStreamType, strVideoCodec, fVideoAspect, 
+                        iVideoWidth, iVideoHeight, iVideoDuration ,strStereoMode)
+                    
+                    VALUES (?, ?, ?, ?, ?, ?, ?, ?)
+                    '''
+                )
+                self.cursor.execute(query, (fileid, 0, videotrack['codec'],
+                    videotrack['aspect'], videotrack['width'], videotrack['height'],
+                    runtime ,videotrack['video3DFormat']))
+            
+            # Audio details
+            for audiotrack in streamdetails['audio']:
+                query = (
+                    '''
+                    INSERT INTO streamdetails(
+                        idFile, iStreamType, strAudioCodec, iAudioChannels, strAudioLanguage)
+                    
+                    VALUES (?, ?, ?, ?, ?)
+                    '''
+                )
+                self.cursor.execute(query, (fileid, 1, audiotrack['codec'],
+                    audiotrack['channels'], audiotrack['language']))
+
+            # Subtitles details
+            for subtitletrack in streamdetails['subtitle']:
+                query = (
+                    '''
+                    INSERT INTO streamdetails(
+                        idFile, iStreamType, strSubtitleLanguage)
+
+                    VALUES (?, ?, ?)
+                    '''
+                )
+                self.cursor.execute(query, (fileid, 2, subtitletrack))
+
+    def addPlaystate(self, fileid, resume_seconds, total_seconds, playcount, dateplayed):
+        
+        # Delete existing resume point
+        query = ' '.join((
+
+            "DELETE FROM bookmark",
+            "WHERE idFile = ?"
+        ))
+        self.cursor.execute(query, (fileid,))
+        
+        # Set watched count
+        query = ' '.join((
+
+            "UPDATE files",
+            "SET playCount = ?, lastPlayed = ?",
+            "WHERE idFile = ?"
+        ))
+        self.cursor.execute(query, (playcount, dateplayed, fileid))
+        
+        # Set the resume bookmark
+        if resume_seconds:
+            self.cursor.execute("select coalesce(max(idBookmark),0) from bookmark")
+            bookmarkId =  self.cursor.fetchone()[0] + 1
+            query = (
+                '''
+                INSERT INTO bookmark(
+                    idBookmark, idFile, timeInSeconds, totalTimeInSeconds, player, type)
+                
+                VALUES (?, ?, ?, ?, ?, ?)
+                '''
+            )
+            self.cursor.execute(query, (bookmarkId, fileid, resume_seconds, total_seconds,
+                "DVDPlayer", 1))
+
+    def addTags(self, kodiid, tags, mediatype):
+        
+        # First, delete any existing tags associated to the id
+        if self.kodiversion in (15, 16, 17):
+            # Kodi Isengard, Jarvis, Krypton
+            query = ' '.join((
+
+                "DELETE FROM tag_link",
+                "WHERE media_id = ?",
+                "AND media_type = ?"
+            ))
+            self.cursor.execute(query, (kodiid, mediatype))
+        else:
+            # Kodi Helix
+            query = ' '.join((
+
+                "DELETE FROM taglinks",
+                "WHERE idMedia = ?",
+                "AND media_type = ?"
+            ))
+            self.cursor.execute(query, (kodiid, mediatype))
+    
+        # Add tags
+        self.logMsg("Adding Tags: %s" % tags, 2)
+        for tag in tags:
+            self.addTag(kodiid, tag, mediatype)
+
+    def addTag(self, kodiid, tag, mediatype):
+
+        if self.kodiversion in (15, 16, 17):
+            # Kodi Isengard, Jarvis, Krypton
+            query = ' '.join((
+
+                "SELECT tag_id",
+                "FROM tag",
+                "WHERE name = ?",
+                "COLLATE NOCASE"
+            ))
+            self.cursor.execute(query, (tag,))
+            try:
+                tag_id = self.cursor.fetchone()[0]
+            
+            except TypeError:
+                # Create the tag, because it does not exist
+                tag_id = self.createTag(tag)
+                self.logMsg("Adding tag: %s" % tag, 2)
+
+            finally:
+                # Assign tag to item
+                query = (
+                    '''
+                    INSERT OR REPLACE INTO tag_link(
+                        tag_id, media_id, media_type)
+                    
+                    VALUES (?, ?, ?)
+                    '''
+                )
+                self.cursor.execute(query, (tag_id, kodiid, mediatype))
+        else:
+            # Kodi Helix
+            query = ' '.join((
+
+                "SELECT idTag",
+                "FROM tag",
+                "WHERE strTag = ?",
+                "COLLATE NOCASE"
+            ))
+            self.cursor.execute(query, (tag,))
+            try:
+                tag_id = self.cursor.fetchone()[0]
+            
+            except TypeError:
+                # Create the tag
+                tag_id = self.createTag(tag)
+                self.logMsg("Adding tag: %s" % tag, 2)
+            
+            finally:
+                # Assign tag to item
+                query = (
+                    '''
+                    INSERT OR REPLACE INTO taglinks(
+                        idTag, idMedia, media_type)
+                    
+                    VALUES (?, ?, ?)
+                    '''
+                )
+                self.cursor.execute(query, (tag_id, kodiid, mediatype))
+
+    def createTag(self, name):
+        
+        # This will create and return the tag_id
+        if self.kodiversion in (15, 16, 17):
+            # Kodi Isengard, Jarvis, Krypton
+            query = ' '.join((
+
+                "SELECT tag_id",
+                "FROM tag",
+                "WHERE name = ?",
+                "COLLATE NOCASE"
+            ))
+            self.cursor.execute(query, (name,))
+            try:
+                tag_id = self.cursor.fetchone()[0]
+            
+            except TypeError:
+                self.cursor.execute("select coalesce(max(tag_id),0) from tag")
+                tag_id = self.cursor.fetchone()[0] + 1
+
+                query = "INSERT INTO tag(tag_id, name) values(?, ?)"
+                self.cursor.execute(query, (tag_id, name))
+                self.logMsg("Create tag_id: %s name: %s" % (tag_id, name), 2)
+        else:
+            # Kodi Helix
+            query = ' '.join((
+
+                "SELECT idTag",
+                "FROM tag",
+                "WHERE strTag = ?",
+                "COLLATE NOCASE"
+            ))
+            self.cursor.execute(query, (name,))
+            try:
+                tag_id = self.cursor.fetchone()[0]
+
+            except TypeError:
+                self.cursor.execute("select coalesce(max(idTag),0) from tag")
+                tag_id = self.cursor.fetchone()[0] + 1
+
+                query = "INSERT INTO tag(idTag, strTag) values(?, ?)"
+                self.cursor.execute(query, (tag_id, name))
+                self.logMsg("Create idTag: %s name: %s" % (tag_id, name), 2)
+
+        return tag_id
+
+    def updateTag(self, oldtag, newtag, kodiid, mediatype):
+
+        self.logMsg("Updating: %s with %s for %s: %s" % (oldtag, newtag, mediatype, kodiid), 2)
+        
+        if self.kodiversion in (15, 16, 17):
+            # Kodi Isengard, Jarvis, Krypton
+            try: 
+                query = ' '.join((
+
+                    "UPDATE tag_link",
+                    "SET tag_id = ?",
+                    "WHERE media_id = ?",
+                    "AND media_type = ?",
+                    "AND tag_id = ?"
+                ))
+                self.cursor.execute(query, (newtag, kodiid, mediatype, oldtag,))
+            except Exception as e:
+                # The new tag we are going to apply already exists for this item
+                # delete current tag instead
+                self.logMsg("Exception: %s" % e, 1)
+                query = ' '.join((
+
+                    "DELETE FROM tag_link",
+                    "WHERE media_id = ?",
+                    "AND media_type = ?",
+                    "AND tag_id = ?"
+                ))
+                self.cursor.execute(query, (kodiid, mediatype, oldtag,))
+        else:
+            # Kodi Helix
+            try:
+                query = ' '.join((
+
+                    "UPDATE taglinks",
+                    "SET idTag = ?",
+                    "WHERE idMedia = ?",
+                    "AND media_type = ?",
+                    "AND idTag = ?"
+                ))
+                self.cursor.execute(query, (newtag, kodiid, mediatype, oldtag,))
+            except Exception as e:
+                # The new tag we are going to apply already exists for this item
+                # delete current tag instead
+                self.logMsg("Exception: %s" % e, 1)
+                query = ' '.join((
+
+                    "DELETE FROM taglinks",
+                    "WHERE idMedia = ?",
+                    "AND media_type = ?",
+                    "AND idTag = ?"
+                ))
+                self.cursor.execute(query, (kodiid, mediatype, oldtag,))
+
+    def removeTag(self, kodiid, tagname, mediatype):
+
+        if self.kodiversion in (15, 16, 17):
+            # Kodi Isengard, Jarvis, Krypton
+            query = ' '.join((
+
+                "SELECT tag_id",
+                "FROM tag",
+                "WHERE name = ?",
+                "COLLATE NOCASE"
+            ))
+            self.cursor.execute(query, (tagname,))
+            try:
+                tag_id = self.cursor.fetchone()[0]
+            except TypeError:
+                return
+            else:
+                query = ' '.join((
+
+                    "DELETE FROM tag_link",
+                    "WHERE media_id = ?",
+                    "AND media_type = ?",
+                    "AND tag_id = ?"
+                ))
+                self.cursor.execute(query, (kodiid, mediatype, tag_id,))
+        else:
+            # Kodi Helix
+            query = ' '.join((
+
+                "SELECT idTag",
+                "FROM tag",
+                "WHERE strTag = ?",
+                "COLLATE NOCASE"
+            ))
+            self.cursor.execute(query, (tagname,))
+            try:
+                tag_id = self.cursor.fetchone()[0]
+            except TypeError:
+                return
+            else:
+                query = ' '.join((
+
+                    "DELETE FROM taglinks",
+                    "WHERE idMedia = ?",
+                    "AND media_type = ?",
+                    "AND idTag = ?"
+                ))
+                self.cursor.execute(query, (kodiid, mediatype, tag_id,))
+
+    def createBoxset(self, boxsetname):
+
+        self.logMsg("Adding boxset: %s" % boxsetname, 2)
+        query = ' '.join((
+
+            "SELECT idSet",
+            "FROM sets",
+            "WHERE strSet = ?",
+            "COLLATE NOCASE"
+        ))
+        self.cursor.execute(query, (boxsetname,))
+        try:
+            setid = self.cursor.fetchone()[0]
+
+        except TypeError:
+            self.cursor.execute("select coalesce(max(idSet),0) from sets")
+            setid = self.cursor.fetchone()[0] + 1
+
+            query = "INSERT INTO sets(idSet, strSet) values(?, ?)"
+            self.cursor.execute(query, (setid, boxsetname))
+
+        return setid
+
+    def assignBoxset(self, setid, movieid):
+        
+        query = ' '.join((
+
+            "UPDATE movie",
+            "SET idSet = ?",
+            "WHERE idMovie = ?"
+        ))
+        self.cursor.execute(query, (setid, movieid,))
+
+    def removefromBoxset(self, movieid):
+
+        query = ' '.join((
+
+            "UPDATE movie",
+            "SET idSet = null",
+            "WHERE idMovie = ?"
+        ))
+        self.cursor.execute(query, (movieid,))
+
+    def addSeason(self, showid, seasonnumber):
+
+        query = ' '.join((
+
+            "SELECT idSeason",
+            "FROM seasons",
+            "WHERE idShow = ?",
+            "AND season = ?"
+        ))
+        self.cursor.execute(query, (showid, seasonnumber,))
+        try:
+            seasonid = self.cursor.fetchone()[0]
+        except TypeError:
+            self.cursor.execute("select coalesce(max(idSeason),0) from seasons")
+            seasonid = self.cursor.fetchone()[0] + 1
+            query = "INSERT INTO seasons(idSeason, idShow, season) values(?, ?, ?)"
+            self.cursor.execute(query, (seasonid, showid, seasonnumber))
+
+        return seasonid
+
+    def addArtist(self, name, musicbrainz):
+
+        query = ' '.join((
+
+            "SELECT idArtist, strArtist",
+            "FROM artist",
+            "WHERE strMusicBrainzArtistID = ?"
+        ))
+        self.cursor.execute(query, (musicbrainz,))
+        try:
+            result = self.cursor.fetchone()
+            artistid = result[0]
+            artistname = result[1]
+
+        except TypeError:
+
+            query = ' '.join((
+
+                "SELECT idArtist",
+                "FROM artist",
+                "WHERE strArtist = ?",
+                "COLLATE NOCASE"
+            ))
+            self.cursor.execute(query, (name,))
+            try:
+                artistid = self.cursor.fetchone()[0]
+            except TypeError:
+                self.cursor.execute("select coalesce(max(idArtist),0) from artist")
+                artistid = self.cursor.fetchone()[0] + 1
+                query = (
+                    '''
+                    INSERT INTO artist(idArtist, strArtist, strMusicBrainzArtistID)
+
+                    VALUES (?, ?, ?)
+                    '''
+                )
+                self.cursor.execute(query, (artistid, name, musicbrainz))
+        else:
+            if artistname != name:
+                query = "UPDATE artist SET strArtist = ? WHERE idArtist = ?"
+                self.cursor.execute(query, (name, artistid,))
+
+        return artistid
+
+    def addAlbum(self, name, musicbrainz):
+
+        query = ' '.join((
+
+            "SELECT idAlbum",
+            "FROM album",
+            "WHERE strMusicBrainzAlbumID = ?"
+        ))
+        self.cursor.execute(query, (musicbrainz,))
+        try:
+            albumid = self.cursor.fetchone()[0]
+        except TypeError:
+            # Create the album
+            self.cursor.execute("select coalesce(max(idAlbum),0) from album")
+            albumid = self.cursor.fetchone()[0] + 1
+            if self.kodiversion in (15, 16, 17):
+                query = (
+                    '''
+                    INSERT INTO album(idAlbum, strAlbum, strMusicBrainzAlbumID, strReleaseType)
+
+                    VALUES (?, ?, ?, ?)
+                    '''
+                )
+                self.cursor.execute(query, (albumid, name, musicbrainz, "album"))
+            else: # Helix
+                query = (
+                    '''
+                    INSERT INTO album(idAlbum, strAlbum, strMusicBrainzAlbumID)
+
+                    VALUES (?, ?, ?)
+                    '''
+                )
+                self.cursor.execute(query, (albumid, name, musicbrainz))
+
+        return albumid
+
+    def addMusicGenres(self, kodiid, genres, mediatype):
+
+        if mediatype == "album":
+
+            # Delete current genres for clean slate
+            query = ' '.join((
+
+                "DELETE FROM album_genre",
+                "WHERE idAlbum = ?"
+            ))
+            self.cursor.execute(query, (kodiid,))
+
+            for genre in genres:
+                query = ' '.join((
+
+                    "SELECT idGenre",
+                    "FROM genre",
+                    "WHERE strGenre = ?",
+                    "COLLATE NOCASE"
+                ))
+                self.cursor.execute(query, (genre,))
+                try:
+                    genreid = self.cursor.fetchone()[0]
+                except TypeError:
+                    # Create the genre
+                    self.cursor.execute("select coalesce(max(idGenre),0) from genre")
+                    genreid = self.cursor.fetchone()[0] + 1
+                    query = "INSERT INTO genre(idGenre, strGenre) values(?, ?)"
+                    self.cursor.execute(query, (genreid, genre))
+
+                query = "INSERT OR REPLACE INTO album_genre(idGenre, idAlbum) values(?, ?)"
+                self.cursor.execute(query, (genreid, kodiid))
+
+        elif mediatype == "song":
+            
+            # Delete current genres for clean slate
+            query = ' '.join((
+
+                "DELETE FROM song_genre",
+                "WHERE idSong = ?"
+            ))
+            self.cursor.execute(query, (kodiid,))
+
+            for genre in genres:
+                query = ' '.join((
+
+                    "SELECT idGenre",
+                    "FROM genre",
+                    "WHERE strGenre = ?",
+                    "COLLATE NOCASE"
+                ))
+                self.cursor.execute(query, (genre,))
+                try:
+                    genreid = self.cursor.fetchone()[0]
+                except TypeError:
+                    # Create the genre
+                    self.cursor.execute("select coalesce(max(idGenre),0) from genre")
+                    genreid = self.cursor.fetchone()[0] + 1
+                    query = "INSERT INTO genre(idGenre, strGenre) values(?, ?)"
+                    self.cursor.execute(query, (genreid, genre))
+
+                query = "INSERT OR REPLACE INTO song_genre(idGenre, idSong) values(?, ?)"
+                self.cursor.execute(query, (genreid, kodiid))
\ No newline at end of file
diff --git a/resources/lib/kodimonitor.py b/resources/lib/kodimonitor.py
index f00423c1..f2b5ae86 100644
--- a/resources/lib/kodimonitor.py
+++ b/resources/lib/kodimonitor.py
@@ -1,209 +1,209 @@
-# -*- coding: utf-8 -*-
-
-#################################################################################################
-
-import json
-
-import xbmc
-import xbmcgui
-
-import clientinfo
-import downloadutils
-import embydb_functions as embydb
-import playbackutils as pbutils
-import utils
-
-#################################################################################################
-
-
-class KodiMonitor(xbmc.Monitor):
-
-
-    def __init__(self):
-
-        self.clientInfo = clientinfo.ClientInfo()
-        self.addonName = self.clientInfo.getAddonName()
-        self.doUtils = downloadutils.DownloadUtils()
-
-        self.logMsg("Kodi monitor started.", 1)
-
-    def logMsg(self, msg, lvl=1):
-
-        self.className = self.__class__.__name__
-        utils.logMsg("%s %s" % (self.addonName, self.className), msg, lvl)
-
-
-    def onScanStarted(self, library):
-        self.logMsg("Kodi library scan %s running." % library, 2)
-        if library == "video":
-            utils.window('emby_kodiScan', value="true")
-            
-    def onScanFinished(self, library):
-        self.logMsg("Kodi library scan %s finished." % library, 2)
-        if library == "video":
-            utils.window('emby_kodiScan', clear=True)
-
-    def onSettingsChanged(self):
-        # Monitor emby settings
-        # Review reset setting at a later time, need to be adjusted to account for initial setup
-        # changes.
-        '''currentPath = utils.settings('useDirectPaths')
-        if utils.window('emby_pluginpath') != currentPath:
-            # Plugin path value changed. Offer to reset
-            self.logMsg("Changed to playback mode detected", 1)
-            utils.window('emby_pluginpath', value=currentPath)
-            resp = xbmcgui.Dialog().yesno(
-                                heading="Playback mode change detected",
-                                line1=(
-                                    "Detected the playback mode has changed. The database "
-                                    "needs to be recreated for the change to be applied. "
-                                    "Proceed?"))
-            if resp:
-                utils.reset()'''
-
-        currentLog = utils.settings('logLevel')
-        if utils.window('emby_logLevel') != currentLog:
-            # The log level changed, set new prop
-            self.logMsg("New log level: %s" % currentLog, 1)
-            utils.window('emby_logLevel', value=currentLog)
-
-    def onNotification(self, sender, method, data):
-
-        doUtils = self.doUtils
-        if method not in ("Playlist.OnAdd"):
-            self.logMsg("Method: %s Data: %s" % (method, data), 1)
-            
-        if data:
-            data = json.loads(data,'utf-8')
-
-
-        if method == "Player.OnPlay":
-            # Set up report progress for emby playback
-            item = data.get('item')
-            try:
-                kodiid = item['id']
-                type = item['type']
-            except (KeyError, TypeError):
-                self.logMsg("Item is invalid for playstate update.", 1)
-            else:
-                if ((utils.settings('useDirectPaths') == "1" and not type == "song") or
-                        (type == "song" and utils.settings('enableMusic') == "true")):
-                    # Set up properties for player
-                    embyconn = utils.kodiSQL('emby')
-                    embycursor = embyconn.cursor()
-                    emby_db = embydb.Embydb_Functions(embycursor)
-                    emby_dbitem = emby_db.getItem_byKodiId(kodiid, type)
-                    try:
-                        itemid = emby_dbitem[0]
-                    except TypeError:
-                        self.logMsg("No kodiid returned.", 1)
-                    else:
-                        url = "{server}/emby/Users/{UserId}/Items/%s?format=json" % itemid
-                        result = doUtils.downloadUrl(url)
-                        self.logMsg("Item: %s" % result, 2)
-
-                        playurl = None
-                        count = 0
-                        while not playurl and count < 2:
-                            try:
-                                playurl = xbmc.Player().getPlayingFile()
-                            except RuntimeError:
-                                count += 1
-                                xbmc.sleep(200)
-                            else:
-                                listItem = xbmcgui.ListItem()
-                                playback = pbutils.PlaybackUtils(result)
-
-                                if type == "song" and utils.settings('streamMusic') == "true":
-                                    utils.window('emby_%s.playmethod' % playurl,
-                                        value="DirectStream")
-                                else:
-                                    utils.window('emby_%s.playmethod' % playurl,
-                                        value="DirectPlay")
-                                # Set properties for player.py
-                                playback.setProperties(playurl, listItem)
-                    finally:
-                        embycursor.close()
-            
-
-        elif method == "VideoLibrary.OnUpdate":
-            # Manually marking as watched/unwatched
-            playcount = data.get('playcount')
-            item = data.get('item')
-            try:
-                kodiid = item['id']
-                type = item['type']
-            except (KeyError, TypeError):
-                self.logMsg("Item is invalid for playstate update.", 1)
-            else:
-                # Send notification to the server.
-                embyconn = utils.kodiSQL('emby')
-                embycursor = embyconn.cursor()
-                emby_db = embydb.Embydb_Functions(embycursor)
-                emby_dbitem = emby_db.getItem_byKodiId(kodiid, type)
-                try:
-                    itemid = emby_dbitem[0]
-                except TypeError:
-                    self.logMsg("Could not find itemid in emby database.", 1)
-                else:
-                    # Stop from manually marking as watched unwatched, with actual playback.
-                    if utils.window('emby_skipWatched%s' % itemid) == "true":
-                        # property is set in player.py
-                        utils.window('emby_skipWatched%s' % itemid, clear=True)
-                    else:
-                        # notify the server
-                        url = "{server}/emby/Users/{UserId}/PlayedItems/%s?format=json" % itemid
-                        if playcount != 0:
-                            doUtils.downloadUrl(url, type="POST")
-                            self.logMsg("Mark as watched for itemid: %s" % itemid, 1)
-                        else:
-                            doUtils.downloadUrl(url, type="DELETE")
-                            self.logMsg("Mark as unwatched for itemid: %s" % itemid, 1)
-                finally:
-                    embycursor.close()
-
-
-        elif method == "VideoLibrary.OnRemove":
-            # Removed function, because with plugin paths + clean library, it will wipe
-            # entire library if user has permissions. Instead, use the emby context menu available
-            # in Isengard and higher version
-            pass
-            '''try:
-                kodiid = data['id']
-                type = data['type']
-            except (KeyError, TypeError):
-                self.logMsg("Item is invalid for emby deletion.", 1)
-            else:
-                # Send the delete action to the server.
-                embyconn = utils.kodiSQL('emby')
-                embycursor = embyconn.cursor()
-                emby_db = embydb.Embydb_Functions(embycursor)
-                emby_dbitem = emby_db.getItem_byKodiId(kodiid, type)
-                try:
-                    itemid = emby_dbitem[0]
-                except TypeError:
-                    self.logMsg("Could not find itemid in emby database.", 1)
-                else:
-                    if utils.settings('skipContextMenu') != "true":
-                        resp = xbmcgui.Dialog().yesno(
-                                                heading="Confirm delete",
-                                                line1="Delete file on Emby Server?")
-                        if not resp:
-                            self.logMsg("User skipped deletion.", 1)
-                            embycursor.close()
-                            return
-
-                    url = "{server}/emby/Items/%s?format=json" % itemid
-                    self.logMsg("Deleting request: %s" % itemid)
-                    doUtils.downloadUrl(url, type="DELETE")
-                finally:
-                    embycursor.close()'''
-
-
-        elif method == "System.OnWake":
-            # Allow network to wake up
-            xbmc.sleep(10000)
-            utils.window('emby_onWake', value="true")
-
-        elif method == "Playlist.OnClear":
+# -*- coding: utf-8 -*-
+
+#################################################################################################
+
+import json
+
+import xbmc
+import xbmcgui
+
+import clientinfo
+import downloadutils
+import embydb_functions as embydb
+import playbackutils as pbutils
+import utils
+
+#################################################################################################
+
+
+class KodiMonitor(xbmc.Monitor):
+
+
+    def __init__(self):
+
+        self.clientInfo = clientinfo.ClientInfo()
+        self.addonName = self.clientInfo.getAddonName()
+        self.doUtils = downloadutils.DownloadUtils()
+
+        self.logMsg("Kodi monitor started.", 1)
+
+    def logMsg(self, msg, lvl=1):
+
+        self.className = self.__class__.__name__
+        utils.logMsg("%s %s" % (self.addonName, self.className), msg, lvl)
+
+
+    def onScanStarted(self, library):
+        self.logMsg("Kodi library scan %s running." % library, 2)
+        if library == "video":
+            utils.window('emby_kodiScan', value="true")
+            
+    def onScanFinished(self, library):
+        self.logMsg("Kodi library scan %s finished." % library, 2)
+        if library == "video":
+            utils.window('emby_kodiScan', clear=True)
+
+    def onSettingsChanged(self):
+        # Monitor emby settings
+        # Review reset setting at a later time, need to be adjusted to account for initial setup
+        # changes.
+        '''currentPath = utils.settings('useDirectPaths')
+        if utils.window('emby_pluginpath') != currentPath:
+            # Plugin path value changed. Offer to reset
+            self.logMsg("Changed to playback mode detected", 1)
+            utils.window('emby_pluginpath', value=currentPath)
+            resp = xbmcgui.Dialog().yesno(
+                                heading="Playback mode change detected",
+                                line1=(
+                                    "Detected the playback mode has changed. The database "
+                                    "needs to be recreated for the change to be applied. "
+                                    "Proceed?"))
+            if resp:
+                utils.reset()'''
+
+        currentLog = utils.settings('logLevel')
+        if utils.window('emby_logLevel') != currentLog:
+            # The log level changed, set new prop
+            self.logMsg("New log level: %s" % currentLog, 1)
+            utils.window('emby_logLevel', value=currentLog)
+
+    def onNotification(self, sender, method, data):
+
+        doUtils = self.doUtils
+        if method not in ("Playlist.OnAdd"):
+            self.logMsg("Method: %s Data: %s" % (method, data), 1)
+            
+        if data:
+            data = json.loads(data,'utf-8')
+
+
+        if method == "Player.OnPlay":
+            # Set up report progress for emby playback
+            item = data.get('item')
+            try:
+                kodiid = item['id']
+                item_type = item['type']
+            except (KeyError, TypeError):
+                self.logMsg("Item is invalid for playstate update.", 1)
+            else:
+                if ((utils.settings('useDirectPaths') == "1" and not item_type == "song") or
+                        (item_type == "song" and utils.settings('enableMusic') == "true")):
+                    # Set up properties for player
+                    embyconn = utils.kodiSQL('emby')
+                    embycursor = embyconn.cursor()
+                    emby_db = embydb.Embydb_Functions(embycursor)
+                    emby_dbitem = emby_db.getItem_byKodiId(kodiid, item_type)
+                    try:
+                        itemid = emby_dbitem[0]
+                    except TypeError:
+                        self.logMsg("No kodiid returned.", 1)
+                    else:
+                        url = "{server}/emby/Users/{UserId}/Items/%s?format=json" % itemid
+                        result = doUtils.downloadUrl(url)
+                        self.logMsg("Item: %s" % result, 2)
+
+                        playurl = None
+                        count = 0
+                        while not playurl and count < 2:
+                            try:
+                                playurl = xbmc.Player().getPlayingFile()
+                            except RuntimeError:
+                                count += 1
+                                xbmc.sleep(200)
+                            else:
+                                listItem = xbmcgui.ListItem()
+                                playback = pbutils.PlaybackUtils(result)
+
+                                if item_type == "song" and utils.settings('streamMusic') == "true":
+                                    utils.window('emby_%s.playmethod' % playurl,
+                                        value="DirectStream")
+                                else:
+                                    utils.window('emby_%s.playmethod' % playurl,
+                                        value="DirectPlay")
+                                # Set properties for player.py
+                                playback.setProperties(playurl, listItem)
+                    finally:
+                        embycursor.close()
+            
+
+        elif method == "VideoLibrary.OnUpdate":
+            # Manually marking as watched/unwatched
+            playcount = data.get('playcount')
+            item = data.get('item')
+            try:
+                kodiid = item['id']
+                item_type = item['type']
+            except (KeyError, TypeError):
+                self.logMsg("Item is invalid for playstate update.", 1)
+            else:
+                # Send notification to the server.
+                embyconn = utils.kodiSQL('emby')
+                embycursor = embyconn.cursor()
+                emby_db = embydb.Embydb_Functions(embycursor)
+                emby_dbitem = emby_db.getItem_byKodiId(kodiid, item_type)
+                try:
+                    itemid = emby_dbitem[0]
+                except TypeError:
+                    self.logMsg("Could not find itemid in emby database.", 1)
+                else:
+                    # Stop from manually marking as watched unwatched, with actual playback.
+                    if utils.window('emby_skipWatched%s' % itemid) == "true":
+                        # property is set in player.py
+                        utils.window('emby_skipWatched%s' % itemid, clear=True)
+                    else:
+                        # notify the server
+                        url = "{server}/emby/Users/{UserId}/PlayedItems/%s?format=json" % itemid
+                        if playcount != 0:
+                            doUtils.downloadUrl(url, action_type="POST")
+                            self.logMsg("Mark as watched for itemid: %s" % itemid, 1)
+                        else:
+                            doUtils.downloadUrl(url, action_type="DELETE")
+                            self.logMsg("Mark as unwatched for itemid: %s" % itemid, 1)
+                finally:
+                    embycursor.close()
+
+
+        elif method == "VideoLibrary.OnRemove":
+            # Removed function, because with plugin paths + clean library, it will wipe
+            # entire library if user has permissions. Instead, use the emby context menu available
+            # in Isengard and higher version
+            pass
+            '''try:
+                kodiid = data['id']
+                type = data['type']
+            except (KeyError, TypeError):
+                self.logMsg("Item is invalid for emby deletion.", 1)
+            else:
+                # Send the delete action to the server.
+                embyconn = utils.kodiSQL('emby')
+                embycursor = embyconn.cursor()
+                emby_db = embydb.Embydb_Functions(embycursor)
+                emby_dbitem = emby_db.getItem_byKodiId(kodiid, type)
+                try:
+                    itemid = emby_dbitem[0]
+                except TypeError:
+                    self.logMsg("Could not find itemid in emby database.", 1)
+                else:
+                    if utils.settings('skipContextMenu') != "true":
+                        resp = xbmcgui.Dialog().yesno(
+                                                heading="Confirm delete",
+                                                line1="Delete file on Emby Server?")
+                        if not resp:
+                            self.logMsg("User skipped deletion.", 1)
+                            embycursor.close()
+                            return
+
+                    url = "{server}/emby/Items/%s?format=json" % itemid
+                    self.logMsg("Deleting request: %s" % itemid)
+                    doUtils.downloadUrl(url, action_type="DELETE")
+                finally:
+                    embycursor.close()'''
+
+
+        elif method == "System.OnWake":
+            # Allow network to wake up
+            xbmc.sleep(10000)
+            utils.window('emby_onWake', value="true")
+
+        elif method == "Playlist.OnClear":
             pass
\ No newline at end of file
diff --git a/resources/lib/librarysync.py b/resources/lib/librarysync.py
index dacebbde..d3a441dd 100644
--- a/resources/lib/librarysync.py
+++ b/resources/lib/librarysync.py
@@ -1,1549 +1,1480 @@
-# -*- coding: utf-8 -*-
-
-##################################################################################################
-
-import sqlite3
-import threading
-from datetime import datetime, timedelta, time
-
-import xbmc
-import xbmcgui
-import xbmcvfs
-
-import api
-import utils
-import clientinfo
-import downloadutils
-import itemtypes
-import embydb_functions as embydb
-import kodidb_functions as kodidb
-import read_embyserver as embyserver
-import userclient
-import videonodes
-
-##################################################################################################
-
-
-class LibrarySync(threading.Thread):
-
-    _shared_state = {}
-
-    stop_thread = False
-    suspend_thread = False
-
-    # Track websocketclient updates
-    addedItems = []
-    updateItems = []
-    userdataItems = []
-    removeItems = []
-    forceLibraryUpdate = False
-    refresh_views = False
-
-
-    def __init__(self):
-
-        self.__dict__ = self._shared_state
-        self.monitor = xbmc.Monitor()
-
-        self.clientInfo = clientinfo.ClientInfo()
-        self.addonName = self.clientInfo.getAddonName()
-        self.doUtils = downloadutils.DownloadUtils().downloadUrl
-        self.user = userclient.UserClient()
-        self.emby = embyserver.Read_EmbyServer()
-        self.vnodes = videonodes.VideoNodes()
-
-        threading.Thread.__init__(self)
-
-    def logMsg(self, msg, lvl=1):
-
-        className = self.__class__.__name__
-        utils.logMsg("%s %s" % (self.addonName, className), msg, lvl)
-
-
-    def progressDialog(self, title, forced=False):
-
-        dialog = None
-
-        if utils.settings('dbSyncIndicator') == "true" or forced:
-            dialog = xbmcgui.DialogProgressBG()
-            dialog.create("Emby for Kodi", title)
-            self.logMsg("Show progress dialog: %s" % title, 2)
-
-        return dialog
-
-    def startSync(self):
-
-        settings = utils.settings
-        # Run at start up - optional to use the server plugin
-        if settings('SyncInstallRunDone') == "true":
-            
-            # Validate views
-            self.refreshViews()
-            completed = False
-            # Verify if server plugin is installed.
-            if settings('serverSync') == "true":
-                # Try to use fast start up
-                url = "{server}/emby/Plugins?format=json"
-                result = self.doUtils(url)
-
-                for plugin in result:
-                    if plugin['Name'] == "Emby.Kodi Sync Queue":
-                        self.logMsg("Found server plugin.", 2)
-                        completed = self.fastSync()
-            
-            if not completed:
-                # Fast sync failed or server plugin is not found
-                completed = ManualSync().sync()
-        else:
-            # Install sync is not completed
-            completed = self.fullSync()
-        
-        return completed
-
-    def fastSync(self):
-
-        log = self.logMsg
-
-        doUtils = self.doUtils
-
-        lastSync = utils.settings('LastIncrementalSync')
-        if not lastSync:
-            lastSync = "2010-01-01T00:00:00Z"
-        
-        lastSyncTime = utils.convertdate(lastSync)
-        log("Last sync run: %s" % lastSyncTime, 1)
-        
-        # get server RetentionDateTime
-        url = "{server}/emby/Emby.Kodi.SyncQueue/GetServerDateTime?format=json"
-        result = doUtils(url)
-        retention_time = "2010-01-01T00:00:00Z"
-        if result and result.get('RetentionDateTime'):
-            retention_time = result['RetentionDateTime']
-
-        #Try/except equivalent
-        '''
-        try:
-            retention_time = result['RetentionDateTime']
-        except (TypeError, KeyError):
-            retention_time = "2010-01-01T00:00:00Z"
-        '''
-
-        retention_time = utils.convertdate(retention_time)
-        log("RetentionDateTime: %s" % retention_time, 1)
-
-        # if last sync before retention time do a full sync
-        if retention_time > lastSyncTime:
-            log("Fast sync server retention insufficient, fall back to full sync", 1)
-            return False
-        
-        url = "{server}/emby/Emby.Kodi.SyncQueue/{UserId}/GetItems?format=json"
-        params = {'LastUpdateDT': lastSync}
-        result = doUtils(url, parameters=params)
-
-        try:
-            processlist = {
-                
-                'added': result['ItemsAdded'],
-                'update': result['ItemsUpdated'],
-                'userdata': result['UserDataChanged'],
-                'remove': result['ItemsRemoved']
-            }
-            
-        except (KeyError, TypeError):
-            log("Failed to retrieve latest updates using fast sync.", 1)
-            return False
-        
-        else:
-            log("Fast sync changes: %s" % result, 1)
-            for action in processlist:
-                self.triage_items(action, processlist[action])
-
-            return True
-
-    def saveLastSync(self):
-
-        log = self.logMsg
-        # Save last sync time
-        overlap = 2
-
-        url = "{server}/emby/Emby.Kodi.SyncQueue/GetServerDateTime?format=json"
-        result = self.doUtils(url)
-        try: # datetime fails when used more than once, TypeError
-            server_time = result['ServerDateTime']
-            server_time = utils.convertdate(server_time)
-        
-        except Exception as e:
-            # If the server plugin is not installed or an error happened.
-            log("An exception occurred: %s" % e, 1)
-            time_now = datetime.utcnow()-timedelta(minutes=overlap)
-            lastSync = time_now.strftime('%Y-%m-%dT%H:%M:%SZ')
-            log("New sync time: client time -%s min: %s" % (overlap, lastSync), 1)
-
-        else:
-            lastSync = (server_time - timedelta(minutes=overlap)).strftime('%Y-%m-%dT%H:%M:%SZ')
-            log("New sync time: server time -%s min: %s" % (overlap, lastSync), 1)
-
-        finally:
-            utils.settings('LastIncrementalSync', value=lastSync)
-
-    def shouldStop(self):
-        # Checkpoint during the syncing process
-        if self.monitor.abortRequested():
-            return True
-        elif utils.window('emby_shouldStop') == "true":
-            return True
-        else: # Keep going
-            return False
-
-    def dbCommit(self, connection):
-        
-        log = self.logMsg
-        window = utils.window
-        # Central commit, verifies if Kodi database update is running
-        kodidb_scan = window('emby_kodiScan') == "true"
-
-        while kodidb_scan:
-
-            log("Kodi scan is running. Waiting...", 1)
-            kodidb_scan = window('emby_kodiScan') == "true"
-
-            if self.shouldStop():
-                log("Commit unsuccessful. Sync terminated.", 1)
-                break
-
-            if self.monitor.waitForAbort(1):
-                # Abort was requested while waiting. We should exit
-                log("Commit unsuccessful.", 1)
-                break
-        else:
-            connection.commit()
-            log("Commit successful.", 1)
-
-    def fullSync(self, manualrun=False, repair=False, forceddialog=False):
-
-        log = self.logMsg
-        window = utils.window
-        settings = utils.settings
-        # Only run once when first setting up. Can be run manually.
-        emby = self.emby
-        music_enabled = utils.settings('enableMusic') == "true"
-
-        xbmc.executebuiltin('InhibitIdleShutdown(true)')
-        screensaver = utils.getScreensaver()
-        utils.setScreensaver(value="")
-        window('emby_dbScan', value="true")
-        # Add sources
-        utils.sourcesXML()
-
-        embyconn = utils.kodiSQL('emby')
-        embycursor = embyconn.cursor()
-        # Create the tables for the emby database
-        # emby, view, version
-        embycursor.execute(
-            """CREATE TABLE IF NOT EXISTS emby(
-            emby_id TEXT UNIQUE, media_folder TEXT, emby_type TEXT, media_type TEXT, kodi_id INTEGER, 
-            kodi_fileid INTEGER, kodi_pathid INTEGER, parent_id INTEGER, checksum INTEGER)""")
-        embycursor.execute(
-            """CREATE TABLE IF NOT EXISTS view(
-            view_id TEXT UNIQUE, view_name TEXT, media_type TEXT, kodi_tagid INTEGER)""")
-        embycursor.execute("CREATE TABLE IF NOT EXISTS version(idVersion TEXT)")
-        embyconn.commit()
-        
-        # content sync: movies, tvshows, musicvideos, music
-        kodiconn = utils.kodiSQL('video')
-        kodicursor = kodiconn.cursor()
-
-        if manualrun:
-            message = "Manual sync"
-        elif repair:
-            message = "Repair sync"
-            forceddialog = True
-        else:
-            message = "Initial sync"
-            forceddialog = True
-            window('emby_initialScan', value="true")
-        
-        pDialog = self.progressDialog("%s" % message, forced=forceddialog)
-        starttotal = datetime.now()
-
-        # Set views
-        self.maintainViews(embycursor, kodicursor)
-        embyconn.commit()
-        
-        # Sync video library
-        process = {
-
-            'movies': self.movies,
-            'musicvideos': self.musicvideos,
-            'tvshows': self.tvshows
-        }
-        for itemtype in process:
-            startTime = datetime.now()
-            completed = process[itemtype](embycursor, kodicursor, pDialog)
-            if not completed:
-                xbmc.executebuiltin('InhibitIdleShutdown(false)')
-                utils.setScreensaver(value=screensaver)
-                window('emby_dbScan', clear=True)
-                if pDialog:
-                    pDialog.close()
-
-                embycursor.close()
-                kodicursor.close()
-                return False
-            else:
-                self.dbCommit(kodiconn)
-                embyconn.commit()
-                elapsedTime = datetime.now() - startTime
-                log("SyncDatabase (finished %s in: %s)"
-                    % (itemtype, str(elapsedTime).split('.')[0]), 1)
-        else:
-            # Close the Kodi cursor
-            kodicursor.close()
-
-        # sync music
-        if music_enabled:
-            
-            musicconn = utils.kodiSQL('music')
-            musiccursor = musicconn.cursor()
-            
-            startTime = datetime.now()
-            completed = self.music(embycursor, musiccursor, pDialog)
-            if not completed:
-                xbmc.executebuiltin('InhibitIdleShutdown(false)')
-                utils.setScreensaver(value=screensaver)
-                window('emby_dbScan', clear=True)
-                if pDialog:
-                    pDialog.close()
-
-                embycursor.close()
-                musiccursor.close()
-                return False
-            else:
-                musicconn.commit()
-                embyconn.commit()
-                elapsedTime = datetime.now() - startTime
-                log("SyncDatabase (finished music in: %s)"
-                    % (str(elapsedTime).split('.')[0]), 1)
-            musiccursor.close()
-
-        if pDialog:
-            pDialog.close()
-        
-        embycursor.close()
-        
-        settings('SyncInstallRunDone', value="true")
-        settings("dbCreatedWithVersion", self.clientInfo.getVersion())
-        self.saveLastSync()
-        xbmc.executebuiltin('UpdateLibrary(video)')
-        elapsedtotal = datetime.now() - starttotal
-
-        xbmc.executebuiltin('InhibitIdleShutdown(false)')
-        utils.setScreensaver(value=screensaver)
-        window('emby_dbScan', clear=True)
-        window('emby_initialScan', clear=True)
-        if forceddialog:
-            xbmcgui.Dialog().notification(
-                        heading="Emby for Kodi",
-                        message="%s %s %s" % 
-                                (message, utils.language(33025), str(elapsedtotal).split('.')[0]),
-                        icon="special://home/addons/plugin.video.emby/icon.png",
-                        sound=False)
-        return True
-
-
-    def refreshViews(self):
-
-        embyconn = utils.kodiSQL('emby')
-        embycursor = embyconn.cursor()
-        kodiconn = utils.kodiSQL('video')
-        kodicursor = kodiconn.cursor()
-
-        # Compare views, assign correct tags to items
-        self.maintainViews(embycursor, kodicursor)
-        
-        self.dbCommit(kodiconn)
-        kodicursor.close()
-
-        embyconn.commit()
-        embycursor.close()
-
-    def maintainViews(self, embycursor, kodicursor):
-
-        log = self.logMsg
-        # Compare the views to emby
-        emby = self.emby
-        emby_db = embydb.Embydb_Functions(embycursor)
-        kodi_db = kodidb.Kodidb_Functions(kodicursor)
-        doUtils = self.doUtils
-        vnodes = self.vnodes
-        
-        # Get views
-        url = "{server}/emby/Users/{UserId}/Views?format=json"
-        result = doUtils(url)
-        grouped_views = result['Items']
-        ordered_views = emby.getViews(sortedlist=True)
-        all_views = []
-        sorted_views = []
-        for view in ordered_views:
-            all_views.append(view['name'])
-            if view['type'] == "music":
-                continue
-                
-            if view['type'] == "mixed":
-                sorted_views.append(view['name'])
-            sorted_views.append(view['name'])
-        log("Sorted views: %s" % sorted_views, 1)
-
-        # total nodes for window properties
-        vnodes.clearProperties()
-        totalnodes = len(sorted_views) + 0
-
-        current_views = emby_db.getViews()
-        # Set views for supported media type
-        emby_mediatypes = {
-
-            'movies': "Movie",
-            'tvshows': "Series",
-            'musicvideos': "MusicVideo",
-            'homevideos': "Video",
-            'music': "Audio",
-            'photos': "Photo"
-        }
-        mediatypes = ['movies', 'tvshows', 'musicvideos', 'homevideos', 'music', 'photos']
-        for mediatype in mediatypes:
-
-            nodes = [] # Prevent duplicate for nodes of the same type
-            playlists = [] # Prevent duplicate for playlists of the same type
-            # Get media folders from server
-            folders = emby.getViews(mediatype, root=True)
-            for folder in folders:
-
-                folderid = folder['id']
-                foldername = folder['name']
-                viewtype = folder['type']
-                
-                if foldername not in all_views:
-                    # Media folders are grouped into userview
-                    url = "{server}/emby/Users/{UserId}/Items?format=json"
-                    params = {
-                        'ParentId': folderid,
-                        'Recursive': True,
-                        'Limit': 1,
-                        'IncludeItemTypes': emby_mediatypes[mediatype]
-                    } # Get one item from server using the folderid
-                    result = doUtils(url, parameters=params)
-                    try:
-                        verifyitem = result['Items'][0]['Id']
-                    except (TypeError, IndexError):
-                        # Something is wrong. Keep the same folder name.
-                        # Could be the view is empty or the connection
-                        pass
-                    else:
-                        for grouped_view in grouped_views:
-                            # This is only reserved for the detection of grouped views
-                            if (grouped_view['Type'] == "UserView" and 
-                                grouped_view.get('CollectionType') == mediatype):
-                                # Take the userview, and validate the item belong to the view
-                                if emby.verifyView(grouped_view['Id'], verifyitem):
-                                    # Take the name of the userview
-                                    log("Found corresponding view: %s %s"
-                                        % (grouped_view['Name'], grouped_view['Id']), 1)
-                                    foldername = grouped_view['Name']
-                                    break
-                        else:
-                            # Unable to find a match, add the name to our sorted_view list
-                            sorted_views.append(foldername)
-                            log("Couldn't find corresponding grouped view: %s" % sorted_views, 1)
-
-                # Failsafe 
-                try:
-                    sorted_views.index(foldername)
-                except ValueError:
-                    sorted_views.append(foldername)
-
-                # Get current media folders from emby database
-                view = emby_db.getView_byId(folderid)
-                try:
-                    current_viewname = view[0]
-                    current_viewtype = view[1]
-                    current_tagid = view[2]
-
-                except TypeError:
-                    log("Creating viewid: %s in Emby database." % folderid, 1)
-                    tagid = kodi_db.createTag(foldername)
-                    # Create playlist for the video library
-                    if (foldername not in playlists and
-                            mediatype in ('movies', 'tvshows', 'musicvideos')):
-                        utils.playlistXSP(mediatype, foldername, folderid, viewtype)
-                        playlists.append(foldername)
-                    # Create the video node
-                    if foldername not in nodes and mediatype not in ("musicvideos", "music"):
-                        vnodes.viewNode(sorted_views.index(foldername), foldername, mediatype,
-                            viewtype, folderid)
-                        if viewtype == "mixed": # Change the value
-                            sorted_views[sorted_views.index(foldername)] = "%ss" % foldername
-                        nodes.append(foldername)
-                        totalnodes += 1
-                    # Add view to emby database
-                    emby_db.addView(folderid, foldername, viewtype, tagid)
-
-                else:
-                    log(' '.join((
-
-                        "Found viewid: %s" % folderid,
-                        "viewname: %s" % current_viewname,
-                        "viewtype: %s" % current_viewtype,
-                        "tagid: %s" % current_tagid)), 2)
-
-                    # View is still valid
-                    try:
-                        current_views.remove(folderid)
-                    except ValueError:
-                        # View was just created, nothing to remove
-                        pass
-
-                    # View was modified, update with latest info
-                    if current_viewname != foldername:
-                        log("viewid: %s new viewname: %s" % (folderid, foldername), 1)
-                        tagid = kodi_db.createTag(foldername)
-                        
-                        # Update view with new info
-                        emby_db.updateView(foldername, tagid, folderid)
-
-                        if mediatype != "music":
-                            if emby_db.getView_byName(current_viewname) is None:
-                                # The tag could be a combined view. Ensure there's no other tags
-                                # with the same name before deleting playlist.
-                                utils.playlistXSP(
-                                    mediatype, current_viewname, folderid, current_viewtype, True)
-                                # Delete video node
-                                if mediatype != "musicvideos":
-                                    vnodes.viewNode(
-                                        indexnumber=None,
-                                        tagname=current_viewname,
-                                        mediatype=mediatype,
-                                        viewtype=current_viewtype,
-                                        viewid=folderid,
-                                        delete=True)
-                            # Added new playlist
-                            if (foldername not in playlists and
-                                    mediatype in ('movies', 'tvshows', 'musicvideos')):
-                                utils.playlistXSP(mediatype, foldername, folderid, viewtype)
-                                playlists.append(foldername)
-                            # Add new video node
-                            if foldername not in nodes and mediatype != "musicvideos":
-                                vnodes.viewNode(sorted_views.index(foldername), foldername,
-                                    mediatype, viewtype, folderid)
-                                if viewtype == "mixed": # Change the value
-                                    sorted_views[sorted_views.index(foldername)] = "%ss" % foldername
-                                nodes.append(foldername)
-                                totalnodes += 1
-                        
-                        # Update items with new tag
-                        items = emby_db.getItem_byView(folderid)
-                        for item in items:
-                            # Remove the "s" from viewtype for tags
-                            kodi_db.updateTag(
-                                current_tagid, tagid, item[0], current_viewtype[:-1])
-                    else:
-                        # Validate the playlist exists or recreate it
-                        if mediatype != "music":
-                            if (foldername not in playlists and
-                                    mediatype in ('movies', 'tvshows', 'musicvideos')):
-                                utils.playlistXSP(mediatype, foldername, folderid, viewtype)
-                                playlists.append(foldername)
-                            # Create the video node if not already exists
-                            if foldername not in nodes and mediatype != "musicvideos":
-                                vnodes.viewNode(sorted_views.index(foldername), foldername,
-                                    mediatype, viewtype, folderid)
-                                if viewtype == "mixed": # Change the value
-                                    sorted_views[sorted_views.index(foldername)] = "%ss" % foldername
-                                nodes.append(foldername)
-                                totalnodes += 1
-        else:
-            # Add video nodes listings
-            vnodes.singleNode(totalnodes, "Favorite movies", "movies", "favourites")
-            totalnodes += 1
-            vnodes.singleNode(totalnodes, "Favorite tvshows", "tvshows", "favourites")
-            totalnodes += 1
-            vnodes.singleNode(totalnodes, "channels", "movies", "channels")
-            totalnodes += 1
-            # Save total
-            utils.window('Emby.nodes.total', str(totalnodes))
-
-            # Remove any old referenced views
-            log("Removing views: %s" % current_views, 1)
-            for view in current_views:
-                emby_db.removeView(view)
-
-    def movies(self, embycursor, kodicursor, pdialog):
-
-        log = self.logMsg
-        lang = utils.language
-        # Get movies from emby
-        emby = self.emby
-        emby_db = embydb.Embydb_Functions(embycursor)
-        movies = itemtypes.Movies(embycursor, kodicursor)
-
-        views = emby_db.getView_byType('movies')
-        views += emby_db.getView_byType('mixed')
-        log("Media folders: %s" % views, 1)
-
-        ##### PROCESS MOVIES #####
-        for view in views:
-            
-            if self.shouldStop():
-                return False
-
-            # Get items per view
-            viewId = view['id']
-            viewName = view['name']
-
-            if pdialog:
-                pdialog.update(
-                        heading="Emby for Kodi",
-                        message="%s %s..." % (lang(33017), viewName))
-
-            # Initial or repair sync
-            all_embymovies = emby.getMovies(viewId, dialog=pdialog)
-            total = all_embymovies['TotalRecordCount']
-            embymovies = all_embymovies['Items']
-
-            if pdialog:
-                pdialog.update(heading="Processing %s / %s items" % (viewName, total))
-
-            count = 0
-            for embymovie in embymovies:
-                # Process individual movies
-                if self.shouldStop():
-                    return False
-                
-                title = embymovie['Name']
-                if pdialog:
-                    percentage = int((float(count) / float(total))*100)
-                    pdialog.update(percentage, message=title)
-                    count += 1
-                movies.add_update(embymovie, viewName, viewId)
-        else:
-            log("Movies finished.", 2)
-
-
-        ##### PROCESS BOXSETS #####
-        if pdialog:
-            pdialog.update(heading="Emby for Kodi", message=lang(33018))
-        
-        boxsets = emby.getBoxset(dialog=pdialog)
-        total = boxsets['TotalRecordCount']
-        embyboxsets = boxsets['Items']
-            
-        if pdialog:
-            pdialog.update(heading="Processing Boxsets / %s items" % total)
-
-        count = 0
-        for boxset in embyboxsets:
-            # Process individual boxset
-            if self.shouldStop():
-                return False
-
-            title = boxset['Name']
-            if pdialog:
-                percentage = int((float(count) / float(total))*100)
-                pdialog.update(percentage, message=title)
-                count += 1
-            movies.add_updateBoxset(boxset)
-        else:
-            log("Boxsets finished.", 2)
-
-        return True
-
-    def musicvideos(self, embycursor, kodicursor, pdialog):
-
-        log = self.logMsg
-        # Get musicvideos from emby
-        emby = self.emby
-        emby_db = embydb.Embydb_Functions(embycursor)
-        mvideos = itemtypes.MusicVideos(embycursor, kodicursor)
-
-        views = emby_db.getView_byType('musicvideos')
-        log("Media folders: %s" % views, 1)
-
-        for view in views:
-            
-            if self.shouldStop():
-                return False
-
-            # Get items per view
-            viewId = view['id']
-            viewName = view['name']
-
-            if pdialog:
-                pdialog.update(
-                        heading="Emby for Kodi",
-                        message="%s %s..." % (utils.language(33019), viewName))
-
-            # Initial or repair sync
-            all_embymvideos = emby.getMusicVideos(viewId, dialog=pdialog)
-            total = all_embymvideos['TotalRecordCount']
-            embymvideos = all_embymvideos['Items']
-
-            if pdialog:
-                pdialog.update(heading="Processing %s / %s items" % (viewName, total))
-
-            count = 0
-            for embymvideo in embymvideos:
-                # Process individual musicvideo
-                if self.shouldStop():
-                    return False
-                
-                title = embymvideo['Name']
-                if pdialog:
-                    percentage = int((float(count) / float(total))*100)
-                    pdialog.update(percentage, message=title)
-                    count += 1
-                mvideos.add_update(embymvideo, viewName, viewId)
-        else:
-            log("MusicVideos finished.", 2)
-
-        return True
-
-    def tvshows(self, embycursor, kodicursor, pdialog):
-
-        log = self.logMsg
-        # Get shows from emby
-        emby = self.emby
-        emby_db = embydb.Embydb_Functions(embycursor)
-        tvshows = itemtypes.TVShows(embycursor, kodicursor)
-
-        views = emby_db.getView_byType('tvshows')
-        views += emby_db.getView_byType('mixed')
-        log("Media folders: %s" % views, 1)
-
-        for view in views:
-            
-            if self.shouldStop():
-                return False
-
-            # Get items per view
-            viewId = view['id']
-            viewName = view['name']
-
-            if pdialog:
-                pdialog.update(
-                        heading="Emby for Kodi",
-                        message="%s %s..." % (utils.language(33020), viewName))
-
-            all_embytvshows = emby.getShows(viewId, dialog=pdialog)
-            total = all_embytvshows['TotalRecordCount']
-            embytvshows = all_embytvshows['Items']
-
-            if pdialog:
-                pdialog.update(heading="Processing %s / %s items" % (viewName, total))
-
-            count = 0
-            for embytvshow in embytvshows:
-                # Process individual show
-                if self.shouldStop():
-                    return False
-                
-                itemid = embytvshow['Id']
-                title = embytvshow['Name']
-                if pdialog:
-                    percentage = int((float(count) / float(total))*100)
-                    pdialog.update(percentage, message=title)
-                    count += 1
-                tvshows.add_update(embytvshow, viewName, viewId)
-
-                # Process episodes
-                all_episodes = emby.getEpisodesbyShow(itemid)
-                for episode in all_episodes['Items']:
-
-                    # Process individual show
-                    if self.shouldStop():
-                        return False
-
-                    episodetitle = episode['Name']
-                    if pdialog:
-                        pdialog.update(percentage, message="%s - %s" % (title, episodetitle))
-                    tvshows.add_updateEpisode(episode)
-        else:
-            log("TVShows finished.", 2)
-
-        return True
-
-    def music(self, embycursor, kodicursor, pdialog):
-        # Get music from emby
-        emby = self.emby
-        emby_db = embydb.Embydb_Functions(embycursor)
-        music = itemtypes.Music(embycursor, kodicursor)
-
-        process = {
-
-            'artists': [emby.getArtists, music.add_updateArtist],
-            'albums': [emby.getAlbums, music.add_updateAlbum],
-            'songs': [emby.getSongs, music.add_updateSong]
-        }
-        types = ['artists', 'albums', 'songs']
-        for itemtype in types:
-
-            if pdialog:
-                pdialog.update(
-                    heading="Emby for Kodi",
-                    message="%s %s..." % (utils.language(33021), itemtype))
-
-            all_embyitems = process[itemtype][0](dialog=pdialog)
-            total = all_embyitems['TotalRecordCount']
-            embyitems = all_embyitems['Items']
-
-            if pdialog:
-                pdialog.update(heading="Processing %s / %s items" % (itemtype, total))
-
-            count = 0
-            for embyitem in embyitems:
-                # Process individual item
-                if self.shouldStop():
-                    return False
-                
-                title = embyitem['Name']
-                if pdialog:
-                    percentage = int((float(count) / float(total))*100)
-                    pdialog.update(percentage, message=title)
-                    count += 1
-
-                process[itemtype][1](embyitem)
-            else:
-                self.logMsg("%s finished." % itemtype, 2)
-
-        return True
-
-    # Reserved for websocket_client.py and fast start
-    def triage_items(self, process, items):
-
-        processlist = {
-
-            'added': self.addedItems,
-            'update': self.updateItems,
-            'userdata': self.userdataItems,
-            'remove': self.removeItems
-        }
-        if items:
-            if process == "userdata":
-                itemids = []
-                for item in items:
-                    itemids.append(item['ItemId'])
-                items = itemids
-
-            self.logMsg("Queue %s: %s" % (process, items), 1)
-            processlist[process].extend(items)
-
-    def incrementalSync(self):
-        
-        log = self.logMsg
-
-        embyconn = utils.kodiSQL('emby')
-        embycursor = embyconn.cursor()
-        kodiconn = utils.kodiSQL('video')
-        kodicursor = kodiconn.cursor()
-        emby = self.emby
-        emby_db = embydb.Embydb_Functions(embycursor)
-        pDialog = None
-        update_embydb = False
-
-        if self.refresh_views:
-            # Received userconfig update
-            self.refresh_views = False
-            self.maintainViews(embycursor, kodicursor)
-            self.forceLibraryUpdate = True
-            update_embydb = True
-
-        if self.addedItems or self.updateItems or self.userdataItems or self.removeItems:
-            # Only present dialog if we are going to process items
-            pDialog = self.progressDialog('Incremental sync')
-
-
-        process = {
-
-            'added': self.addedItems,
-            'update': self.updateItems,
-            'userdata': self.userdataItems,
-            'remove': self.removeItems
-        }
-        types = ['added', 'update', 'userdata', 'remove']
-        for type in types:
-
-            if process[type] and utils.window('emby_kodiScan') != "true":
-                
-                listItems = list(process[type])
-                del process[type][:] # Reset class list
-
-                items_process = itemtypes.Items(embycursor, kodicursor)
-                update = False
-
-                # Prepare items according to process type
-                if type == "added":
-                    items = emby.sortby_mediatype(listItems)
-
-                elif type in ("userdata", "remove"):
-                    items = emby_db.sortby_mediaType(listItems, unsorted=False)
-                
-                else:
-                    items = emby_db.sortby_mediaType(listItems)
-                    if items.get('Unsorted'):
-                        sorted_items = emby.sortby_mediatype(items['Unsorted'])
-                        doupdate = items_process.itemsbyId(sorted_items, "added", pDialog)
-                        if doupdate:
-                            embyupdate, kodiupdate_video = doupdate
-                            if embyupdate:
-                                update_embydb = True
-                            if kodiupdate_video:
-                                self.forceLibraryUpdate = True
-                        del items['Unsorted']
-
-                doupdate = items_process.itemsbyId(items, type, pDialog)
-                if doupdate:
-                    embyupdate, kodiupdate_video = doupdate
-                    if embyupdate:
-                        update_embydb = True
-                    if kodiupdate_video:
-                        self.forceLibraryUpdate = True
-
-        if update_embydb:
-            update_embydb = False
-            log("Updating emby database.", 1)
-            embyconn.commit()
-            self.saveLastSync()
-
-        if self.forceLibraryUpdate:
-            # Force update the Kodi library
-            self.forceLibraryUpdate = False
-            self.dbCommit(kodiconn)
-
-            log("Updating video library.", 1)
-            utils.window('emby_kodiScan', value="true")
-            xbmc.executebuiltin('UpdateLibrary(video)')
-
-        if pDialog:
-            pDialog.close()
-
-        kodicursor.close()
-        embycursor.close()
-
-
-    def compareDBVersion(self, current, minimum):
-        # It returns True is database is up to date. False otherwise.
-        self.logMsg("current: %s minimum: %s" % (current, minimum), 1)
-        currMajor, currMinor, currPatch = current.split(".")
-        minMajor, minMinor, minPatch = minimum.split(".")
-
-        if currMajor > minMajor:
-            return True
-        elif currMajor == minMajor and (currMinor > minMinor or
-                                       (currMinor == minMinor and currPatch >= minPatch)):
-            return True
-        else:
-            # Database out of date.
-            return False
-
-    def run(self):
-    
-        try:
-            self.run_internal()
-        except Exception as e:
-            utils.window('emby_dbScan', clear=True)
-            xbmcgui.Dialog().ok(
-                        heading="Emby for Kodi",
-                        line1=(
-                            "Library sync thread has exited! "
-                            "You should restart Kodi now. "
-                            "Please report this on the forum."))
-            raise
-
-    def run_internal(self):
-
-        log = self.logMsg
-        lang = utils.language
-        window = utils.window
-        settings = utils.settings
-        dialog = xbmcgui.Dialog()
-
-        startupComplete = False
-        monitor = self.monitor
-
-        log("---===### Starting LibrarySync ###===---", 0)
-
-        while not monitor.abortRequested():
-
-            # In the event the server goes offline
-            while self.suspend_thread:
-                # Set in service.py
-                if monitor.waitForAbort(5):
-                    # Abort was requested while waiting. We should exit
-                    break
-
-            if (window('emby_dbCheck') != "true" and settings('SyncInstallRunDone') == "true"):
-                # Verify the validity of the database
-                currentVersion = settings('dbCreatedWithVersion')
-                minVersion = window('emby_minDBVersion')
-                uptoDate = self.compareDBVersion(currentVersion, minVersion)
-
-                if not uptoDate:
-                    log("Database version out of date: %s minimum version required: %s"
-                        % (currentVersion, minVersion), 0)
-                    
-                    resp = dialog.yesno("Emby for Kodi", lang(33022))
-                    if not resp:
-                        log("Database version is out of date! USER IGNORED!", 0)
-                        dialog.ok("Emby for Kodi", lang(33023))
-                    else:
-                        utils.reset()
-
-                    break
-
-                window('emby_dbCheck', value="true")
-
-
-            if not startupComplete:
-                # Verify the video database can be found
-                videoDb = utils.getKodiVideoDBPath()
-                if not xbmcvfs.exists(videoDb):
-                    # Database does not exists
-                    log(
-                        "The current Kodi version is incompatible "
-                        "with the Emby for Kodi add-on. Please visit "
-                        "https://github.com/MediaBrowser/Emby.Kodi/wiki "
-                        "to know which Kodi versions are supported.", 0)
-
-                    dialog.ok(
-                            heading="Emby for Kodi",
-                            line1=lang(33024))
-                    break
-
-                # Run start up sync
-                log("Database version: %s" % settings('dbCreatedWithVersion'), 0)
-                log("SyncDatabase (started)", 1)
-                startTime = datetime.now()
-                librarySync = self.startSync()
-                elapsedTime = datetime.now() - startTime
-                log("SyncDatabase (finished in: %s) %s"
-                    % (str(elapsedTime).split('.')[0], librarySync), 1)
-                # Only try the initial sync once per kodi session regardless
-                # This will prevent an infinite loop in case something goes wrong.
-                startupComplete = True
-
-            # Process updates
-            if window('emby_dbScan') != "true":
-                self.incrementalSync()
-
-            if window('emby_onWake') == "true" and window('emby_online') == "true":
-                # Kodi is waking up
-                # Set in kodimonitor.py
-                window('emby_onWake', clear=True)
-                if window('emby_syncRunning') != "true":
-                    log("SyncDatabase onWake (started)", 0)
-                    librarySync = self.startSync()
-                    log("SyncDatabase onWake (finished) %s" % librarySync, 0)
-
-            if self.stop_thread:
-                # Set in service.py
-                log("Service terminated thread.", 2)
-                break
-
-            if monitor.waitForAbort(1):
-                # Abort was requested while waiting. We should exit
-                break
-
-        log("###===--- LibrarySync Stopped ---===###", 0)
-
-    def stopThread(self):
-        self.stop_thread = True
-        self.logMsg("Ending thread...", 2)
-
-    def suspendThread(self):
-        self.suspend_thread = True
-        self.logMsg("Pausing thread...", 0)
-
-    def resumeThread(self):
-        self.suspend_thread = False
-        self.logMsg("Resuming thread...", 0)
-
-
-class ManualSync(LibrarySync):
-
-
-    def __init__(self):
-
-        LibrarySync.__init__(self)
-
-    def sync(self, dialog=False):
-
-        return self.fullSync(manualrun=True, forceddialog=dialog)
-        
-
-    def movies(self, embycursor, kodicursor, pdialog):
-
-        log = self.logMsg
-        lang = utils.language
-        # Get movies from emby
-        emby = self.emby
-        emby_db = embydb.Embydb_Functions(embycursor)
-        movies = itemtypes.Movies(embycursor, kodicursor)
-
-        views = emby_db.getView_byType('movies')
-        views += emby_db.getView_byType('mixed')
-        log("Media folders: %s" % views, 1)
-
-        # Pull the list of movies and boxsets in Kodi
-        try:
-            all_kodimovies = dict(emby_db.getChecksum('Movie'))
-        except ValueError:
-            all_kodimovies = {}
-
-        try:
-            all_kodisets = dict(emby_db.getChecksum('BoxSet'))
-        except ValueError:
-            all_kodisets = {}
-
-        all_embymoviesIds = set()
-        all_embyboxsetsIds = set()
-        updatelist = []
-
-        ##### PROCESS MOVIES #####
-        for view in views:
-            
-            if self.shouldStop():
-                return False
-
-            # Get items per view
-            viewId = view['id']
-            viewName = view['name']
-
-            if pdialog:
-                pdialog.update(
-                        heading="Emby for Kodi",
-                        message="%s %s..." % (lang(33026), viewName))
-
-            all_embymovies = emby.getMovies(viewId, basic=True, dialog=pdialog)
-            for embymovie in all_embymovies['Items']:
-
-                if self.shouldStop():
-                    return False
-
-                API = api.API(embymovie)
-                itemid = embymovie['Id']
-                all_embymoviesIds.add(itemid)
-
-                
-                if all_kodimovies.get(itemid) != API.getChecksum():
-                    # Only update if movie is not in Kodi or checksum is different
-                    updatelist.append(itemid)
-
-            log("Movies to update for %s: %s" % (viewName, updatelist), 1)
-            embymovies = emby.getFullItems(updatelist)
-            total = len(updatelist)
-            del updatelist[:]
-
-            if pdialog:
-                pdialog.update(heading="Processing %s / %s items" % (viewName, total))
-
-            count = 0
-            for embymovie in embymovies:
-                # Process individual movies
-                if self.shouldStop():
-                    return False
-                
-                title = embymovie['Name']
-                if pdialog:
-                    percentage = int((float(count) / float(total))*100)
-                    pdialog.update(percentage, message=title)
-                    count += 1
-                movies.add_update(embymovie, viewName, viewId)
-
-        ##### PROCESS BOXSETS #####
-      
-        boxsets = emby.getBoxset(dialog=pdialog)
-        embyboxsets = []
-
-        if pdialog:
-            pdialog.update(heading="Emby for Kodi", message=lang(33027))
-
-        for boxset in boxsets['Items']:
-
-            if self.shouldStop():
-                return False
-
-            # Boxset has no real userdata, so using etag to compare
-            checksum = boxset['Etag']
-            itemid = boxset['Id']
-            all_embyboxsetsIds.add(itemid)
-
-            if all_kodisets.get(itemid) != checksum:
-                # Only update if boxset is not in Kodi or checksum is different
-                updatelist.append(itemid)
-                embyboxsets.append(boxset)
-
-        log("Boxsets to update: %s" % updatelist, 1)
-        total = len(updatelist)
-            
-        if pdialog:
-            pdialog.update(heading="Processing Boxsets / %s items" % total)
-
-        count = 0
-        for boxset in embyboxsets:
-            # Process individual boxset
-            if self.shouldStop():
-                return False
-
-            title = boxset['Name']
-            if pdialog:
-                percentage = int((float(count) / float(total))*100)
-                pdialog.update(percentage, message=title)
-                count += 1
-            movies.add_updateBoxset(boxset)
-
-        ##### PROCESS DELETES #####
-
-        for kodimovie in all_kodimovies:
-            if kodimovie not in all_embymoviesIds:
-                movies.remove(kodimovie)
-        else:
-            log("Movies compare finished.", 1)
-
-        for boxset in all_kodisets:
-            if boxset not in all_embyboxsetsIds:
-                movies.remove(boxset)
-        else:
-            log("Boxsets compare finished.", 1)
-
-        return True
-
-    def musicvideos(self, embycursor, kodicursor, pdialog):
-
-        log = self.logMsg
-        # Get musicvideos from emby
-        emby = self.emby
-        emby_db = embydb.Embydb_Functions(embycursor)
-        mvideos = itemtypes.MusicVideos(embycursor, kodicursor)
-
-        views = emby_db.getView_byType('musicvideos')
-        log("Media folders: %s" % views, 1)
-
-        # Pull the list of musicvideos in Kodi
-        try:
-            all_kodimvideos = dict(emby_db.getChecksum('MusicVideo'))
-        except ValueError:
-            all_kodimvideos = {}
-
-        all_embymvideosIds = set()
-        updatelist = []
-
-        for view in views:
-            
-            if self.shouldStop():
-                return False
-
-            # Get items per view
-            viewId = view['id']
-            viewName = view['name']
-
-            if pdialog:
-                pdialog.update(
-                        heading="Emby for Kodi",
-                        message="%s %s..." % (utils.language(33028), viewName))
-
-            all_embymvideos = emby.getMusicVideos(viewId, basic=True, dialog=pdialog)
-            for embymvideo in all_embymvideos['Items']:
-
-                if self.shouldStop():
-                    return False
-
-                API = api.API(embymvideo)
-                itemid = embymvideo['Id']
-                all_embymvideosIds.add(itemid)
-
-                
-                if all_kodimvideos.get(itemid) != API.getChecksum():
-                    # Only update if musicvideo is not in Kodi or checksum is different
-                    updatelist.append(itemid)
-
-            log("MusicVideos to update for %s: %s" % (viewName, updatelist), 1)
-            embymvideos = emby.getFullItems(updatelist)
-            total = len(updatelist)
-            del updatelist[:]
-
-
-            if pdialog:
-                pdialog.update(heading="Processing %s / %s items" % (viewName, total))
-
-            count = 0
-            for embymvideo in embymvideos:
-                # Process individual musicvideo
-                if self.shouldStop():
-                    return False
-                
-                title = embymvideo['Name']
-                if pdialog:
-                    percentage = int((float(count) / float(total))*100)
-                    pdialog.update(percentage, message=title)
-                    count += 1
-                mvideos.add_update(embymvideo, viewName, viewId)
-        
-        ##### PROCESS DELETES #####
-
-        for kodimvideo in all_kodimvideos:
-            if kodimvideo not in all_embymvideosIds:
-                mvideos.remove(kodimvideo)
-        else:
-            log("MusicVideos compare finished.", 1)
-
-        return True
-
-    def tvshows(self, embycursor, kodicursor, pdialog):
-
-        log = self.logMsg
-        lang = utils.language
-        # Get shows from emby
-        emby = self.emby
-        emby_db = embydb.Embydb_Functions(embycursor)
-        tvshows = itemtypes.TVShows(embycursor, kodicursor)
-
-        views = emby_db.getView_byType('tvshows')
-        views += emby_db.getView_byType('mixed')
-        log("Media folders: %s" % views, 1)
-
-        # Pull the list of tvshows and episodes in Kodi
-        try:
-            all_koditvshows = dict(emby_db.getChecksum('Series'))
-        except ValueError:
-            all_koditvshows = {}
-
-        try:
-            all_kodiepisodes = dict(emby_db.getChecksum('Episode'))
-        except ValueError:
-            all_kodiepisodes = {}
-
-        all_embytvshowsIds = set()
-        all_embyepisodesIds = set()
-        updatelist = []
-
-
-        for view in views:
-            
-            if self.shouldStop():
-                return False
-
-            # Get items per view
-            viewId = view['id']
-            viewName = view['name']
-
-            if pdialog:
-                pdialog.update(
-                        heading="Emby for Kodi",
-                        message="%s %s..." % (lang(33029), viewName))
-
-            all_embytvshows = emby.getShows(viewId, basic=True, dialog=pdialog)
-            for embytvshow in all_embytvshows['Items']:
-
-                if self.shouldStop():
-                    return False
-
-                API = api.API(embytvshow)
-                itemid = embytvshow['Id']
-                all_embytvshowsIds.add(itemid)
-
-                
-                if all_koditvshows.get(itemid) != API.getChecksum():
-                    # Only update if movie is not in Kodi or checksum is different
-                    updatelist.append(itemid)
-
-            log("TVShows to update for %s: %s" % (viewName, updatelist), 1)
-            embytvshows = emby.getFullItems(updatelist)
-            total = len(updatelist)
-            del updatelist[:]
-
-
-            if pdialog:
-                pdialog.update(heading="Processing %s / %s items" % (viewName, total))
-
-            count = 0
-            for embytvshow in embytvshows:
-                # Process individual show
-                if self.shouldStop():
-                    return False
-                
-                itemid = embytvshow['Id']
-                title = embytvshow['Name']
-                if pdialog:
-                    percentage = int((float(count) / float(total))*100)
-                    pdialog.update(percentage, message=title)
-                    count += 1
-                tvshows.add_update(embytvshow, viewName, viewId)
-
-            else:
-                # Get all episodes in view
-                if pdialog:
-                    pdialog.update(
-                            heading="Emby for Kodi",
-                            message="%s %s..." % (lang(33030), viewName))
-
-                all_embyepisodes = emby.getEpisodes(viewId, basic=True, dialog=pdialog)
-                for embyepisode in all_embyepisodes['Items']:
-
-                    if self.shouldStop():
-                        return False
-
-                    API = api.API(embyepisode)
-                    itemid = embyepisode['Id']
-                    all_embyepisodesIds.add(itemid)
-
-                    if all_kodiepisodes.get(itemid) != API.getChecksum():
-                        # Only update if movie is not in Kodi or checksum is different
-                        updatelist.append(itemid)
-
-                log("Episodes to update for %s: %s" % (viewName, updatelist), 1)
-                embyepisodes = emby.getFullItems(updatelist)
-                total = len(updatelist)
-                del updatelist[:]
-
-                count = 0
-                for episode in embyepisodes:
-
-                    # Process individual episode
-                    if self.shouldStop():
-                        return False                          
-
-                    title = episode['SeriesName']
-                    episodetitle = episode['Name']
-                    if pdialog:
-                        percentage = int((float(count) / float(total))*100)
-                        pdialog.update(percentage, message="%s - %s" % (title, episodetitle))
-                        count += 1
-                    tvshows.add_updateEpisode(episode)
-        
-        ##### PROCESS DELETES #####
-
-        for koditvshow in all_koditvshows:
-            if koditvshow not in all_embytvshowsIds:
-                tvshows.remove(koditvshow)
-        else:
-            log("TVShows compare finished.", 1)
-
-        for kodiepisode in all_kodiepisodes:
-            if kodiepisode not in all_embyepisodesIds:
-                tvshows.remove(kodiepisode)
-        else:
-            log("Episodes compare finished.", 1)
-
-        return True
-
-    def music(self, embycursor, kodicursor, pdialog):
-
-        log = self.logMsg
-        # Get music from emby
-        emby = self.emby
-        emby_db = embydb.Embydb_Functions(embycursor)
-        music = itemtypes.Music(embycursor, kodicursor)
-
-        # Pull the list of artists, albums, songs
-        try:
-            all_kodiartists = dict(emby_db.getChecksum('MusicArtist'))
-        except ValueError:
-            all_kodiartists = {}
-
-        try:
-            all_kodialbums = dict(emby_db.getChecksum('MusicAlbum'))
-        except ValueError:
-            all_kodialbums = {}
-
-        try:
-            all_kodisongs = dict(emby_db.getChecksum('Audio'))
-        except ValueError:
-            all_kodisongs = {}
-
-        all_embyartistsIds = set()
-        all_embyalbumsIds = set()
-        all_embysongsIds = set()
-        updatelist = []
-
-        process = {
-
-            'artists': [emby.getArtists, music.add_updateArtist],
-            'albums': [emby.getAlbums, music.add_updateAlbum],
-            'songs': [emby.getSongs, music.add_updateSong]
-        }
-        types = ['artists', 'albums', 'songs']
-        for type in types:
-
-            if pdialog:
-                pdialog.update(
-                        heading="Emby for Kodi",
-                        message="%s %s..." % (utils.language(33031), type))
-
-            if type != "artists":
-                all_embyitems = process[type][0](basic=True, dialog=pdialog)
-            else:
-                all_embyitems = process[type][0](dialog=pdialog)
-            for embyitem in all_embyitems['Items']:
-
-                if self.shouldStop():
-                    return False
-
-                API = api.API(embyitem)
-                itemid = embyitem['Id']
-                if type == "artists":
-                    all_embyartistsIds.add(itemid)
-                    if all_kodiartists.get(itemid) != API.getChecksum():
-                        # Only update if artist is not in Kodi or checksum is different
-                        updatelist.append(itemid)
-                elif type == "albums":
-                    all_embyalbumsIds.add(itemid)
-                    if all_kodialbums.get(itemid) != API.getChecksum():
-                        # Only update if album is not in Kodi or checksum is different
-                        updatelist.append(itemid)
-                else:
-                    all_embysongsIds.add(itemid)
-                    if all_kodisongs.get(itemid) != API.getChecksum():
-                        # Only update if songs is not in Kodi or checksum is different
-                        updatelist.append(itemid)
-
-            log("%s to update: %s" % (type, updatelist), 1)
-            embyitems = emby.getFullItems(updatelist)
-            total = len(updatelist)
-            del updatelist[:]
-
-            if pdialog:
-                pdialog.update(heading="Processing %s / %s items" % (type, total))
-
-            count = 0
-            for embyitem in embyitems:
-                # Process individual item
-                if self.shouldStop():
-                    return False
-                
-                title = embyitem['Name']
-                if pdialog:
-                    percentage = int((float(count) / float(total))*100)
-                    pdialog.update(percentage, message=title)
-                    count += 1
-
-                process[type][1](embyitem)
-
-        ##### PROCESS DELETES #####
-
-        for kodiartist in all_kodiartists:
-            if kodiartist not in all_embyartistsIds and all_kodiartists[kodiartist] is not None:
-                music.remove(kodiartist)
-        else:
-            log("Artist compare finished.", 1)
-
-        for kodialbum in all_kodialbums:
-            if kodialbum not in all_embyalbumsIds:
-                music.remove(kodialbum)
-        else:
-            log("Albums compare finished.", 1)
-
-        for kodisong in all_kodisongs:
-            if kodisong not in all_embysongsIds:
-                music.remove(kodisong)
-        else:
-            log("Songs compare finished.", 1)
-
-        return True
\ No newline at end of file
+# -*- coding: utf-8 -*-
+
+##################################################################################################
+
+import sqlite3
+import threading
+from datetime import datetime, timedelta, time
+
+import xbmc
+import xbmcgui
+import xbmcvfs
+
+import api
+import utils
+import clientinfo
+import downloadutils
+import itemtypes
+import embydb_functions as embydb
+import kodidb_functions as kodidb
+import read_embyserver as embyserver
+import userclient
+import videonodes
+
+##################################################################################################
+
+
+class LibrarySync(threading.Thread):
+
+    _shared_state = {}
+
+    stop_thread = False
+    suspend_thread = False
+
+    # Track websocketclient updates
+    addedItems = []
+    updateItems = []
+    userdataItems = []
+    removeItems = []
+    forceLibraryUpdate = False
+    refresh_views = False
+
+
+    def __init__(self):
+
+        self.__dict__ = self._shared_state
+        self.monitor = xbmc.Monitor()
+
+        self.clientInfo = clientinfo.ClientInfo()
+        self.addonName = self.clientInfo.getAddonName()
+        self.doUtils = downloadutils.DownloadUtils().downloadUrl
+        self.user = userclient.UserClient()
+        self.emby = embyserver.Read_EmbyServer()
+        self.vnodes = videonodes.VideoNodes()
+
+        threading.Thread.__init__(self)
+
+    def logMsg(self, msg, lvl=1):
+
+        className = self.__class__.__name__
+        utils.logMsg("%s %s" % (self.addonName, className), msg, lvl)
+
+
+    def progressDialog(self, title, forced=False):
+
+        dialog = None
+
+        if utils.settings('dbSyncIndicator') == "true" or forced:
+            dialog = xbmcgui.DialogProgressBG()
+            dialog.create("Emby for Kodi", title)
+            self.logMsg("Show progress dialog: %s" % title, 2)
+
+        return dialog
+
+    def startSync(self):
+
+        settings = utils.settings
+        # Run at start up - optional to use the server plugin
+        if settings('SyncInstallRunDone') == "true":
+
+            # Validate views
+            self.refreshViews()
+            completed = False
+            # Verify if server plugin is installed.
+            if settings('serverSync') == "true":
+                # Try to use fast start up
+                url = "{server}/emby/Plugins?format=json"
+                result = self.doUtils(url)
+
+                for plugin in result:
+                    if plugin['Name'] == "Emby.Kodi Sync Queue":
+                        self.logMsg("Found server plugin.", 2)
+                        completed = self.fastSync()
+                        break
+
+            if not completed:
+                # Fast sync failed or server plugin is not found
+                completed = ManualSync().sync()
+        else:
+            # Install sync is not completed
+            completed = self.fullSync()
+
+        return completed
+
+    def fastSync(self):
+
+        lastSync = utils.settings('LastIncrementalSync')
+        if not lastSync:
+            lastSync = "2010-01-01T00:00:00Z"
+
+        lastSyncTime = utils.convertdate(lastSync)
+        self.logMsg("Last sync run: %s" % lastSyncTime, 1)
+
+        # get server RetentionDateTime
+        result = self.doUtils("{server}/emby/Emby.Kodi.SyncQueue/GetServerDateTime?format=json")
+        retention_time = "2010-01-01T00:00:00Z"
+        if result and result.get('RetentionDateTime'):
+            retention_time = result['RetentionDateTime']
+
+        #Try/except equivalent
+        '''
+        try:
+            retention_time = result['RetentionDateTime']
+        except (TypeError, KeyError):
+            retention_time = "2010-01-01T00:00:00Z"
+        '''
+
+        retention_time = utils.convertdate(retention_time)
+        self.logMsg("RetentionDateTime: %s" % retention_time, 1)
+
+        # if last sync before retention time do a full sync
+        if retention_time > lastSyncTime:
+            self.logMsg("Fast sync server retention insufficient, fall back to full sync", 1)
+            return False
+
+        params = {'LastUpdateDT': lastSync}
+        result = self.doUtils("{server}/emby/Emby.Kodi.SyncQueue/{UserId}/GetItems?format=json", parameters=params)
+
+        try:
+            processlist = {
+
+                'added': result['ItemsAdded'],
+                'update': result['ItemsUpdated'],
+                'userdata': result['UserDataChanged'],
+                'remove': result['ItemsRemoved']
+            }
+
+        except (KeyError, TypeError):
+            self.logMsg("Failed to retrieve latest updates using fast sync.", 1)
+            return False
+
+        else:
+            self.logMsg("Fast sync changes: %s" % result, 1)
+            for action in processlist:
+                self.triage_items(action, processlist[action])
+
+            return True
+
+    def saveLastSync(self):
+
+        # Save last sync time
+        overlap = 2
+
+        result = self.doUtils("{server}/emby/Emby.Kodi.SyncQueue/GetServerDateTime?format=json")
+        try: # datetime fails when used more than once, TypeError
+            server_time = result['ServerDateTime']
+            server_time = utils.convertdate(server_time)
+
+        except Exception as e:
+            # If the server plugin is not installed or an error happened.
+            self.logMsg("An exception occurred: %s" % e, 1)
+            time_now = datetime.utcnow()-timedelta(minutes=overlap)
+            lastSync = time_now.strftime('%Y-%m-%dT%H:%M:%SZ')
+            self.logMsg("New sync time: client time -%s min: %s" % (overlap, lastSync), 1)
+
+        else:
+            lastSync = (server_time - timedelta(minutes=overlap)).strftime('%Y-%m-%dT%H:%M:%SZ')
+            self.logMsg("New sync time: server time -%s min: %s" % (overlap, lastSync), 1)
+
+        finally:
+            utils.settings('LastIncrementalSync', value=lastSync)
+
+    def shouldStop(self):
+        # Checkpoint during the syncing process
+        if self.monitor.abortRequested():
+            return True
+        elif utils.window('emby_shouldStop') == "true":
+            return True
+        else: # Keep going
+            return False
+
+    def dbCommit(self, connection):
+
+        window = utils.window
+        # Central commit, verifies if Kodi database update is running
+        kodidb_scan = window('emby_kodiScan') == "true"
+
+        while kodidb_scan:
+
+            self.logMsg("Kodi scan is running. Waiting...", 1)
+            kodidb_scan = window('emby_kodiScan') == "true"
+
+            if self.shouldStop():
+                self.logMsg("Commit unsuccessful. Sync terminated.", 1)
+                break
+
+            if self.monitor.waitForAbort(1):
+                # Abort was requested while waiting. We should exit
+                self.logMsg("Commit unsuccessful.", 1)
+                break
+        else:
+            connection.commit()
+            self.logMsg("Commit successful.", 1)
+
+    def fullSync(self, manualrun=False, repair=False, forceddialog=False):
+
+        window = utils.window
+        settings = utils.settings
+        # Only run once when first setting up. Can be run manually.
+        music_enabled = utils.settings('enableMusic') == "true"
+
+        xbmc.executebuiltin('InhibitIdleShutdown(true)')
+        screensaver = utils.getScreensaver()
+        utils.setScreensaver(value="")
+        window('emby_dbScan', value="true")
+        # Add sources
+        utils.sourcesXML()
+
+        embyconn = utils.kodiSQL('emby')
+        embycursor = embyconn.cursor()
+        # Create the tables for the emby database
+        # emby, view, version
+        embycursor.execute(
+            """CREATE TABLE IF NOT EXISTS emby(
+            emby_id TEXT UNIQUE, media_folder TEXT, emby_type TEXT, media_type TEXT, kodi_id INTEGER,
+            kodi_fileid INTEGER, kodi_pathid INTEGER, parent_id INTEGER, checksum INTEGER)""")
+        embycursor.execute(
+            """CREATE TABLE IF NOT EXISTS view(
+            view_id TEXT UNIQUE, view_name TEXT, media_type TEXT, kodi_tagid INTEGER)""")
+        embycursor.execute("CREATE TABLE IF NOT EXISTS version(idVersion TEXT)")
+        embyconn.commit()
+
+        # content sync: movies, tvshows, musicvideos, music
+        kodiconn = utils.kodiSQL('video')
+        kodicursor = kodiconn.cursor()
+
+        if manualrun:
+            message = "Manual sync"
+        elif repair:
+            message = "Repair sync"
+            forceddialog = True
+        else:
+            message = "Initial sync"
+            forceddialog = True
+            window('emby_initialScan', value="true")
+
+        pDialog = self.progressDialog("%s" % message, forced=forceddialog)
+        starttotal = datetime.now()
+
+        # Set views
+        self.maintainViews(embycursor, kodicursor)
+        embyconn.commit()
+
+        # Sync video library
+        process = {
+
+            'movies': self.movies,
+            'musicvideos': self.musicvideos,
+            'tvshows': self.tvshows
+        }
+        for itemtype in process:
+            startTime = datetime.now()
+            completed = process[itemtype](embycursor, kodicursor, pDialog)
+            if not completed:
+                xbmc.executebuiltin('InhibitIdleShutdown(false)')
+                utils.setScreensaver(value=screensaver)
+                window('emby_dbScan', clear=True)
+                if pDialog:
+                    pDialog.close()
+
+                embycursor.close()
+                kodicursor.close()
+                return False
+            else:
+                self.dbCommit(kodiconn)
+                embyconn.commit()
+                elapsedTime = datetime.now() - startTime
+                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:
+
+            musicconn = utils.kodiSQL('music')
+            musiccursor = musicconn.cursor()
+
+            startTime = datetime.now()
+            completed = self.music(embycursor, musiccursor, pDialog)
+            if not completed:
+                xbmc.executebuiltin('InhibitIdleShutdown(false)')
+                utils.setScreensaver(value=screensaver)
+                window('emby_dbScan', clear=True)
+                if pDialog:
+                    pDialog.close()
+
+                embycursor.close()
+                musiccursor.close()
+                return False
+            else:
+                musicconn.commit()
+                embyconn.commit()
+                elapsedTime = datetime.now() - startTime
+                self.logMsg("SyncDatabase (finished music in: %s)"
+                    % (str(elapsedTime).split('.')[0]), 1)
+            musiccursor.close()
+
+        if pDialog:
+            pDialog.close()
+
+        embycursor.close()
+
+        settings('SyncInstallRunDone', value="true")
+        settings("dbCreatedWithVersion", self.clientInfo.getVersion())
+        self.saveLastSync()
+        xbmc.executebuiltin('UpdateLibrary(video)')
+        elapsedtotal = datetime.now() - starttotal
+
+        xbmc.executebuiltin('InhibitIdleShutdown(false)')
+        utils.setScreensaver(value=screensaver)
+        window('emby_dbScan', clear=True)
+        window('emby_initialScan', clear=True)
+        if forceddialog:
+            xbmcgui.Dialog().notification(
+                        heading="Emby for Kodi",
+                        message="%s %s %s" %
+                                (message, utils.language(33025), str(elapsedtotal).split('.')[0]),
+                        icon="special://home/addons/plugin.video.emby/icon.png",
+                        sound=False)
+        return True
+
+
+    def refreshViews(self):
+
+        embyconn = utils.kodiSQL('emby')
+        embycursor = embyconn.cursor()
+        kodiconn = utils.kodiSQL('video')
+        kodicursor = kodiconn.cursor()
+
+        # Compare views, assign correct tags to items
+        self.maintainViews(embycursor, kodicursor)
+
+        self.dbCommit(kodiconn)
+        kodicursor.close()
+
+        embyconn.commit()
+        embycursor.close()
+
+    def maintainViews(self, embycursor, kodicursor):
+
+        # Compare the views to emby
+        emby = self.emby
+        emby_db = embydb.Embydb_Functions(embycursor)
+        kodi_db = kodidb.Kodidb_Functions(kodicursor)
+
+        # Get views
+        result = self.doUtils("{server}/emby/Users/{UserId}/Views?format=json")
+        grouped_views = result['Items']
+        ordered_views = self.emby.getViews(sortedlist=True)
+        all_views = []
+        sorted_views = []
+        for view in ordered_views:
+            all_views.append(view['name'])
+            if view['type'] == "music":
+                continue
+
+            if view['type'] == "mixed":
+                sorted_views.append(view['name'])
+            sorted_views.append(view['name'])
+        self.logMsg("Sorted views: %s" % sorted_views, 1)
+
+        # total nodes for window properties
+        self.vnodes.clearProperties()
+        totalnodes = len(sorted_views) + 0
+
+        current_views = emby_db.getViews()
+        # Set views for supported media type
+        emby_mediatypes = {
+
+            'movies': "Movie",
+            'tvshows': "Series",
+            'musicvideos': "MusicVideo",
+            'homevideos': "Video",
+            'music': "Audio",
+            'photos': "Photo"
+        }
+        for mediatype in ['movies', 'tvshows', 'musicvideos', 'homevideos', 'music', 'photos']:
+
+            nodes = [] # Prevent duplicate for nodes of the same type
+            playlists = [] # Prevent duplicate for playlists of the same type
+            # Get media folders from server
+            folders = self.emby.getViews(mediatype, root=True)
+            for folder in folders:
+
+                folderid = folder['id']
+                foldername = folder['name']
+                viewtype = folder['type']
+
+                if foldername not in all_views:
+                    # Media folders are grouped into userview
+                    params = {
+                        'ParentId': folderid,
+                        'Recursive': True,
+                        'Limit': 1,
+                        'IncludeItemTypes': emby_mediatypes[mediatype]
+                    } # Get one item from server using the folderid
+                    result = self.doUtils("{server}/emby/Users/{UserId}/Items?format=json", parameters=params)
+                    try:
+                        verifyitem = result['Items'][0]['Id']
+                    except (TypeError, IndexError):
+                        # Something is wrong. Keep the same folder name.
+                        # Could be the view is empty or the connection
+                        pass
+                    else:
+                        for grouped_view in grouped_views:
+                            # This is only reserved for the detection of grouped views
+                            if (grouped_view['Type'] == "UserView" and
+                                grouped_view.get('CollectionType') == mediatype):
+                                # Take the userview, and validate the item belong to the view
+                                if self.emby.verifyView(grouped_view['Id'], verifyitem):
+                                    # Take the name of the userview
+                                    self.logMsg("Found corresponding view: %s %s"
+                                        % (grouped_view['Name'], grouped_view['Id']), 1)
+                                    foldername = grouped_view['Name']
+                                    break
+                        else:
+                            # Unable to find a match, add the name to our sorted_view list
+                            sorted_views.append(foldername)
+                            self.logMsg("Couldn't find corresponding grouped view: %s" % sorted_views, 1)
+
+                # Failsafe
+                try:
+                    sorted_views.index(foldername)
+                except ValueError:
+                    sorted_views.append(foldername)
+
+                # Get current media folders from emby database
+                view = emby_db.getView_byId(folderid)
+                try:
+                    current_viewname = view[0]
+                    current_viewtype = view[1]
+                    current_tagid = view[2]
+
+                except TypeError:
+                    self.logMsg("Creating viewid: %s in Emby database." % folderid, 1)
+                    tagid = kodi_db.createTag(foldername)
+                    # Create playlist for the video library
+                    if (foldername not in playlists and
+                            mediatype in ('movies', 'tvshows', 'musicvideos')):
+                        utils.playlistXSP(mediatype, foldername, folderid, viewtype)
+                        playlists.append(foldername)
+                    # Create the video node
+                    if foldername not in nodes and mediatype not in ("musicvideos", "music"):
+                        self.vnodes.viewNode(sorted_views.index(foldername), foldername, mediatype,
+                            viewtype, folderid)
+                        if viewtype == "mixed": # Change the value
+                            sorted_views[sorted_views.index(foldername)] = "%ss" % foldername
+                        nodes.append(foldername)
+                        totalnodes += 1
+                    # Add view to emby database
+                    emby_db.addView(folderid, foldername, viewtype, tagid)
+
+                else:
+                    self.logMsg(' '.join((
+
+                        "Found viewid: %s" % folderid,
+                        "viewname: %s" % current_viewname,
+                        "viewtype: %s" % current_viewtype,
+                        "tagid: %s" % current_tagid)), 2)
+
+                    # View is still valid
+                    try:
+                        current_views.remove(folderid)
+                    except ValueError:
+                        # View was just created, nothing to remove
+                        pass
+
+                    # View was modified, update with latest info
+                    if current_viewname != foldername:
+                        self.logMsg("viewid: %s new viewname: %s" % (folderid, foldername), 1)
+                        tagid = kodi_db.createTag(foldername)
+
+                        # Update view with new info
+                        emby_db.updateView(foldername, tagid, folderid)
+
+                        if mediatype != "music":
+                            if emby_db.getView_byName(current_viewname) is None:
+                                # The tag could be a combined view. Ensure there's no other tags
+                                # with the same name before deleting playlist.
+                                utils.playlistXSP(
+                                    mediatype, current_viewname, folderid, current_viewtype, True)
+                                # Delete video node
+                                if mediatype != "musicvideos":
+                                    self.vnodes.viewNode(
+                                        indexnumber=None,
+                                        tagname=current_viewname,
+                                        mediatype=mediatype,
+                                        viewtype=current_viewtype,
+                                        viewid=folderid,
+                                        delete=True)
+                            # Added new playlist
+                            if (foldername not in playlists and
+                                    mediatype in ('movies', 'tvshows', 'musicvideos')):
+                                utils.playlistXSP(mediatype, foldername, folderid, viewtype)
+                                playlists.append(foldername)
+                            # Add new video node
+                            if foldername not in nodes and mediatype != "musicvideos":
+                                self.vnodes.viewNode(sorted_views.index(foldername), foldername,
+                                    mediatype, viewtype, folderid)
+                                if viewtype == "mixed": # Change the value
+                                    sorted_views[sorted_views.index(foldername)] = "%ss" % foldername
+                                nodes.append(foldername)
+                                totalnodes += 1
+
+                        # Update items with new tag
+                        items = emby_db.getItem_byView(folderid)
+                        for item in items:
+                            # Remove the "s" from viewtype for tags
+                            kodi_db.updateTag(
+                                current_tagid, tagid, item[0], current_viewtype[:-1])
+                    else:
+                        # Validate the playlist exists or recreate it
+                        if mediatype != "music":
+                            if (foldername not in playlists and
+                                    mediatype in ('movies', 'tvshows', 'musicvideos')):
+                                utils.playlistXSP(mediatype, foldername, folderid, viewtype)
+                                playlists.append(foldername)
+                            # Create the video node if not already exists
+                            if foldername not in nodes and mediatype != "musicvideos":
+                                self.vnodes.viewNode(sorted_views.index(foldername), foldername,
+                                    mediatype, viewtype, folderid)
+                                if viewtype == "mixed": # Change the value
+                                    sorted_views[sorted_views.index(foldername)] = "%ss" % foldername
+                                nodes.append(foldername)
+                                totalnodes += 1
+        else:
+            # Add video nodes listings
+            self.vnodes.singleNode(totalnodes, "Favorite movies", "movies", "favourites")
+            totalnodes += 1
+            self.vnodes.singleNode(totalnodes, "Favorite tvshows", "tvshows", "favourites")
+            totalnodes += 1
+            self.vnodes.singleNode(totalnodes, "channels", "movies", "channels")
+            totalnodes += 1
+            # Save total
+            utils.window('Emby.nodes.total', str(totalnodes))
+
+            # Remove any old referenced views
+            self.logMsg("Removing views: %s" % current_views, 1)
+            for view in current_views:
+                emby_db.removeView(view)
+
+    def movies(self, embycursor, kodicursor, pdialog):
+
+        lang = utils.language
+        # Get movies from emby
+        emby_db = embydb.Embydb_Functions(embycursor)
+        movies = itemtypes.Movies(embycursor, kodicursor)
+
+        views = emby_db.getView_byType('movies')
+        views += emby_db.getView_byType('mixed')
+        self.logMsg("Media folders: %s" % views, 1)
+
+        ##### PROCESS MOVIES #####
+        for view in views:
+
+            if self.shouldStop():
+                return False
+
+            # Get items per view
+            if pdialog:
+                pdialog.update(
+                        heading="Emby for Kodi",
+                        message="%s %s..." % (lang(33017), view['name']))
+
+            # Initial or repair sync
+            all_embymovies = self.emby.getMovies(view['id'], dialog=pdialog)
+            total = all_embymovies['TotalRecordCount']
+            embymovies = all_embymovies['Items']
+
+            if pdialog:
+                pdialog.update(heading="Processing %s / %s items" % (view['name'], total))
+
+            count = 0
+            for embymovie in embymovies:
+                # Process individual movies
+                if self.shouldStop():
+                    return False
+
+                title = embymovie['Name']
+                if pdialog:
+                    percentage = int((float(count) / float(total))*100)
+                    pdialog.update(percentage, message=title)
+                    count += 1
+                movies.add_update(embymovie, view['name'], view['id'])
+        else:
+            self.logMsg("Movies finished.", 2)
+
+
+        ##### PROCESS BOXSETS #####
+        if pdialog:
+            pdialog.update(heading="Emby for Kodi", message=lang(33018))
+
+        boxsets = self.emby.getBoxset(dialog=pdialog)
+        total = boxsets['TotalRecordCount']
+        embyboxsets = boxsets['Items']
+
+        if pdialog:
+            pdialog.update(heading="Processing Boxsets / %s items" % total)
+
+        count = 0
+        for boxset in embyboxsets:
+            # Process individual boxset
+            if self.shouldStop():
+                return False
+
+            title = boxset['Name']
+            if pdialog:
+                percentage = int((float(count) / float(total))*100)
+                pdialog.update(percentage, message=title)
+                count += 1
+            movies.add_updateBoxset(boxset)
+        else:
+            self.logMsg("Boxsets finished.", 2)
+
+        return True
+
+    def musicvideos(self, embycursor, kodicursor, pdialog):
+
+        # Get musicvideos from emby
+        emby_db = embydb.Embydb_Functions(embycursor)
+        mvideos = itemtypes.MusicVideos(embycursor, kodicursor)
+
+        views = emby_db.getView_byType('musicvideos')
+        self.logMsg("Media folders: %s" % views, 1)
+
+        for view in views:
+
+            if self.shouldStop():
+                return False
+
+            # Get items per view
+            viewId = view['id']
+            viewName = view['name']
+
+            if pdialog:
+                pdialog.update(
+                        heading="Emby for Kodi",
+                        message="%s %s..." % (utils.language(33019), viewName))
+
+            # Initial or repair sync
+            all_embymvideos = self.emby.getMusicVideos(viewId, dialog=pdialog)
+            total = all_embymvideos['TotalRecordCount']
+            embymvideos = all_embymvideos['Items']
+
+            if pdialog:
+                pdialog.update(heading="Processing %s / %s items" % (viewName, total))
+
+            count = 0
+            for embymvideo in embymvideos:
+                # Process individual musicvideo
+                if self.shouldStop():
+                    return False
+
+                title = embymvideo['Name']
+                if pdialog:
+                    percentage = int((float(count) / float(total))*100)
+                    pdialog.update(percentage, message=title)
+                    count += 1
+                mvideos.add_update(embymvideo, viewName, viewId)
+        else:
+            self.logMsg("MusicVideos finished.", 2)
+
+        return True
+
+    def tvshows(self, embycursor, kodicursor, pdialog):
+
+        # Get shows from emby
+        emby_db = embydb.Embydb_Functions(embycursor)
+        tvshows = itemtypes.TVShows(embycursor, kodicursor)
+
+        views = emby_db.getView_byType('tvshows')
+        views += emby_db.getView_byType('mixed')
+        self.logMsg("Media folders: %s" % views, 1)
+
+        for view in views:
+
+            if self.shouldStop():
+                return False
+
+            # Get items per view
+            if pdialog:
+                pdialog.update(
+                        heading="Emby for Kodi",
+                        message="%s %s..." % (utils.language(33020), view['name']))
+
+            all_embytvshows = self.emby.getShows(view['id'], dialog=pdialog)
+            total = all_embytvshows['TotalRecordCount']
+            embytvshows = all_embytvshows['Items']
+
+            if pdialog:
+                pdialog.update(heading="Processing %s / %s items" % (view['name'], total))
+
+            count = 0
+            for embytvshow in embytvshows:
+                # Process individual show
+                if self.shouldStop():
+                    return False
+
+                title = embytvshow['Name']
+                if pdialog:
+                    percentage = int((float(count) / float(total))*100)
+                    pdialog.update(percentage, message=title)
+                    count += 1
+                tvshows.add_update(embytvshow, view['name'], view['id'])
+
+                # Process episodes
+                all_episodes = self.emby.getEpisodesbyShow(embytvshow['Id'])
+                for episode in all_episodes['Items']:
+
+                    # Process individual show
+                    if self.shouldStop():
+                        return False
+
+                    episodetitle = episode['Name']
+                    if pdialog:
+                        pdialog.update(percentage, message="%s - %s" % (title, episodetitle))
+                    tvshows.add_updateEpisode(episode)
+        else:
+            self.logMsg("TVShows finished.", 2)
+
+        return True
+
+    def music(self, embycursor, kodicursor, pdialog):
+        # Get music from emby
+        emby_db = embydb.Embydb_Functions(embycursor)
+        music = itemtypes.Music(embycursor, kodicursor)
+
+        process = {
+
+            'artists': [self.emby.getArtists, music.add_updateArtist],
+            'albums': [self.emby.getAlbums, music.add_updateAlbum],
+            'songs': [self.emby.getSongs, music.add_updateSong]
+        }
+        for itemtype in ['artists', 'albums', 'songs']:
+
+            if pdialog:
+                pdialog.update(
+                    heading="Emby for Kodi",
+                    message="%s %s..." % (utils.language(33021), itemtype))
+
+            all_embyitems = process[itemtype][0](dialog=pdialog)
+            total = all_embyitems['TotalRecordCount']
+            embyitems = all_embyitems['Items']
+
+            if pdialog:
+                pdialog.update(heading="Processing %s / %s items" % (itemtype, total))
+
+            count = 0
+            for embyitem in embyitems:
+                # Process individual item
+                if self.shouldStop():
+                    return False
+                if pdialog:
+                    percentage = int((float(count) / float(total))*100)
+                    pdialog.update(percentage, message=embyitem['Name'])
+                    count += 1
+
+                process[itemtype][1](embyitem)
+            else:
+                self.logMsg("%s finished." % itemtype, 2)
+
+        return True
+
+    # Reserved for websocket_client.py and fast start
+    def triage_items(self, process, items):
+
+        processlist = {
+
+            'added': self.addedItems,
+            'update': self.updateItems,
+            'userdata': self.userdataItems,
+            'remove': self.removeItems
+        }
+        if items:
+            if process == "userdata":
+                itemids = []
+                for item in items:
+                    itemids.append(item['ItemId'])
+                items = itemids
+
+            self.logMsg("Queue %s: %s" % (process, items), 1)
+            processlist[process].extend(items)
+
+    def incrementalSync(self):
+
+        embyconn = utils.kodiSQL('emby')
+        embycursor = embyconn.cursor()
+        kodiconn = utils.kodiSQL('video')
+        kodicursor = kodiconn.cursor()
+        emby_db = embydb.Embydb_Functions(embycursor)
+        pDialog = None
+        update_embydb = False
+
+        if self.refresh_views:
+            # Received userconfig update
+            self.refresh_views = False
+            self.maintainViews(embycursor, kodicursor)
+            self.forceLibraryUpdate = True
+            update_embydb = True
+
+        if self.addedItems or self.updateItems or self.userdataItems or self.removeItems:
+            # Only present dialog if we are going to process items
+            pDialog = self.progressDialog('Incremental sync')
+
+
+        process = {
+
+            'added': self.addedItems,
+            'update': self.updateItems,
+            'userdata': self.userdataItems,
+            'remove': self.removeItems
+        }
+        for process_type in ['added', 'update', 'userdata', 'remove']:
+
+            if process[process_type] and utils.window('emby_kodiScan') != "true":
+
+                listItems = list(process[process_type])
+                del process[process_type][:] # Reset class list
+
+                items_process = itemtypes.Items(embycursor, kodicursor)
+                update = False
+
+                # Prepare items according to process process_type
+                if process_type == "added":
+                    items = self.emby.sortby_mediatype(listItems)
+
+                elif process_type in ("userdata", "remove"):
+                    items = emby_db.sortby_mediaType(listItems, unsorted=False)
+
+                else:
+                    items = emby_db.sortby_mediaType(listItems)
+                    if items.get('Unsorted'):
+                        sorted_items = self.emby.sortby_mediatype(items['Unsorted'])
+                        doupdate = items_process.itemsbyId(sorted_items, "added", pDialog)
+                        if doupdate:
+                            embyupdate, kodiupdate_video = doupdate
+                            if embyupdate:
+                                update_embydb = True
+                            if kodiupdate_video:
+                                self.forceLibraryUpdate = True
+                        del items['Unsorted']
+
+                doupdate = items_process.itemsbyId(items, process_type, pDialog)
+                if doupdate:
+                    embyupdate, kodiupdate_video = doupdate
+                    if embyupdate:
+                        update_embydb = True
+                    if kodiupdate_video:
+                        self.forceLibraryUpdate = True
+
+        if update_embydb:
+            update_embydb = False
+            self.logMsg("Updating emby database.", 1)
+            embyconn.commit()
+            self.saveLastSync()
+
+        if self.forceLibraryUpdate:
+            # Force update the Kodi library
+            self.forceLibraryUpdate = False
+            self.dbCommit(kodiconn)
+
+            self.logMsg("Updating video library.", 1)
+            utils.window('emby_kodiScan', value="true")
+            xbmc.executebuiltin('UpdateLibrary(video)')
+
+        if pDialog:
+            pDialog.close()
+
+        kodicursor.close()
+        embycursor.close()
+
+
+    def compareDBVersion(self, current, minimum):
+        # It returns True is database is up to date. False otherwise.
+        self.logMsg("current: %s minimum: %s" % (current, minimum), 1)
+        currMajor, currMinor, currPatch = current.split(".")
+        minMajor, minMinor, minPatch = minimum.split(".")
+
+        if currMajor > minMajor:
+            return True
+        elif currMajor == minMajor and (currMinor > minMinor or
+                                       (currMinor == minMinor and currPatch >= minPatch)):
+            return True
+        else:
+            # Database out of date.
+            return False
+
+    def run(self):
+
+        try:
+            self.run_internal()
+        except Exception as e:
+            utils.window('emby_dbScan', clear=True)
+            xbmcgui.Dialog().ok(
+                        heading="Emby for Kodi",
+                        line1=(
+                            "Library sync thread has exited! "
+                            "You should restart Kodi now. "
+                            "Please report this on the forum."))
+            raise
+
+    def run_internal(self):
+
+        lang = utils.language
+        window = utils.window
+        settings = utils.settings
+        dialog = xbmcgui.Dialog()
+
+        startupComplete = False
+
+        self.logMsg("---===### Starting LibrarySync ###===---", 0)
+
+        while not self.monitor.abortRequested():
+
+            # In the event the server goes offline
+            while self.suspend_thread:
+                # Set in service.py
+                if self.monitor.waitForAbort(5):
+                    # Abort was requested while waiting. We should exit
+                    break
+
+            if (window('emby_dbCheck') != "true" and settings('SyncInstallRunDone') == "true"):
+                # Verify the validity of the database
+                currentVersion = settings('dbCreatedWithVersion')
+                minVersion = window('emby_minDBVersion')
+                uptoDate = self.compareDBVersion(currentVersion, minVersion)
+
+                if not uptoDate:
+                    self.logMsg("Database version out of date: %s minimum version required: %s"
+                        % (currentVersion, minVersion), 0)
+
+                    resp = dialog.yesno("Emby for Kodi", lang(33022))
+                    if not resp:
+                        self.logMsg("Database version is out of date! USER IGNORED!", 0)
+                        dialog.ok("Emby for Kodi", lang(33023))
+                    else:
+                        utils.reset()
+
+                    break
+
+                window('emby_dbCheck', value="true")
+
+
+            if not startupComplete:
+                # Verify the video database can be found
+                videoDb = utils.getKodiVideoDBPath()
+                if not xbmcvfs.exists(videoDb):
+                    # Database does not exists
+                    self.logMsg(
+                        "The current Kodi version is incompatible "
+                        "with the Emby for Kodi add-on. Please visit "
+                        "https://github.com/MediaBrowser/Emby.Kodi/wiki "
+                        "to know which Kodi versions are supported.", 0)
+
+                    dialog.ok(
+                            heading="Emby for Kodi",
+                            line1=lang(33024))
+                    break
+
+                # Run start up sync
+                self.logMsg("Database version: %s" % settings('dbCreatedWithVersion'), 0)
+                self.logMsg("SyncDatabase (started)", 1)
+                startTime = datetime.now()
+                librarySync = self.startSync()
+                elapsedTime = datetime.now() - startTime
+                self.logMsg("SyncDatabase (finished in: %s) %s"
+                    % (str(elapsedTime).split('.')[0], librarySync), 1)
+                # Only try the initial sync once per kodi session regardless
+                # This will prevent an infinite loop in case something goes wrong.
+                startupComplete = True
+
+            # Process updates
+            if window('emby_dbScan') != "true":
+                self.incrementalSync()
+
+            if window('emby_onWake') == "true" and window('emby_online') == "true":
+                # Kodi is waking up
+                # Set in kodimonitor.py
+                window('emby_onWake', clear=True)
+                if window('emby_syncRunning') != "true":
+                    self.logMsg("SyncDatabase onWake (started)", 0)
+                    librarySync = self.startSync()
+                    self.logMsg("SyncDatabase onWake (finished) %s" % librarySync, 0)
+
+            if self.stop_thread:
+                # Set in service.py
+                self.logMsg("Service terminated thread.", 2)
+                break
+
+            if self.monitor.waitForAbort(1):
+                # Abort was requested while waiting. We should exit
+                break
+
+        self.logMsg("###===--- LibrarySync Stopped ---===###", 0)
+
+    def stopThread(self):
+        self.stop_thread = True
+        self.logMsg("Ending thread...", 2)
+
+    def suspendThread(self):
+        self.suspend_thread = True
+        self.logMsg("Pausing thread...", 0)
+
+    def resumeThread(self):
+        self.suspend_thread = False
+        self.logMsg("Resuming thread...", 0)
+
+
+class ManualSync(LibrarySync):
+
+
+    def __init__(self):
+
+        LibrarySync.__init__(self)
+
+    def sync(self, dialog=False):
+
+        return self.fullSync(manualrun=True, forceddialog=dialog)
+
+
+    def movies(self, embycursor, kodicursor, pdialog):
+
+        lang = utils.language
+        # Get movies from emby
+        emby_db = embydb.Embydb_Functions(embycursor)
+        movies = itemtypes.Movies(embycursor, kodicursor)
+
+        views = emby_db.getView_byType('movies')
+        views += emby_db.getView_byType('mixed')
+        self.logMsg("Media folders: %s" % views, 1)
+
+        # Pull the list of movies and boxsets in Kodi
+        try:
+            all_kodimovies = dict(emby_db.getChecksum('Movie'))
+        except ValueError:
+            all_kodimovies = {}
+
+        try:
+            all_kodisets = dict(emby_db.getChecksum('BoxSet'))
+        except ValueError:
+            all_kodisets = {}
+
+        all_embymoviesIds = set()
+        all_embyboxsetsIds = set()
+        updatelist = []
+
+        ##### PROCESS MOVIES #####
+        for view in views:
+
+            if self.shouldStop():
+                return False
+
+            # Get items per view
+            viewId = view['id']
+            viewName = view['name']
+
+            if pdialog:
+                pdialog.update(
+                        heading="Emby for Kodi",
+                        message="%s %s..." % (lang(33026), viewName))
+
+            all_embymovies = self.emby.getMovies(viewId, basic=True, dialog=pdialog)
+            for embymovie in all_embymovies['Items']:
+
+                if self.shouldStop():
+                    return False
+
+                API = api.API(embymovie)
+                itemid = embymovie['Id']
+                all_embymoviesIds.add(itemid)
+
+
+                if all_kodimovies.get(itemid) != API.getChecksum():
+                    # Only update if movie is not in Kodi or checksum is different
+                    updatelist.append(itemid)
+
+            self.logMsg("Movies to update for %s: %s" % (viewName, updatelist), 1)
+            embymovies = self.emby.getFullItems(updatelist)
+            total = len(updatelist)
+            del updatelist[:]
+
+            if pdialog:
+                pdialog.update(heading="Processing %s / %s items" % (viewName, total))
+
+            count = 0
+            for embymovie in embymovies:
+                # Process individual movies
+                if self.shouldStop():
+                    return False
+
+                if pdialog:
+                    percentage = int((float(count) / float(total))*100)
+                    pdialog.update(percentage, message=embymovie['Name'])
+                    count += 1
+                movies.add_update(embymovie, viewName, viewId)
+
+        ##### PROCESS BOXSETS #####
+
+        boxsets = self.emby.getBoxset(dialog=pdialog)
+        embyboxsets = []
+
+        if pdialog:
+            pdialog.update(heading="Emby for Kodi", message=lang(33027))
+
+        for boxset in boxsets['Items']:
+
+            if self.shouldStop():
+                return False
+
+            # Boxset has no real userdata, so using etag to compare
+            itemid = boxset['Id']
+            all_embyboxsetsIds.add(itemid)
+
+            if all_kodisets.get(itemid) != boxset['Etag']:
+                # Only update if boxset is not in Kodi or boxset['Etag'] is different
+                updatelist.append(itemid)
+                embyboxsets.append(boxset)
+
+        self.logMsg("Boxsets to update: %s" % updatelist, 1)
+        total = len(updatelist)
+
+        if pdialog:
+            pdialog.update(heading="Processing Boxsets / %s items" % total)
+
+        count = 0
+        for boxset in embyboxsets:
+            # Process individual boxset
+            if self.shouldStop():
+                return False
+
+            if pdialog:
+                percentage = int((float(count) / float(total))*100)
+                pdialog.update(percentage, message=boxset['Name'])
+                count += 1
+            movies.add_updateBoxset(boxset)
+
+        ##### PROCESS DELETES #####
+
+        for kodimovie in all_kodimovies:
+            if kodimovie not in all_embymoviesIds:
+                movies.remove(kodimovie)
+        else:
+            self.logMsg("Movies compare finished.", 1)
+
+        for boxset in all_kodisets:
+            if boxset not in all_embyboxsetsIds:
+                movies.remove(boxset)
+        else:
+            self.logMsg("Boxsets compare finished.", 1)
+
+        return True
+
+    def musicvideos(self, embycursor, kodicursor, pdialog):
+
+        # Get musicvideos from emby
+        emby_db = embydb.Embydb_Functions(embycursor)
+        mvideos = itemtypes.MusicVideos(embycursor, kodicursor)
+
+        views = emby_db.getView_byType('musicvideos')
+        self.logMsg("Media folders: %s" % views, 1)
+
+        # Pull the list of musicvideos in Kodi
+        try:
+            all_kodimvideos = dict(emby_db.getChecksum('MusicVideo'))
+        except ValueError:
+            all_kodimvideos = {}
+
+        all_embymvideosIds = set()
+        updatelist = []
+
+        for view in views:
+
+            if self.shouldStop():
+                return False
+
+            # Get items per view
+            viewId = view['id']
+            viewName = view['name']
+
+            if pdialog:
+                pdialog.update(
+                        heading="Emby for Kodi",
+                        message="%s %s..." % (utils.language(33028), viewName))
+
+            all_embymvideos = self.emby.getMusicVideos(viewId, basic=True, dialog=pdialog)
+            for embymvideo in all_embymvideos['Items']:
+
+                if self.shouldStop():
+                    return False
+
+                API = api.API(embymvideo)
+                itemid = embymvideo['Id']
+                all_embymvideosIds.add(itemid)
+
+
+                if all_kodimvideos.get(itemid) != API.getChecksum():
+                    # Only update if musicvideo is not in Kodi or checksum is different
+                    updatelist.append(itemid)
+
+            self.logMsg("MusicVideos to update for %s: %s" % (viewName, updatelist), 1)
+            embymvideos = self.emby.getFullItems(updatelist)
+            total = len(updatelist)
+            del updatelist[:]
+
+
+            if pdialog:
+                pdialog.update(heading="Processing %s / %s items" % (viewName, total))
+
+            count = 0
+            for embymvideo in embymvideos:
+                # Process individual musicvideo
+                if self.shouldStop():
+                    return False
+
+                if pdialog:
+                    percentage = int((float(count) / float(total))*100)
+                    pdialog.update(percentage, message=embymvideo['Name'])
+                    count += 1
+                mvideos.add_update(embymvideo, viewName, viewId)
+
+        ##### PROCESS DELETES #####
+
+        for kodimvideo in all_kodimvideos:
+            if kodimvideo not in all_embymvideosIds:
+                mvideos.remove(kodimvideo)
+        else:
+            self.logMsg("MusicVideos compare finished.", 1)
+
+        return True
+
+    def tvshows(self, embycursor, kodicursor, pdialog):
+
+        lang = utils.language
+        # Get shows from emby
+        emby_db = embydb.Embydb_Functions(embycursor)
+        tvshows = itemtypes.TVShows(embycursor, kodicursor)
+
+        views = emby_db.getView_byType('tvshows')
+        views += emby_db.getView_byType('mixed')
+        self.logMsg("Media folders: %s" % views, 1)
+
+        # Pull the list of tvshows and episodes in Kodi
+        try:
+            all_koditvshows = dict(emby_db.getChecksum('Series'))
+        except ValueError:
+            all_koditvshows = {}
+
+        try:
+            all_kodiepisodes = dict(emby_db.getChecksum('Episode'))
+        except ValueError:
+            all_kodiepisodes = {}
+
+        all_embytvshowsIds = set()
+        all_embyepisodesIds = set()
+        updatelist = []
+
+
+        for view in views:
+
+            if self.shouldStop():
+                return False
+
+            # Get items per view
+            viewId = view['id']
+            viewName = view['name']
+
+            if pdialog:
+                pdialog.update(
+                        heading="Emby for Kodi",
+                        message="%s %s..." % (lang(33029), viewName))
+
+            all_embytvshows = self.emby.getShows(viewId, basic=True, dialog=pdialog)
+            for embytvshow in all_embytvshows['Items']:
+
+                if self.shouldStop():
+                    return False
+
+                API = api.API(embytvshow)
+                itemid = embytvshow['Id']
+                all_embytvshowsIds.add(itemid)
+
+
+                if all_koditvshows.get(itemid) != API.getChecksum():
+                    # Only update if movie is not in Kodi or checksum is different
+                    updatelist.append(itemid)
+
+            self.logMsg("TVShows to update for %s: %s" % (viewName, updatelist), 1)
+            embytvshows = self.emby.getFullItems(updatelist)
+            total = len(updatelist)
+            del updatelist[:]
+
+
+            if pdialog:
+                pdialog.update(heading="Processing %s / %s items" % (viewName, total))
+
+            count = 0
+            for embytvshow in embytvshows:
+                # Process individual show
+                if self.shouldStop():
+                    return False
+
+                itemid = embytvshow['Id']
+                title = embytvshow['Name']
+                if pdialog:
+                    percentage = int((float(count) / float(total))*100)
+                    pdialog.update(percentage, message=title)
+                    count += 1
+                tvshows.add_update(embytvshow, viewName, viewId)
+
+            else:
+                # Get all episodes in view
+                if pdialog:
+                    pdialog.update(
+                            heading="Emby for Kodi",
+                            message="%s %s..." % (lang(33030), viewName))
+
+                all_embyepisodes = self.emby.getEpisodes(viewId, basic=True, dialog=pdialog)
+                for embyepisode in all_embyepisodes['Items']:
+
+                    if self.shouldStop():
+                        return False
+
+                    API = api.API(embyepisode)
+                    itemid = embyepisode['Id']
+                    all_embyepisodesIds.add(itemid)
+
+                    if all_kodiepisodes.get(itemid) != API.getChecksum():
+                        # Only update if movie is not in Kodi or checksum is different
+                        updatelist.append(itemid)
+
+                self.logMsg("Episodes to update for %s: %s" % (viewName, updatelist), 1)
+                embyepisodes = self.emby.getFullItems(updatelist)
+                total = len(updatelist)
+                del updatelist[:]
+
+                count = 0
+                for episode in embyepisodes:
+
+                    # Process individual episode
+                    if self.shouldStop():
+                        return False
+
+                    if pdialog:
+                        percentage = int((float(count) / float(total))*100)
+                        pdialog.update(percentage, message="%s - %s" % (episode['SeriesName'], episode['Name']))
+                        count += 1
+                    tvshows.add_updateEpisode(episode)
+
+        ##### PROCESS DELETES #####
+
+        for koditvshow in all_koditvshows:
+            if koditvshow not in all_embytvshowsIds:
+                tvshows.remove(koditvshow)
+        else:
+            self.logMsg("TVShows compare finished.", 1)
+
+        for kodiepisode in all_kodiepisodes:
+            if kodiepisode not in all_embyepisodesIds:
+                tvshows.remove(kodiepisode)
+        else:
+            self.logMsg("Episodes compare finished.", 1)
+
+        return True
+
+    def music(self, embycursor, kodicursor, pdialog):
+
+        # Get music from emby
+        emby_db = embydb.Embydb_Functions(embycursor)
+        music = itemtypes.Music(embycursor, kodicursor)
+
+        # Pull the list of artists, albums, songs
+        try:
+            all_kodiartists = dict(emby_db.getChecksum('MusicArtist'))
+        except ValueError:
+            all_kodiartists = {}
+
+        try:
+            all_kodialbums = dict(emby_db.getChecksum('MusicAlbum'))
+        except ValueError:
+            all_kodialbums = {}
+
+        try:
+            all_kodisongs = dict(emby_db.getChecksum('Audio'))
+        except ValueError:
+            all_kodisongs = {}
+
+        all_embyartistsIds = set()
+        all_embyalbumsIds = set()
+        all_embysongsIds = set()
+        updatelist = []
+
+        process = {
+
+            'artists': [self.emby.getArtists, music.add_updateArtist],
+            'albums': [self.emby.getAlbums, music.add_updateAlbum],
+            'songs': [self.emby.getSongs, music.add_updateSong]
+        }
+        for data_type in ['artists', 'albums', 'songs']:
+            if pdialog:
+                pdialog.update(
+                        heading="Emby for Kodi",
+                        message="%s %s..." % (utils.language(33031), data_type))
+            if data_type != "artists":
+                all_embyitems = process[data_type][0](basic=True, dialog=pdialog)
+            else:
+                all_embyitems = process[data_type][0](dialog=pdialog)
+            for embyitem in all_embyitems['Items']:
+                if self.shouldStop():
+                    return False
+                API = api.API(embyitem)
+                itemid = embyitem['Id']
+                if data_type == "artists":
+                    all_embyartistsIds.add(itemid)
+                    if all_kodiartists.get(itemid) != API.getChecksum():
+                        # Only update if artist is not in Kodi or checksum is different
+                        updatelist.append(itemid)
+                elif data_type == "albums":
+                    all_embyalbumsIds.add(itemid)
+                    if all_kodialbums.get(itemid) != API.getChecksum():
+                        # Only update if album is not in Kodi or checksum is different
+                        updatelist.append(itemid)
+                else:
+                    all_embysongsIds.add(itemid)
+                    if all_kodisongs.get(itemid) != API.getChecksum():
+                        # Only update if songs is not in Kodi or checksum is different
+                        updatelist.append(itemid)
+            self.logMsg("%s to update: %s" % (data_type, updatelist), 1)
+            embyitems = self.emby.getFullItems(updatelist)
+            total = len(updatelist)
+            del updatelist[:]
+            if pdialog:
+                pdialog.update(heading="Processing %s / %s items" % (data_type, total))
+            count = 0
+            for embyitem in embyitems:
+                # Process individual item
+                if self.shouldStop():
+                    return False
+                if pdialog:
+                    percentage = int((float(count) / float(total))*100)
+                    pdialog.update(percentage, message=embyitem['Name'])
+                    count += 1
+                process[data_type][1](embyitem)
+        ##### PROCESS DELETES #####
+        for kodiartist in all_kodiartists:
+            if kodiartist not in all_embyartistsIds and all_kodiartists[kodiartist] is not None:
+                music.remove(kodiartist)
+        else:
+            self.logMsg("Artist compare finished.", 1)
+        for kodialbum in all_kodialbums:
+            if kodialbum not in all_embyalbumsIds:
+                music.remove(kodialbum)
+        else:
+            self.logMsg("Albums compare finished.", 1)
+        for kodisong in all_kodisongs:
+            if kodisong not in all_embysongsIds:
+                music.remove(kodisong)
+        else:
+            self.logMsg("Songs compare finished.", 1)
+        return True
diff --git a/resources/lib/musicutils.py b/resources/lib/musicutils.py
index 97db089d..b058c5c5 100644
--- a/resources/lib/musicutils.py
+++ b/resources/lib/musicutils.py
@@ -1,286 +1,287 @@
-# -*- coding: utf-8 -*-
-
-#################################################################################################
-
-import os
-
-import xbmc
-import xbmcaddon
-import xbmcvfs
-
-from mutagen.flac import FLAC, Picture
-from mutagen.id3 import ID3
-from mutagen import id3
-import base64
-
-import read_embyserver as embyserver
-import utils
-
-#################################################################################################
-
-# Helper for the music library, intended to fix missing song ID3 tags on Emby
-
-def logMsg(msg, lvl=1):
-    utils.logMsg("%s %s" % ("Emby", "musictools"), msg, lvl)
-
-def getRealFileName(filename, isTemp=False):
-    #get the filename path accessible by python if possible...
-    
-    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 = filename.encode("utf-8")
-    
-    # determine if our python module is able to access the file directly...
-    if os.path.exists(checkfile):
-        filename = filename
-    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...
-        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 (isTemp,filename)
-
-def getEmbyRatingFromKodiRating(rating):
-    # Translation needed between Kodi/ID3 rating and emby likes/favourites:
-    # 3+ rating in ID3 = emby like
-    # 5+ rating in ID3 = emby favourite
-    # rating 0 = emby dislike
-    # rating 1-2 = emby no likes or dislikes (returns 1 in results)
-    favourite = False
-    deletelike = False
-    like = False
-    if (rating >= 3): like = True
-    if (rating == 0): like = False
-    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):
-    
-    emby = embyserver.Read_EmbyServer()
-
-    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)
-        utils.window("ignore-update-%s" %embyid, "true") #set temp windows prop to ignore the update from webclient update
-        emby.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
-    comment = ""
-    hasEmbeddedCover = False
-    
-    isTemp,filename = getRealFileName(file)
-    logMsg( "getting song ID3 tags for " + filename)
-    
-    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"):
-                if audio.get("POPM:Windows Media Player 9 Series").rating:
-                    rating = float(audio.get("POPM:Windows Media Player 9 Series").rating)
-                    #POPM rating is 0-255 and needs to be converted to 0-5 range
-                    if rating > 5: rating = (rating / 255) * 5
-        else:
-            logMsg( "Not supported fileformat or unable to access file: %s" %(filename))
-        
-        #the rating must be a round value
-        rating = int(round(rating,0))
-    
-    except Exception as e:
-        #file in use ?
-        utils.logMsg("Exception in getSongTags", str(e),0)
-        rating = None
-    
-    #remove tempfile if needed....
-    if isTemp: xbmcvfs.delete(filename)
-        
-    return (rating, comment, hasEmbeddedCover)
-
-def updateRatingToFile(rating, file):
-    #update the rating from Emby to the file
-    
-    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
-    
-    try:
-        if tempfile.lower().endswith(".flac"):
-            audio = FLAC(tempfile)
-            calcrating = int(round((float(rating) / 5) * 100, 0))
-            audio["rating"] = str(calcrating)
-            audio.save()
-        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" %(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 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:
-        #file in use ?
-        logMsg("Exception in updateRatingToFile %s" %e,0)
-        
-    
+# -*- coding: utf-8 -*-
+
+#################################################################################################
+
+import os
+
+import xbmc
+import xbmcaddon
+import xbmcvfs
+
+from mutagen.flac import FLAC, Picture
+from mutagen.id3 import ID3
+from mutagen import id3
+import base64
+
+import read_embyserver as embyserver
+import utils
+
+#################################################################################################
+
+# Helper for the music library, intended to fix missing song ID3 tags on Emby
+
+def logMsg(msg, lvl=1):
+    utils.logMsg("%s %s" % ("Emby", "musictools"), msg, lvl)
+
+def getRealFileName(filename, isTemp=False):
+    #get the filename path accessible by python if possible...
+    
+    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 = filename.encode("utf-8")
+    
+    # determine if our python module is able to access the file directly...
+    if os.path.exists(checkfile):
+        filename = filename
+    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...
+        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 (isTemp,filename)
+
+def getEmbyRatingFromKodiRating(rating):
+    # Translation needed between Kodi/ID3 rating and emby likes/favourites:
+    # 3+ rating in ID3 = emby like
+    # 5+ rating in ID3 = emby favourite
+    # rating 0 = emby dislike
+    # rating 1-2 = emby no likes or dislikes (returns 1 in results)
+    favourite = False
+    deletelike = False
+    like = False
+    if (rating >= 3): like = True
+    if (rating == 0): like = False
+    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):
+    
+    emby = embyserver.Read_EmbyServer()
+
+    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)
+        utils.window("ignore-update-%s" %embyid, "true") #set temp windows prop to ignore the update from webclient update
+        emby.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
+    comment = ""
+    hasEmbeddedCover = False
+    
+    isTemp,filename = getRealFileName(file)
+    logMsg( "getting song ID3 tags for " + filename)
+    
+    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
+                    break
+            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"):
+                if audio.get("POPM:Windows Media Player 9 Series").rating:
+                    rating = float(audio.get("POPM:Windows Media Player 9 Series").rating)
+                    #POPM rating is 0-255 and needs to be converted to 0-5 range
+                    if rating > 5: rating = (rating / 255) * 5
+        else:
+            logMsg( "Not supported fileformat or unable to access file: %s" %(filename))
+        
+        #the rating must be a round value
+        rating = int(round(rating,0))
+    
+    except Exception as e:
+        #file in use ?
+        utils.logMsg("Exception in getSongTags", str(e),0)
+        rating = None
+    
+    #remove tempfile if needed....
+    if isTemp: xbmcvfs.delete(filename)
+        
+    return (rating, comment, hasEmbeddedCover)
+
+def updateRatingToFile(rating, file):
+    #update the rating from Emby to the file
+    
+    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
+    
+    try:
+        if tempfile.lower().endswith(".flac"):
+            audio = FLAC(tempfile)
+            calcrating = int(round((float(rating) / 5) * 100, 0))
+            audio["rating"] = str(calcrating)
+            audio.save()
+        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" %(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 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:
+        #file in use ?
+        logMsg("Exception in updateRatingToFile %s" %e,0)
+        
+    
     
\ No newline at end of file
diff --git a/resources/lib/playbackutils.py b/resources/lib/playbackutils.py
index a0c3743c..e77777a3 100644
--- a/resources/lib/playbackutils.py
+++ b/resources/lib/playbackutils.py
@@ -1,358 +1,346 @@
-# -*- coding: utf-8 -*-
-
-#################################################################################################
-
-import json
-import sys
-
-import xbmc
-import xbmcgui
-import xbmcplugin
-
-import api
-import artwork
-import clientinfo
-import downloadutils
-import playutils as putils
-import playlist
-import read_embyserver as embyserver
-import utils
-
-#################################################################################################
-
-
-class PlaybackUtils():
-    
-    
-    def __init__(self, item):
-
-        self.item = item
-        self.API = api.API(self.item)
-
-        self.clientInfo = clientinfo.ClientInfo()
-        self.addonName = self.clientInfo.getAddonName()
-        self.doUtils = downloadutils.DownloadUtils().downloadUrl
-
-        self.userid = utils.window('emby_currUser')
-        self.server = utils.window('emby_server%s' % self.userid)
-
-        self.artwork = artwork.Artwork()
-        self.emby = embyserver.Read_EmbyServer()
-        self.pl = playlist.Playlist()
-
-    def logMsg(self, msg, lvl=1):
-
-        self.className = self.__class__.__name__
-        utils.logMsg("%s %s" % (self.addonName, self.className), msg, lvl)
-
-
-    def play(self, itemid, dbid=None):
-
-        log = self.logMsg
-        window = utils.window
-        settings = utils.settings
-
-        doUtils = self.doUtils
-        item = self.item
-        API = self.API
-        listitem = xbmcgui.ListItem()
-        playutils = putils.PlayUtils(item)
-
-        log("Play called.", 1)
-        playurl = playutils.getPlayUrl()
-        if not playurl:
-            return xbmcplugin.setResolvedUrl(int(sys.argv[1]), False, listitem)
-
-        if dbid is None:
-            # Item is not in Kodi database
-            listitem.setPath(playurl)
-            self.setProperties(playurl, listitem)
-            return xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, listitem)
-
-        ############### ORGANIZE CURRENT PLAYLIST ################
-        
-        homeScreen = xbmc.getCondVisibility('Window.IsActive(home)')
-        playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
-        startPos = max(playlist.getposition(), 0) # Can return -1
-        sizePlaylist = playlist.size()
-        currentPosition = startPos
-
-        propertiesPlayback = window('emby_playbackProps') == "true"
-        introsPlaylist = False
-        dummyPlaylist = False
-
-        log("Playlist start position: %s" % startPos, 2)
-        log("Playlist plugin position: %s" % currentPosition, 2)
-        log("Playlist size: %s" % sizePlaylist, 2)
-
-        ############### RESUME POINT ################
-        
-        userdata = API.getUserData()
-        seektime = API.adjustResume(userdata['Resume'])
-
-        # We need to ensure we add the intro and additional parts only once.
-        # Otherwise we get a loop.
-        if not propertiesPlayback:
-
-            window('emby_playbackProps', value="true")
-            log("Setting up properties in playlist.", 1)
-
-            if (not homeScreen and not seektime and 
-                    window('emby_customPlaylist') != "true"):
-                
-                log("Adding dummy file to playlist.", 2)
-                dummyPlaylist = True
-                playlist.add(playurl, listitem, index=startPos)
-                # Remove the original item from playlist 
-                self.pl.removefromPlaylist(startPos+1)
-                # Readd the original item to playlist - via jsonrpc so we have full metadata
-                self.pl.insertintoPlaylist(currentPosition+1, dbid, item['Type'].lower())
-                currentPosition += 1
-            
-            ############### -- CHECK FOR INTROS ################
-
-            if settings('enableCinema') == "true" and not seektime:
-                # if we have any play them when the movie/show is not being resumed
-                url = "{server}/emby/Users/{UserId}/Items/%s/Intros?format=json" % itemid    
-                intros = doUtils(url)
-
-                if intros['TotalRecordCount'] != 0:
-                    getTrailers = True
-
-                    if settings('askCinema') == "true":
-                        resp = xbmcgui.Dialog().yesno("Emby for Kodi", utils.language(33016))
-                        if not resp:
-                            # User selected to not play trailers
-                            getTrailers = False
-                            log("Skip trailers.", 1)
-                    
-                    if getTrailers:
-                        for intro in intros['Items']:
-                            # The server randomly returns intros, process them.
-                            introListItem = xbmcgui.ListItem()
-                            introPlayurl = putils.PlayUtils(intro).getPlayUrl()
-                            log("Adding Intro: %s" % introPlayurl, 1)
-
-                            # Set listitem and properties for intros
-                            pbutils = PlaybackUtils(intro)
-                            pbutils.setProperties(introPlayurl, introListItem)
-
-                            self.pl.insertintoPlaylist(currentPosition, url=introPlayurl)
-                            introsPlaylist = True
-                            currentPosition += 1
-
-
-            ############### -- ADD MAIN ITEM ONLY FOR HOMESCREEN ###############
-
-            if homeScreen and not seektime and not sizePlaylist:
-                # Extend our current playlist with the actual item to play
-                # only if there's no playlist first
-                log("Adding main item to playlist.", 1)
-                self.pl.addtoPlaylist(dbid, item['Type'].lower())
-
-            # Ensure that additional parts are played after the main item
-            currentPosition += 1
-
-            ############### -- CHECK FOR ADDITIONAL PARTS ################
-            
-            if item.get('PartCount'):
-                # Only add to the playlist after intros have played
-                partcount = item['PartCount']
-                url = "{server}/emby/Videos/%s/AdditionalParts?format=json" % itemid
-                parts = doUtils(url)
-                for part in parts['Items']:
-
-                    additionalListItem = xbmcgui.ListItem()
-                    additionalPlayurl = putils.PlayUtils(part).getPlayUrl()
-                    log("Adding additional part: %s" % partcount, 1)
-
-                    # Set listitem and properties for each additional parts
-                    pbutils = PlaybackUtils(part)
-                    pbutils.setProperties(additionalPlayurl, additionalListItem)
-                    pbutils.setArtwork(additionalListItem)
-
-                    playlist.add(additionalPlayurl, additionalListItem, index=currentPosition)
-                    self.pl.verifyPlaylist()
-                    currentPosition += 1
-
-            if dummyPlaylist:
-                # Added a dummy file to the playlist,
-                # because the first item is going to fail automatically.
-                log("Processed as a playlist. First item is skipped.", 1)
-                return xbmcplugin.setResolvedUrl(int(sys.argv[1]), False, listitem)
-                
-
-        # We just skipped adding properties. Reset flag for next time.
-        elif propertiesPlayback:
-            log("Resetting properties playback flag.", 2)
-            window('emby_playbackProps', clear=True)
-
-        #self.pl.verifyPlaylist()
-        ########## SETUP MAIN ITEM ##########
-
-        # For transcoding only, ask for audio/subs pref
-        if window('emby_%s.playmethod' % playurl) == "Transcode":
-            playurl = playutils.audioSubsPref(playurl, listitem)
-            window('emby_%s.playmethod' % playurl, value="Transcode")
-
-        listitem.setPath(playurl)
-        self.setProperties(playurl, listitem)
-
-        ############### PLAYBACK ################
-
-        if homeScreen and seektime and window('emby_customPlaylist') != "true":
-            log("Play as a widget item.", 1)
-            self.setListItem(listitem)
-            xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, listitem)
-
-        elif ((introsPlaylist and window('emby_customPlaylist') == "true") or
-                (homeScreen and not sizePlaylist)):
-            # Playlist was created just now, play it.
-            log("Play playlist.", 1)
-            xbmc.Player().play(playlist, startpos=startPos)
-
-        else:
-            log("Play as a regular item.", 1)
-            xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, listitem)
-
-    def setProperties(self, playurl, listitem):
-
-        window = utils.window
-        # Set all properties necessary for plugin path playback
-        item = self.item
-        itemid = item['Id']
-        itemtype = item['Type']
-
-        embyitem = "emby_%s" % playurl
-        window('%s.runtime' % embyitem, value=str(item.get('RunTimeTicks')))
-        window('%s.type' % embyitem, value=itemtype)
-        window('%s.itemid' % embyitem, value=itemid)
-
-        if itemtype == "Episode":
-            window('%s.refreshid' % embyitem, value=item.get('SeriesId'))
-        else:
-            window('%s.refreshid' % embyitem, value=itemid)
-
-        # Append external subtitles to stream
-        playmethod = utils.window('%s.playmethod' % embyitem)
-        # Only for direct stream
-        if playmethod in ("DirectStream"):
-            # Direct play automatically appends external
-            subtitles = self.externalSubs(playurl)
-            listitem.setSubtitles(subtitles)
-
-        self.setArtwork(listitem)
-
-    def externalSubs(self, playurl):
-
-        externalsubs = []
-        mapping = {}
-
-        item = self.item
-        itemid = item['Id']
-        try:
-            mediastreams = item['MediaSources'][0]['MediaStreams']
-        except (TypeError, KeyError, IndexError):
-            return
-
-        kodiindex = 0
-        for stream in mediastreams:
-
-            index = stream['Index']
-            # Since Emby returns all possible tracks together, have to pull only external subtitles.
-            # IsTextSubtitleStream if true, is available to download from emby.
-            if (stream['Type'] == "Subtitle" and 
-                    stream['IsExternal'] and stream['IsTextSubtitleStream']):
-
-                # Direct stream
-                url = ("%s/Videos/%s/%s/Subtitles/%s/Stream.srt"
-                        % (self.server, itemid, itemid, index))
-                
-                # map external subtitles for mapping
-                mapping[kodiindex] = index
-                externalsubs.append(url)
-                kodiindex += 1
-        
-        mapping = json.dumps(mapping)
-        utils.window('emby_%s.indexMapping' % playurl, value=mapping)
-
-        return externalsubs
-
-    def setArtwork(self, listItem):
-        # Set up item and item info
-        item = self.item
-        artwork = self.artwork
-
-        allartwork = artwork.getAllArtwork(item, parentInfo=True)
-        # Set artwork for listitem
-        arttypes = {
-
-            'poster': "Primary",
-            'tvshow.poster': "Primary",
-            'clearart': "Art",
-            'tvshow.clearart': "Art",
-            'clearlogo': "Logo",
-            'tvshow.clearlogo': "Logo",
-            'discart': "Disc",
-            'fanart_image': "Backdrop",
-            'landscape': "Thumb"
-        }
-        for arttype in arttypes:
-
-            art = arttypes[arttype]
-            if art == "Backdrop":
-                try: # Backdrop is a list, grab the first backdrop
-                    self.setArtProp(listItem, arttype, allartwork[art][0])
-                except: pass
-            else:
-                self.setArtProp(listItem, arttype, allartwork[art])
-
-    def setArtProp(self, listItem, arttype, path):
-        
-        if arttype in (
-                'thumb', 'fanart_image', 'small_poster', 'tiny_poster',
-                'medium_landscape', 'medium_poster', 'small_fanartimage',
-                'medium_fanartimage', 'fanart_noindicators'):
-            
-            listItem.setProperty(arttype, path)
-        else:
-            listItem.setArt({arttype: path})
-
-    def setListItem(self, listItem):
-
-        item = self.item
-        itemtype = item['Type']
-        API = self.API
-        people = API.getPeople()
-        studios = API.getStudios()
-
-        metadata = {
-            
-            'title': item.get('Name', "Missing name"),
-            'year': item.get('ProductionYear'),
-            'plot': API.getOverview(),
-            'director': people.get('Director'),
-            'writer': people.get('Writer'),
-            'mpaa': API.getMpaa(),
-            'genre': " / ".join(item['Genres']),
-            'studio': " / ".join(studios),
-            'aired': API.getPremiereDate(),
-            'rating': item.get('CommunityRating'),
-            'votes': item.get('VoteCount')
-        }
-
-        if "Episode" in itemtype:
-            # Only for tv shows
-            thumbId = item.get('SeriesId')
-            season = item.get('ParentIndexNumber', -1)
-            episode = item.get('IndexNumber', -1)
-            show = item.get('SeriesName', "")
-
-            metadata['TVShowTitle'] = show
-            metadata['season'] = season
-            metadata['episode'] = episode
-
-        listItem.setProperty('IsPlayable', 'true')
-        listItem.setProperty('IsFolder', 'false')
-        listItem.setLabel(metadata['title'])
+# -*- coding: utf-8 -*-
+
+#################################################################################################
+
+import json
+import sys
+
+import xbmc
+import xbmcgui
+import xbmcplugin
+
+import api
+import artwork
+import clientinfo
+import downloadutils
+import playutils as putils
+import playlist
+import read_embyserver as embyserver
+import utils
+
+#################################################################################################
+
+
+class PlaybackUtils():
+    
+    
+    def __init__(self, item):
+
+        self.item = item
+        self.API = api.API(self.item)
+
+        self.clientInfo = clientinfo.ClientInfo()
+        self.addonName = self.clientInfo.getAddonName()
+        self.doUtils = downloadutils.DownloadUtils().downloadUrl
+
+        self.userid = utils.window('emby_currUser')
+        self.server = utils.window('emby_server%s' % self.userid)
+
+        self.artwork = artwork.Artwork()
+        self.emby = embyserver.Read_EmbyServer()
+        self.pl = playlist.Playlist()
+
+    def logMsg(self, msg, lvl=1):
+
+        self.className = self.__class__.__name__
+        utils.logMsg("%s %s" % (self.addonName, self.className), msg, lvl)
+
+
+    def play(self, itemid, dbid=None):
+
+        window = utils.window
+        settings = utils.settings
+
+        listitem = xbmcgui.ListItem()
+        playutils = putils.PlayUtils(self.item)
+
+        self.logMsg("Play called.", 1)
+        playurl = playutils.getPlayUrl()
+        if not playurl:
+            return xbmcplugin.setResolvedUrl(int(sys.argv[1]), False, listitem)
+
+        if dbid is None:
+            # Item is not in Kodi database
+            listitem.setPath(playurl)
+            self.setProperties(playurl, listitem)
+            return xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, listitem)
+
+        ############### ORGANIZE CURRENT PLAYLIST ################
+        
+        homeScreen = xbmc.getCondVisibility('Window.IsActive(home)')
+        playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
+        startPos = max(playlist.getposition(), 0) # Can return -1
+        sizePlaylist = playlist.size()
+        currentPosition = startPos
+
+        propertiesPlayback = window('emby_playbackProps') == "true"
+        introsPlaylist = False
+        dummyPlaylist = False
+
+        self.logMsg("Playlist start position: %s" % startPos, 2)
+        self.logMsg("Playlist plugin position: %s" % currentPosition, 2)
+        self.logMsg("Playlist size: %s" % sizePlaylist, 2)
+
+        ############### RESUME POINT ################
+        
+        userdata = self.API.getUserData()
+        seektime = self.API.adjustResume(userdata['Resume'])
+
+        # We need to ensure we add the intro and additional parts only once.
+        # Otherwise we get a loop.
+        if not propertiesPlayback:
+
+            window('emby_playbackProps', value="true")
+            self.logMsg("Setting up properties in playlist.", 1)
+
+            if (not homeScreen and not seektime and 
+                    window('emby_customPlaylist') != "true"):
+                
+                self.logMsg("Adding dummy file to playlist.", 2)
+                dummyPlaylist = True
+                playlist.add(playurl, listitem, index=startPos)
+                # Remove the original item from playlist 
+                self.pl.removefromPlaylist(startPos+1)
+                # Readd the original item to playlist - via jsonrpc so we have full metadata
+                self.pl.insertintoPlaylist(currentPosition+1, dbid, self.item['Type'].lower())
+                currentPosition += 1
+            
+            ############### -- CHECK FOR INTROS ################
+
+            if settings('enableCinema') == "true" and not seektime:
+                # if we have any play them when the movie/show is not being resumed
+                url = "{server}/emby/Users/{UserId}/Items/%s/Intros?format=json" % itemid    
+                intros = self.doUtils(url)
+
+                if intros['TotalRecordCount'] != 0:
+                    getTrailers = True
+
+                    if settings('askCinema') == "true":
+                        resp = xbmcgui.Dialog().yesno("Emby for Kodi", utils.language(33016))
+                        if not resp:
+                            # User selected to not play trailers
+                            getTrailers = False
+                            self.logMsg("Skip trailers.", 1)
+                    
+                    if getTrailers:
+                        for intro in intros['Items']:
+                            # The server randomly returns intros, process them.
+                            introListItem = xbmcgui.ListItem()
+                            introPlayurl = putils.PlayUtils(intro).getPlayUrl()
+                            self.logMsg("Adding Intro: %s" % introPlayurl, 1)
+
+                            # Set listitem and properties for intros
+                            pbutils = PlaybackUtils(intro)
+                            pbutils.setProperties(introPlayurl, introListItem)
+
+                            self.pl.insertintoPlaylist(currentPosition, url=introPlayurl)
+                            introsPlaylist = True
+                            currentPosition += 1
+
+
+            ############### -- ADD MAIN ITEM ONLY FOR HOMESCREEN ###############
+
+            if homeScreen and not seektime and not sizePlaylist:
+                # Extend our current playlist with the actual item to play
+                # only if there's no playlist first
+                self.logMsg("Adding main item to playlist.", 1)
+                self.pl.addtoPlaylist(dbid, self.item['Type'].lower())
+
+            # Ensure that additional parts are played after the main item
+            currentPosition += 1
+
+            ############### -- CHECK FOR ADDITIONAL PARTS ################
+            
+            if self.item.get('PartCount'):
+                # Only add to the playlist after intros have played
+                partcount = self.item['PartCount']
+                url = "{server}/emby/Videos/%s/AdditionalParts?format=json" % itemid
+                parts = self.doUtils(url)
+                for part in parts['Items']:
+
+                    additionalListItem = xbmcgui.ListItem()
+                    additionalPlayurl = putils.PlayUtils(part).getPlayUrl()
+                    self.logMsg("Adding additional part: %s" % partcount, 1)
+
+                    # Set listitem and properties for each additional parts
+                    pbutils = PlaybackUtils(part)
+                    pbutils.setProperties(additionalPlayurl, additionalListItem)
+                    pbutils.setArtwork(additionalListItem)
+
+                    playlist.add(additionalPlayurl, additionalListItem, index=currentPosition)
+                    self.pl.verifyPlaylist()
+                    currentPosition += 1
+
+            if dummyPlaylist:
+                # Added a dummy file to the playlist,
+                # because the first item is going to fail automatically.
+                self.logMsg("Processed as a playlist. First item is skipped.", 1)
+                return xbmcplugin.setResolvedUrl(int(sys.argv[1]), False, listitem)
+                
+
+        # We just skipped adding properties. Reset flag for next time.
+        elif propertiesPlayback:
+            self.logMsg("Resetting properties playback flag.", 2)
+            window('emby_playbackProps', clear=True)
+
+        #self.pl.verifyPlaylist()
+        ########## SETUP MAIN ITEM ##########
+
+        # For transcoding only, ask for audio/subs pref
+        if window('emby_%s.playmethod' % playurl) == "Transcode":
+            playurl = playutils.audioSubsPref(playurl, listitem)
+            window('emby_%s.playmethod' % playurl, value="Transcode")
+
+        listitem.setPath(playurl)
+        self.setProperties(playurl, listitem)
+
+        ############### PLAYBACK ################
+
+        if homeScreen and seektime and window('emby_customPlaylist') != "true":
+            self.logMsg("Play as a widget item.", 1)
+            self.setListItem(listitem)
+            xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, listitem)
+
+        elif ((introsPlaylist and window('emby_customPlaylist') == "true") or
+                (homeScreen and not sizePlaylist)):
+            # Playlist was created just now, play it.
+            self.logMsg("Play playlist.", 1)
+            xbmc.Player().play(playlist, startpos=startPos)
+
+        else:
+            self.logMsg("Play as a regular item.", 1)
+            xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, listitem)
+
+    def setProperties(self, playurl, listitem):
+
+        window = utils.window
+        # Set all properties necessary for plugin path playback
+        itemid = self.item['Id']
+        itemtype = self.item['Type']
+
+        embyitem = "emby_%s" % playurl
+        window('%s.runtime' % embyitem, value=str(self.item.get('RunTimeTicks')))
+        window('%s.type' % embyitem, value=itemtype)
+        window('%s.itemid' % embyitem, value=itemid)
+
+        if itemtype == "Episode":
+            window('%s.refreshid' % embyitem, value=self.item.get('SeriesId'))
+        else:
+            window('%s.refreshid' % embyitem, value=itemid)
+
+        # Append external subtitles to stream
+        playmethod = utils.window('%s.playmethod' % embyitem)
+        # Only for direct stream
+        if playmethod in ("DirectStream"):
+            # Direct play automatically appends external
+            subtitles = self.externalSubs(playurl)
+            listitem.setSubtitles(subtitles)
+
+        self.setArtwork(listitem)
+
+    def externalSubs(self, playurl):
+
+        externalsubs = []
+        mapping = {}
+
+        itemid = self.item['Id']
+        try:
+            mediastreams = self.item['MediaSources'][0]['MediaStreams']
+        except (TypeError, KeyError, IndexError):
+            return
+
+        kodiindex = 0
+        for stream in mediastreams:
+
+            index = stream['Index']
+            # Since Emby returns all possible tracks together, have to pull only external subtitles.
+            # IsTextSubtitleStream if true, is available to download from emby.
+            if (stream['Type'] == "Subtitle" and 
+                    stream['IsExternal'] and stream['IsTextSubtitleStream']):
+
+                # Direct stream
+                url = ("%s/Videos/%s/%s/Subtitles/%s/Stream.srt"
+                        % (self.server, itemid, itemid, index))
+                
+                # map external subtitles for mapping
+                mapping[kodiindex] = index
+                externalsubs.append(url)
+                kodiindex += 1
+        
+        mapping = json.dumps(mapping)
+        utils.window('emby_%s.indexMapping' % playurl, value=mapping)
+
+        return externalsubs
+
+    def setArtwork(self, listItem):
+        # Set up item and item info
+        allartwork = self.artwork.getAllArtwork(self.item, parentInfo=True)
+        # Set artwork for listitem
+        arttypes = {
+
+            'poster': "Primary",
+            'tvshow.poster': "Primary",
+            'clearart': "Art",
+            'tvshow.clearart': "Art",
+            'clearlogo': "Logo",
+            'tvshow.clearlogo': "Logo",
+            'discart': "Disc",
+            'fanart_image': "Backdrop",
+            'landscape': "Thumb"
+        }
+        for arttype in arttypes:
+
+            art = arttypes[arttype]
+            if art == "Backdrop":
+                try: # Backdrop is a list, grab the first backdrop
+                    self.setArtProp(listItem, arttype, allartwork[art][0])
+                except: pass
+            else:
+                self.setArtProp(listItem, arttype, allartwork[art])
+
+    def setArtProp(self, listItem, arttype, path):
+        
+        if arttype in (
+                'thumb', 'fanart_image', 'small_poster', 'tiny_poster',
+                'medium_landscape', 'medium_poster', 'small_fanartimage',
+                'medium_fanartimage', 'fanart_noindicators'):
+            
+            listItem.setProperty(arttype, path)
+        else:
+            listItem.setArt({arttype: path})
+
+    def setListItem(self, listItem):
+
+        people = self.API.getPeople()
+        studios = self.API.getStudios()
+
+        metadata = {
+            
+            'title': self.item.get('Name', "Missing name"),
+            'year': self.item.get('ProductionYear'),
+            'plot': self.API.getOverview(),
+            'director': people.get('Director'),
+            'writer': people.get('Writer'),
+            'mpaa': self.API.getMpaa(),
+            'genre': " / ".join(self.item['Genres']),
+            'studio': " / ".join(studios),
+            'aired': self.API.getPremiereDate(),
+            'rating': self.item.get('CommunityRating'),
+            'votes': self.item.get('VoteCount')
+        }
+
+        if "Episode" in self.item['Type']:
+            # Only for tv shows
+            thumbId = self.item.get('SeriesId')
+            season = self.item.get('ParentIndexNumber', -1)
+            episode = self.item.get('IndexNumber', -1)
+            show = self.item.get('SeriesName', "")
+
+            metadata['TVShowTitle'] = show
+            metadata['season'] = season
+            metadata['episode'] = episode
+
+        listItem.setProperty('IsPlayable', 'true')
+        listItem.setProperty('IsFolder', 'false')
+        listItem.setLabel(metadata['title'])
         listItem.setInfo('video', infoLabels=metadata)
\ No newline at end of file
diff --git a/resources/lib/player.py b/resources/lib/player.py
index 2e648180..7f323460 100644
--- a/resources/lib/player.py
+++ b/resources/lib/player.py
@@ -1,520 +1,511 @@
-# -*- coding: utf-8 -*-
-
-#################################################################################################
-
-import json
-
-import xbmc
-import xbmcgui
-
-import utils
-import clientinfo
-import downloadutils
-import kodidb_functions as kodidb
-import websocket_client as wsc
-
-#################################################################################################
-
-
-class Player(xbmc.Player):
-
-    # Borg - multiple instances, shared state
-    _shared_state = {}
-
-    played_info = {}
-    playStats = {}
-    currentFile = None
-
-
-    def __init__(self):
-
-        self.__dict__ = self._shared_state
-
-        self.clientInfo = clientinfo.ClientInfo()
-        self.addonName = self.clientInfo.getAddonName()
-        self.doUtils = downloadutils.DownloadUtils().downloadUrl
-        self.ws = wsc.WebSocket_Client()
-        self.xbmcplayer = xbmc.Player()
-
-        self.logMsg("Starting playback monitor.", 2)
-
-    def logMsg(self, msg, lvl=1):
-        
-        self.className = self.__class__.__name__
-        utils.logMsg("%s %s" % (self.addonName, self.className), msg, lvl)
-
-
-    def GetPlayStats(self):
-        return self.playStats
-
-    def onPlayBackStarted(self):
-
-        log = self.logMsg
-        window = utils.window
-        # Will be called when xbmc starts playing a file
-        xbmcplayer = self.xbmcplayer
-        self.stopAll()
-
-        # Get current file
-        try:
-            currentFile = xbmcplayer.getPlayingFile()
-            xbmc.sleep(300)
-        except:
-            currentFile = ""
-            count = 0
-            while not currentFile:
-                xbmc.sleep(100)
-                try:
-                    currentFile = xbmcplayer.getPlayingFile()
-                except: pass
-
-                if count == 5: # try 5 times
-                    log("Cancelling playback report...", 1)
-                    break
-                else: count += 1
-
-
-        if currentFile:
-
-            self.currentFile = currentFile
-            
-            # We may need to wait for info to be set in kodi monitor
-            itemId = window("emby_%s.itemid" % currentFile)
-            tryCount = 0
-            while not itemId:
-                
-                xbmc.sleep(200)
-                itemId = window("emby_%s.itemid" % currentFile)
-                if tryCount == 20: # try 20 times or about 10 seconds
-                    log("Could not find itemId, cancelling playback report...", 1)
-                    break
-                else: tryCount += 1
-            
-            else:
-                log("ONPLAYBACK_STARTED: %s itemid: %s" % (currentFile, itemId), 0)
-
-                # Only proceed if an itemId was found.
-                embyitem = "emby_%s" % currentFile
-                runtime = window("%s.runtime" % embyitem)
-                refresh_id = window("%s.refreshid" % embyitem)
-                playMethod = window("%s.playmethod" % embyitem)
-                itemType = window("%s.type" % embyitem)
-                window('emby_skipWatched%s' % itemId, value="true")
-
-                customseek = window('emby_customPlaylist.seektime')
-                if window('emby_customPlaylist') == "true" and customseek:
-                    # Start at, when using custom playlist (play to Kodi from webclient)
-                    log("Seeking to: %s" % customseek, 1)
-                    xbmcplayer.seekTime(int(customseek)/10000000.0)
-                    window('emby_customPlaylist.seektime', clear=True)
-
-                seekTime = xbmcplayer.getTime()
-
-                # Get playback volume
-                volume_query = {
-
-                    "jsonrpc": "2.0",
-                    "id": 1,
-                    "method": "Application.GetProperties",
-                    "params": {
-
-                        "properties": ["volume", "muted"] 
-                    }
-                }
-                result = xbmc.executeJSONRPC(json.dumps(volume_query))
-                result = json.loads(result)
-                result = result.get('result')
-                
-                volume = result.get('volume')
-                muted = result.get('muted')
-
-                # Postdata structure to send to Emby server
-                url = "{server}/emby/Sessions/Playing"
-                postdata = {
-
-                    'QueueableMediaTypes': "Video",
-                    'CanSeek': True,
-                    'ItemId': itemId,
-                    'MediaSourceId': itemId,
-                    'PlayMethod': playMethod,
-                    'VolumeLevel': volume,
-                    'PositionTicks': int(seekTime * 10000000),
-                    'IsMuted': muted
-                }
-
-                # Get the current audio track and subtitles
-                if playMethod == "Transcode":
-                    # property set in PlayUtils.py
-                    postdata['AudioStreamIndex'] = window("%sAudioStreamIndex" % currentFile)
-                    postdata['SubtitleStreamIndex'] = window("%sSubtitleStreamIndex" % currentFile)
-                else:
-                    # Get the current kodi audio and subtitles and convert to Emby equivalent
-                    tracks_query = {
-
-                        "jsonrpc": "2.0",
-                        "id": 1,
-                        "method": "Player.GetProperties",
-                        "params": {
-
-                            "playerid": 1,
-                            "properties": ["currentsubtitle","currentaudiostream","subtitleenabled"]
-                        }
-                    }
-                    result = xbmc.executeJSONRPC(json.dumps(tracks_query))
-                    result = json.loads(result)
-                    result = result.get('result')
-
-                    try: # Audio tracks
-                        indexAudio = result['currentaudiostream']['index']
-                    except (KeyError, TypeError):
-                        indexAudio = 0
-                    
-                    try: # Subtitles tracks
-                        indexSubs = result['currentsubtitle']['index']
-                    except (KeyError, TypeError):
-                        indexSubs = 0
-
-                    try: # If subtitles are enabled
-                        subsEnabled = result['subtitleenabled']
-                    except (KeyError, TypeError):
-                        subsEnabled = ""
-
-                    # Postdata for the audio
-                    postdata['AudioStreamIndex'] = indexAudio + 1
-                    
-                    # Postdata for the subtitles
-                    if subsEnabled and len(xbmc.Player().getAvailableSubtitleStreams()) > 0:
-                        
-                        # Number of audiotracks to help get Emby Index
-                        audioTracks = len(xbmc.Player().getAvailableAudioStreams())
-                        mapping = window("%s.indexMapping" % embyitem)
-
-                        if mapping: # Set in playbackutils.py
-                            
-                            log("Mapping for external subtitles index: %s" % mapping, 2)
-                            externalIndex = json.loads(mapping)
-
-                            if externalIndex.get(str(indexSubs)):
-                                # If the current subtitle is in the mapping
-                                postdata['SubtitleStreamIndex'] = externalIndex[str(indexSubs)]
-                            else:
-                                # Internal subtitle currently selected
-                                subindex = indexSubs - len(externalIndex) + audioTracks + 1
-                                postdata['SubtitleStreamIndex'] = subindex
-                        
-                        else: # Direct paths enabled scenario or no external subtitles set
-                            postdata['SubtitleStreamIndex'] = indexSubs + audioTracks + 1
-                    else:
-                        postdata['SubtitleStreamIndex'] = ""
-                
-
-                # Post playback to server
-                log("Sending POST play started: %s." % postdata, 2)
-                self.doUtils(url, postBody=postdata, type="POST")
-                
-                # Ensure we do have a runtime
-                try:
-                    runtime = int(runtime)
-                except ValueError:
-                    runtime = xbmcplayer.getTotalTime()
-                    log("Runtime is missing, Kodi runtime: %s" % runtime, 1)
-
-                # Save data map for updates and position calls
-                data = {
-                    
-                    'runtime': runtime,
-                    'item_id': itemId,
-                    'refresh_id': refresh_id,
-                    'currentfile': currentFile,
-                    'AudioStreamIndex': postdata['AudioStreamIndex'],
-                    'SubtitleStreamIndex': postdata['SubtitleStreamIndex'],
-                    'playmethod': playMethod,
-                    'Type': itemType,
-                    'currentPosition': int(seekTime)
-                }
-                
-                self.played_info[currentFile] = data
-                log("ADDING_FILE: %s" % self.played_info, 1)
-
-                # log some playback stats
-                '''if(itemType != None):
-                    if(self.playStats.get(itemType) != None):
-                        count = self.playStats.get(itemType) + 1
-                        self.playStats[itemType] = count
-                    else:
-                        self.playStats[itemType] = 1
-                        
-                if(playMethod != None):
-                    if(self.playStats.get(playMethod) != None):
-                        count = self.playStats.get(playMethod) + 1
-                        self.playStats[playMethod] = count
-                    else:
-                        self.playStats[playMethod] = 1'''
-
-    def reportPlayback(self):
-        
-        log = self.logMsg
-
-        log("reportPlayback Called", 2)
-        xbmcplayer = self.xbmcplayer
-
-        # Get current file
-        currentFile = self.currentFile
-        data = self.played_info.get(currentFile)
-
-        # only report playback if emby has initiated the playback (item_id has value)
-        if data:
-            # Get playback information
-            itemId = data['item_id']
-            audioindex = data['AudioStreamIndex']
-            subtitleindex = data['SubtitleStreamIndex']
-            playTime = data['currentPosition']
-            playMethod = data['playmethod']
-            paused = data.get('paused', False)
-
-
-            # Get playback volume
-            volume_query = {
-
-                    "jsonrpc": "2.0",
-                    "id": 1,
-                    "method": "Application.GetProperties",
-                    "params": {
-
-                        "properties": ["volume", "muted"] 
-                    }
-            }
-            result = xbmc.executeJSONRPC(json.dumps(volume_query))
-            result = json.loads(result)
-            result = result.get('result')
-
-            volume = result.get('volume')
-            muted = result.get('muted')
-
-            # Postdata for the websocketclient report
-            postdata = {
-
-                'QueueableMediaTypes': "Video",
-                'CanSeek': True,
-                'ItemId': itemId,
-                'MediaSourceId': itemId,
-                'PlayMethod': playMethod,
-                'PositionTicks': int(playTime * 10000000),
-                'IsPaused': paused,
-                'VolumeLevel': volume,
-                'IsMuted': muted
-            }
-
-            if playMethod == "Transcode":
-                # Track can't be changed, keep reporting the same index
-                postdata['AudioStreamIndex'] = audioindex
-                postdata['AudioStreamIndex'] = subtitleindex
-
-            else:
-                # Get current audio and subtitles track
-                tracks_query = {
-
-                        "jsonrpc": "2.0",
-                        "id": 1,
-                        "method": "Player.GetProperties",
-                        "params": {
-
-                            "playerid": 1,
-                            "properties": ["currentsubtitle","currentaudiostream","subtitleenabled"]
-                        }
-                    }
-                result = xbmc.executeJSONRPC(json.dumps(tracks_query))
-                result = json.loads(result)
-                result = result.get('result')
-
-                try: # Audio tracks
-                    indexAudio = result['currentaudiostream']['index']
-                except (KeyError, TypeError):
-                    indexAudio = 0
-                
-                try: # Subtitles tracks
-                    indexSubs = result['currentsubtitle']['index']
-                except (KeyError, TypeError):
-                    indexSubs = 0
-
-                try: # If subtitles are enabled
-                    subsEnabled = result['subtitleenabled']
-                except (KeyError, TypeError):
-                    subsEnabled = ""
-
-                # Postdata for the audio
-                data['AudioStreamIndex'], postdata['AudioStreamIndex'] = [indexAudio + 1] * 2
-                
-                # Postdata for the subtitles
-                if subsEnabled and len(xbmc.Player().getAvailableSubtitleStreams()) > 0:
-                    
-                    # Number of audiotracks to help get Emby Index
-                    audioTracks = len(xbmc.Player().getAvailableAudioStreams())
-                    mapping = utils.window("emby_%s.indexMapping" % currentFile)
-
-                    if mapping: # Set in PlaybackUtils.py
-                        
-                        log("Mapping for external subtitles index: %s" % mapping, 2)
-                        externalIndex = json.loads(mapping)
-
-                        if externalIndex.get(str(indexSubs)):
-                            # If the current subtitle is in the mapping
-                            subindex = [externalIndex[str(indexSubs)]] * 2
-                            data['SubtitleStreamIndex'], postdata['SubtitleStreamIndex'] = subindex
-                        else:
-                            # Internal subtitle currently selected
-                            subindex = [indexSubs - len(externalIndex) + audioTracks + 1] * 2
-                            data['SubtitleStreamIndex'], postdata['SubtitleStreamIndex'] = subindex
-                    
-                    else: # Direct paths enabled scenario or no external subtitles set
-                        subindex = [indexSubs + audioTracks + 1] * 2
-                        data['SubtitleStreamIndex'], postdata['SubtitleStreamIndex'] = subindex
-                else:
-                    data['SubtitleStreamIndex'], postdata['SubtitleStreamIndex'] = [""] * 2
-
-            # Report progress via websocketclient
-            postdata = json.dumps(postdata)
-            log("Report: %s" % postdata, 2)
-            self.ws.sendProgressUpdate(postdata)
-
-    def onPlayBackPaused(self):
-
-        currentFile = self.currentFile
-        self.logMsg("PLAYBACK_PAUSED: %s" % currentFile, 2)
-
-        if self.played_info.get(currentFile):
-            self.played_info[currentFile]['paused'] = True
-        
-            self.reportPlayback()
-
-    def onPlayBackResumed(self):
-
-        currentFile = self.currentFile
-        self.logMsg("PLAYBACK_RESUMED: %s" % currentFile, 2)
-
-        if self.played_info.get(currentFile):
-            self.played_info[currentFile]['paused'] = False
-        
-            self.reportPlayback()
-
-    def onPlayBackSeek(self, time, seekOffset):
-        # Make position when seeking a bit more accurate
-        currentFile = self.currentFile
-        self.logMsg("PLAYBACK_SEEK: %s" % currentFile, 2)
-
-        if self.played_info.get(currentFile):
-            position = self.xbmcplayer.getTime()
-            self.played_info[currentFile]['currentPosition'] = position
-
-            self.reportPlayback()
-    
-    def onPlayBackStopped(self):
-        
-        log = self.logMsg
-        window = utils.window
-        # Will be called when user stops xbmc playing a file
-        log("ONPLAYBACK_STOPPED", 2)
-        window('emby_customPlaylist', clear=True)
-        window('emby_customPlaylist.seektime', clear=True)
-        window('emby_playbackProps', clear=True)
-        log("Clear playlist properties.", 1)
-        self.stopAll()
-
-    def onPlayBackEnded(self):
-        # Will be called when xbmc stops playing a file
-        self.logMsg("ONPLAYBACK_ENDED", 2)
-        utils.window('emby_customPlaylist.seektime', clear=True)
-        self.stopAll()
-
-    def stopAll(self):
-
-        log = self.logMsg
-        lang = utils.language
-        settings = utils.settings
-
-        doUtils = self.doUtils
-
-        if not self.played_info:
-            return 
-            
-        log("Played_information: %s" % self.played_info, 1)
-        # Process each items
-        for item in self.played_info:
-            
-            data = self.played_info.get(item)
-            if data:
-                
-                log("Item path: %s" % item, 2)
-                log("Item data: %s" % data, 2)
-
-                runtime = data['runtime']
-                currentPosition = data['currentPosition']
-                itemid = data['item_id']
-                refresh_id = data['refresh_id']
-                currentFile = data['currentfile']
-                type = data['Type']
-                playMethod = data['playmethod']
-
-                # Prevent manually mark as watched in Kodi monitor
-                utils.window('emby_skipWatched%s' % itemid, value="true")
-
-                if currentPosition and runtime:
-                    try:
-                        percentComplete = (currentPosition * 10000000) / int(runtime)
-                    except ZeroDivisionError:
-                        # Runtime is 0.
-                        percentComplete = 0
-                        
-                    markPlayedAt = float(settings('markPlayed')) / 100
-                    log("Percent complete: %s Mark played at: %s"
-                        % (percentComplete, markPlayedAt), 1)
-
-                    # Send the delete action to the server.
-                    offerDelete = False
-
-                    if type == "Episode" and settings('deleteTV') == "true":
-                        offerDelete = True
-                    elif type == "Movie" and settings('deleteMovies') == "true":
-                        offerDelete = True
-
-                    if settings('offerDelete') != "true":
-                        # Delete could be disabled, even if the subsetting is enabled.
-                        offerDelete = False
-
-                    if percentComplete >= markPlayedAt and offerDelete:
-                        resp = xbmcgui.Dialog().yesno(lang(30091), lang(33015), autoclose=120000)
-                        if not resp:
-                            log("User skipped deletion.", 1)
-                            continue
-
-                        url = "{server}/emby/Items/%s?format=json" % itemid
-                        log("Deleting request: %s" % itemid, 1)
-                        doUtils(url, type="DELETE")
-
-                self.stopPlayback(data)
-
-                # Stop transcoding
-                if playMethod == "Transcode":
-                    log("Transcoding for %s terminated." % itemid, 1)
-                    deviceId = self.clientInfo.getDeviceId()
-                    url = "{server}/emby/Videos/ActiveEncodings?DeviceId=%s" % deviceId
-                    doUtils(url, type="DELETE")
-    
-        self.played_info.clear()
-    
-    def stopPlayback(self, data):
-        
-        self.logMsg("stopPlayback called", 2)
-        
-        itemId = data['item_id']
-        currentPosition = data['currentPosition']
-        positionTicks = int(currentPosition * 10000000)
-
-        url = "{server}/emby/Sessions/Playing/Stopped"
-        postdata = {
-            
-            'ItemId': itemId,
-            'MediaSourceId': itemId,
-            'PositionTicks': positionTicks
-        }
-        self.doUtils(url, postBody=postdata, type="POST")
\ No newline at end of file
+# -*- coding: utf-8 -*-
+
+#################################################################################################
+
+import json
+
+import xbmc
+import xbmcgui
+
+import utils
+import clientinfo
+import downloadutils
+import kodidb_functions as kodidb
+import websocket_client as wsc
+
+#################################################################################################
+
+
+class Player(xbmc.Player):
+
+    # Borg - multiple instances, shared state
+    _shared_state = {}
+
+    played_info = {}
+    playStats = {}
+    currentFile = None
+
+
+    def __init__(self):
+
+        self.__dict__ = self._shared_state
+
+        self.clientInfo = clientinfo.ClientInfo()
+        self.addonName = self.clientInfo.getAddonName()
+        self.doUtils = downloadutils.DownloadUtils().downloadUrl
+        self.ws = wsc.WebSocket_Client()
+        self.xbmcplayer = xbmc.Player()
+
+        self.logMsg("Starting playback monitor.", 2)
+
+    def logMsg(self, msg, lvl=1):
+        
+        self.className = self.__class__.__name__
+        utils.logMsg("%s %s" % (self.addonName, self.className), msg, lvl)
+
+
+    def GetPlayStats(self):
+        return self.playStats
+
+    def onPlayBackStarted(self):
+
+        window = utils.window
+        # Will be called when xbmc starts playing a file
+        self.stopAll()
+
+        # Get current file
+        try:
+            currentFile = self.xbmcplayer.getPlayingFile()
+            xbmc.sleep(300)
+        except:
+            currentFile = ""
+            count = 0
+            while not currentFile:
+                xbmc.sleep(100)
+                try:
+                    currentFile = self.xbmcplayer.getPlayingFile()
+                except: pass
+
+                if count == 5: # try 5 times
+                    self.logMsg("Cancelling playback report...", 1)
+                    break
+                else: count += 1
+
+
+        if currentFile:
+
+            self.currentFile = currentFile
+            
+            # We may need to wait for info to be set in kodi monitor
+            itemId = window("emby_%s.itemid" % currentFile)
+            tryCount = 0
+            while not itemId:
+                
+                xbmc.sleep(200)
+                itemId = window("emby_%s.itemid" % currentFile)
+                if tryCount == 20: # try 20 times or about 10 seconds
+                    self.logMsg("Could not find itemId, cancelling playback report...", 1)
+                    break
+                else: tryCount += 1
+            
+            else:
+                self.logMsg("ONPLAYBACK_STARTED: %s itemid: %s" % (currentFile, itemId), 0)
+
+                # Only proceed if an itemId was found.
+                embyitem = "emby_%s" % currentFile
+                runtime = window("%s.runtime" % embyitem)
+                refresh_id = window("%s.refreshid" % embyitem)
+                playMethod = window("%s.playmethod" % embyitem)
+                itemType = window("%s.type" % embyitem)
+                window('emby_skipWatched%s' % itemId, value="true")
+
+                customseek = window('emby_customPlaylist.seektime')
+                if window('emby_customPlaylist') == "true" and customseek:
+                    # Start at, when using custom playlist (play to Kodi from webclient)
+                    self.logMsg("Seeking to: %s" % customseek, 1)
+                    self.xbmcplayer.seekTime(int(customseek)/10000000.0)
+                    window('emby_customPlaylist.seektime', clear=True)
+
+                seekTime = self.xbmcplayer.getTime()
+
+                # Get playback volume
+                volume_query = {
+
+                    "jsonrpc": "2.0",
+                    "id": 1,
+                    "method": "Application.GetProperties",
+                    "params": {
+
+                        "properties": ["volume", "muted"] 
+                    }
+                }
+                result = xbmc.executeJSONRPC(json.dumps(volume_query))
+                result = json.loads(result)
+                result = result.get('result')
+                
+                volume = result.get('volume')
+                muted = result.get('muted')
+
+                # Postdata structure to send to Emby server
+                url = "{server}/emby/Sessions/Playing"
+                postdata = {
+
+                    'QueueableMediaTypes': "Video",
+                    'CanSeek': True,
+                    'ItemId': itemId,
+                    'MediaSourceId': itemId,
+                    'PlayMethod': playMethod,
+                    'VolumeLevel': volume,
+                    'PositionTicks': int(seekTime * 10000000),
+                    'IsMuted': muted
+                }
+
+                # Get the current audio track and subtitles
+                if playMethod == "Transcode":
+                    # property set in PlayUtils.py
+                    postdata['AudioStreamIndex'] = window("%sAudioStreamIndex" % currentFile)
+                    postdata['SubtitleStreamIndex'] = window("%sSubtitleStreamIndex" % currentFile)
+                else:
+                    # Get the current kodi audio and subtitles and convert to Emby equivalent
+                    tracks_query = {
+
+                        "jsonrpc": "2.0",
+                        "id": 1,
+                        "method": "Player.GetProperties",
+                        "params": {
+
+                            "playerid": 1,
+                            "properties": ["currentsubtitle","currentaudiostream","subtitleenabled"]
+                        }
+                    }
+                    result = xbmc.executeJSONRPC(json.dumps(tracks_query))
+                    result = json.loads(result)
+                    result = result.get('result')
+
+                    try: # Audio tracks
+                        indexAudio = result['currentaudiostream']['index']
+                    except (KeyError, TypeError):
+                        indexAudio = 0
+                    
+                    try: # Subtitles tracks
+                        indexSubs = result['currentsubtitle']['index']
+                    except (KeyError, TypeError):
+                        indexSubs = 0
+
+                    try: # If subtitles are enabled
+                        subsEnabled = result['subtitleenabled']
+                    except (KeyError, TypeError):
+                        subsEnabled = ""
+
+                    # Postdata for the audio
+                    postdata['AudioStreamIndex'] = indexAudio + 1
+                    
+                    # Postdata for the subtitles
+                    if subsEnabled and len(xbmc.Player().getAvailableSubtitleStreams()) > 0:
+                        
+                        # Number of audiotracks to help get Emby Index
+                        audioTracks = len(xbmc.Player().getAvailableAudioStreams())
+                        mapping = window("%s.indexMapping" % embyitem)
+
+                        if mapping: # Set in playbackutils.py
+                            
+                            self.logMsg("Mapping for external subtitles index: %s" % mapping, 2)
+                            externalIndex = json.loads(mapping)
+
+                            if externalIndex.get(str(indexSubs)):
+                                # If the current subtitle is in the mapping
+                                postdata['SubtitleStreamIndex'] = externalIndex[str(indexSubs)]
+                            else:
+                                # Internal subtitle currently selected
+                                subindex = indexSubs - len(externalIndex) + audioTracks + 1
+                                postdata['SubtitleStreamIndex'] = subindex
+                        
+                        else: # Direct paths enabled scenario or no external subtitles set
+                            postdata['SubtitleStreamIndex'] = indexSubs + audioTracks + 1
+                    else:
+                        postdata['SubtitleStreamIndex'] = ""
+                
+
+                # Post playback to server
+                self.logMsg("Sending POST play started: %s." % postdata, 2)
+                self.doUtils(url, postBody=postdata, action_type="POST")
+                
+                # Ensure we do have a runtime
+                try:
+                    runtime = int(runtime)
+                except ValueError:
+                    runtime = self.xbmcplayer.getTotalTime()
+                    self.logMsg("Runtime is missing, Kodi runtime: %s" % runtime, 1)
+
+                # Save data map for updates and position calls
+                data = {
+                    
+                    'runtime': runtime,
+                    'item_id': itemId,
+                    'refresh_id': refresh_id,
+                    'currentfile': currentFile,
+                    'AudioStreamIndex': postdata['AudioStreamIndex'],
+                    'SubtitleStreamIndex': postdata['SubtitleStreamIndex'],
+                    'playmethod': playMethod,
+                    'Type': itemType,
+                    'currentPosition': int(seekTime)
+                }
+                
+                self.played_info[currentFile] = data
+                self.logMsg("ADDING_FILE: %s" % self.played_info, 1)
+
+                # log some playback stats
+                '''if(itemType != None):
+                    if(self.playStats.get(itemType) != None):
+                        count = self.playStats.get(itemType) + 1
+                        self.playStats[itemType] = count
+                    else:
+                        self.playStats[itemType] = 1
+                        
+                if(playMethod != None):
+                    if(self.playStats.get(playMethod) != None):
+                        count = self.playStats.get(playMethod) + 1
+                        self.playStats[playMethod] = count
+                    else:
+                        self.playStats[playMethod] = 1'''
+
+    def reportPlayback(self):
+        
+        self.logMsg("reportPlayback Called", 2)
+
+        # Get current file
+        currentFile = self.currentFile
+        data = self.played_info.get(currentFile)
+
+        # only report playback if emby has initiated the playback (item_id has value)
+        if data:
+            # Get playback information
+            itemId = data['item_id']
+            audioindex = data['AudioStreamIndex']
+            subtitleindex = data['SubtitleStreamIndex']
+            playTime = data['currentPosition']
+            playMethod = data['playmethod']
+            paused = data.get('paused', False)
+
+
+            # Get playback volume
+            volume_query = {
+
+                    "jsonrpc": "2.0",
+                    "id": 1,
+                    "method": "Application.GetProperties",
+                    "params": {
+
+                        "properties": ["volume", "muted"] 
+                    }
+            }
+            result = xbmc.executeJSONRPC(json.dumps(volume_query))
+            result = json.loads(result)
+            result = result.get('result')
+
+            volume = result.get('volume')
+            muted = result.get('muted')
+
+            # Postdata for the websocketclient report
+            postdata = {
+
+                'QueueableMediaTypes': "Video",
+                'CanSeek': True,
+                'ItemId': itemId,
+                'MediaSourceId': itemId,
+                'PlayMethod': playMethod,
+                'PositionTicks': int(playTime * 10000000),
+                'IsPaused': paused,
+                'VolumeLevel': volume,
+                'IsMuted': muted
+            }
+
+            if playMethod == "Transcode":
+                # Track can't be changed, keep reporting the same index
+                postdata['AudioStreamIndex'] = audioindex
+                postdata['AudioStreamIndex'] = subtitleindex
+
+            else:
+                # Get current audio and subtitles track
+                tracks_query = {
+
+                        "jsonrpc": "2.0",
+                        "id": 1,
+                        "method": "Player.GetProperties",
+                        "params": {
+
+                            "playerid": 1,
+                            "properties": ["currentsubtitle","currentaudiostream","subtitleenabled"]
+                        }
+                    }
+                result = xbmc.executeJSONRPC(json.dumps(tracks_query))
+                result = json.loads(result)
+                result = result.get('result')
+
+                try: # Audio tracks
+                    indexAudio = result['currentaudiostream']['index']
+                except (KeyError, TypeError):
+                    indexAudio = 0
+                
+                try: # Subtitles tracks
+                    indexSubs = result['currentsubtitle']['index']
+                except (KeyError, TypeError):
+                    indexSubs = 0
+
+                try: # If subtitles are enabled
+                    subsEnabled = result['subtitleenabled']
+                except (KeyError, TypeError):
+                    subsEnabled = ""
+
+                # Postdata for the audio
+                data['AudioStreamIndex'], postdata['AudioStreamIndex'] = [indexAudio + 1] * 2
+                
+                # Postdata for the subtitles
+                if subsEnabled and len(xbmc.Player().getAvailableSubtitleStreams()) > 0:
+                    
+                    # Number of audiotracks to help get Emby Index
+                    audioTracks = len(xbmc.Player().getAvailableAudioStreams())
+                    mapping = utils.window("emby_%s.indexMapping" % currentFile)
+
+                    if mapping: # Set in PlaybackUtils.py
+                        
+                        self.logMsg("Mapping for external subtitles index: %s" % mapping, 2)
+                        externalIndex = json.loads(mapping)
+
+                        if externalIndex.get(str(indexSubs)):
+                            # If the current subtitle is in the mapping
+                            subindex = [externalIndex[str(indexSubs)]] * 2
+                            data['SubtitleStreamIndex'], postdata['SubtitleStreamIndex'] = subindex
+                        else:
+                            # Internal subtitle currently selected
+                            subindex = [indexSubs - len(externalIndex) + audioTracks + 1] * 2
+                            data['SubtitleStreamIndex'], postdata['SubtitleStreamIndex'] = subindex
+                    
+                    else: # Direct paths enabled scenario or no external subtitles set
+                        subindex = [indexSubs + audioTracks + 1] * 2
+                        data['SubtitleStreamIndex'], postdata['SubtitleStreamIndex'] = subindex
+                else:
+                    data['SubtitleStreamIndex'], postdata['SubtitleStreamIndex'] = [""] * 2
+
+            # Report progress via websocketclient
+            postdata = json.dumps(postdata)
+            self.logMsg("Report: %s" % postdata, 2)
+            self.ws.sendProgressUpdate(postdata)
+
+    def onPlayBackPaused(self):
+
+        currentFile = self.currentFile
+        self.logMsg("PLAYBACK_PAUSED: %s" % currentFile, 2)
+
+        if self.played_info.get(currentFile):
+            self.played_info[currentFile]['paused'] = True
+        
+            self.reportPlayback()
+
+    def onPlayBackResumed(self):
+
+        currentFile = self.currentFile
+        self.logMsg("PLAYBACK_RESUMED: %s" % currentFile, 2)
+
+        if self.played_info.get(currentFile):
+            self.played_info[currentFile]['paused'] = False
+        
+            self.reportPlayback()
+
+    def onPlayBackSeek(self, time, seekOffset):
+        # Make position when seeking a bit more accurate
+        currentFile = self.currentFile
+        self.logMsg("PLAYBACK_SEEK: %s" % currentFile, 2)
+
+        if self.played_info.get(currentFile):
+            position = self.xbmcplayer.getTime()
+            self.played_info[currentFile]['currentPosition'] = position
+
+            self.reportPlayback()
+    
+    def onPlayBackStopped(self):
+        
+        window = utils.window
+        # Will be called when user stops xbmc playing a file
+        self.logMsg("ONPLAYBACK_STOPPED", 2)
+        window('emby_customPlaylist', clear=True)
+        window('emby_customPlaylist.seektime', clear=True)
+        window('emby_playbackProps', clear=True)
+        self.logMsg("Clear playlist properties.", 1)
+        self.stopAll()
+
+    def onPlayBackEnded(self):
+        # Will be called when xbmc stops playing a file
+        self.logMsg("ONPLAYBACK_ENDED", 2)
+        utils.window('emby_customPlaylist.seektime', clear=True)
+        self.stopAll()
+
+    def stopAll(self):
+
+        lang = utils.language
+        settings = utils.settings
+
+        if not self.played_info:
+            return 
+            
+        self.logMsg("Played_information: %s" % self.played_info, 1)
+        # Process each items
+        for item in self.played_info:
+            
+            data = self.played_info.get(item)
+            if data:
+                
+                self.logMsg("Item path: %s" % item, 2)
+                self.logMsg("Item data: %s" % data, 2)
+
+                runtime = data['runtime']
+                currentPosition = data['currentPosition']
+                itemid = data['item_id']
+                refresh_id = data['refresh_id']
+                currentFile = data['currentfile']
+                media_type = data['Type']
+                playMethod = data['playmethod']
+
+                # Prevent manually mark as watched in Kodi monitor
+                utils.window('emby_skipWatched%s' % itemid, value="true")
+
+                if currentPosition and runtime:
+                    try:
+                        percentComplete = (currentPosition * 10000000) / int(runtime)
+                    except ZeroDivisionError:
+                        # Runtime is 0.
+                        percentComplete = 0
+                        
+                    markPlayedAt = float(settings('markPlayed')) / 100
+                    self.logMsg("Percent complete: %s Mark played at: %s"
+                        % (percentComplete, markPlayedAt), 1)
+
+                    # Send the delete action to the server.
+                    offerDelete = False
+
+                    if media_type == "Episode" and settings('deleteTV') == "true":
+                        offerDelete = True
+                    elif media_type == "Movie" and settings('deleteMovies') == "true":
+                        offerDelete = True
+
+                    if settings('offerDelete') != "true":
+                        # Delete could be disabled, even if the subsetting is enabled.
+                        offerDelete = False
+
+                    if percentComplete >= markPlayedAt and offerDelete:
+                        resp = xbmcgui.Dialog().yesno(lang(30091), lang(33015), autoclose=120000)
+                        if not resp:
+                            self.logMsg("User skipped deletion.", 1)
+                            continue
+
+                        url = "{server}/emby/Items/%s?format=json" % itemid
+                        self.logMsg("Deleting request: %s" % itemid, 1)
+                        self.doUtils(url, action_type="DELETE")
+
+                self.stopPlayback(data)
+
+                # Stop transcoding
+                if playMethod == "Transcode":
+                    self.logMsg("Transcoding for %s terminated." % itemid, 1)
+                    deviceId = self.clientInfo.getDeviceId()
+                    url = "{server}/emby/Videos/ActiveEncodings?DeviceId=%s" % deviceId
+                    self.doUtils(url, action_type="DELETE")
+    
+        self.played_info.clear()
+    
+    def stopPlayback(self, data):
+        
+        self.logMsg("stopPlayback called", 2)
+        
+        itemId = data['item_id']
+        currentPosition = data['currentPosition']
+        positionTicks = int(currentPosition * 10000000)
+
+        url = "{server}/emby/Sessions/Playing/Stopped"
+        postdata = {
+            
+            'ItemId': itemId,
+            'MediaSourceId': itemId,
+            'PositionTicks': positionTicks
+        }
+        self.doUtils(url, postBody=postdata, action_type="POST")
\ No newline at end of file
diff --git a/resources/lib/playlist.py b/resources/lib/playlist.py
index 383d34a8..bcd34a46 100644
--- a/resources/lib/playlist.py
+++ b/resources/lib/playlist.py
@@ -1,204 +1,196 @@
-# -*- coding: utf-8 -*-
-
-#################################################################################################
-
-import json
-
-import xbmc
-import xbmcgui
-import xbmcplugin
-
-import clientinfo
-import playutils
-import playbackutils
-import embydb_functions as embydb
-import read_embyserver as embyserver
-import utils
-
-#################################################################################################
-
-
-class Playlist():
-
-
-    def __init__(self):
-
-        self.clientInfo = clientinfo.ClientInfo()
-        self.addonName = self.clientInfo.getAddonName()
-
-        self.userid = utils.window('emby_currUser')
-        self.server = utils.window('emby_server%s' % self.userid)
-
-        self.emby = embyserver.Read_EmbyServer()
-
-    def logMsg(self, msg, lvl=1):
-
-        self.className = self.__class__.__name__
-        utils.logMsg("%s %s" % (self.addonName, self.className), msg, lvl)
-
-
-    def playAll(self, itemids, startat):
-
-        log = self.logMsg
-        window = utils.window
-
-        embyconn = utils.kodiSQL('emby')
-        embycursor = embyconn.cursor()
-        emby_db = embydb.Embydb_Functions(embycursor)
-
-        player = xbmc.Player()
-        playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
-        playlist.clear()
-
-        log("---*** PLAY ALL ***---", 1)
-        log("Items: %s and start at: %s" % (itemids, startat), 1)
-
-        started = False
-        window('emby_customplaylist', value="true")
-
-        if startat != 0:
-            # Seek to the starting position
-            window('emby_customplaylist.seektime', str(startat))
-
-        for itemid in itemids:
-            embydb_item = emby_db.getItem_byId(itemid)
-            try:
-                dbid = embydb_item[0]
-                mediatype = embydb_item[4]
-            except TypeError:
-                # Item is not found in our database, add item manually
-                log("Item was not found in the database, manually adding item.", 1)
-                item = self.emby.getItem(itemid)
-                self.addtoPlaylist_xbmc(playlist, item)
-            else:
-                # Add to playlist
-                self.addtoPlaylist(dbid, mediatype)
-
-            log("Adding %s to playlist." % itemid, 1)
-
-            if not started:
-                started = True
-                player.play(playlist)
-
-        self.verifyPlaylist()
-        embycursor.close()
-
-    def modifyPlaylist(self, itemids):
-
-        log = self.logMsg
-
-        embyconn = utils.kodiSQL('emby')
-        embycursor = embyconn.cursor()
-        emby_db = embydb.Embydb_Functions(embycursor)
-
-        log("---*** ADD TO PLAYLIST ***---", 1)
-        log("Items: %s" % itemids, 1)
-
-        player = xbmc.Player()
-        playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
-
-        for itemid in itemids:
-            embydb_item = emby_db.getItem_byId(itemid)
-            try:
-                dbid = embydb_item[0]
-                mediatype = embydb_item[4]
-            except TypeError:
-                # Item is not found in our database, add item manually
-                item = self.emby.getItem(itemid)
-                self.addtoPlaylist_xbmc(playlist, item)
-            else:
-                # Add to playlist
-                self.addtoPlaylist(dbid, mediatype)
-
-            log("Adding %s to playlist." % itemid, 1)
-
-        self.verifyPlaylist()
-        embycursor.close()
-        return playlist
-    
-    def addtoPlaylist(self, dbid=None, mediatype=None, url=None):
-
-        pl = {
-
-            'jsonrpc': "2.0",
-            'id': 1,
-            'method': "Playlist.Add",
-            'params': {
-
-                'playlistid': 1
-            }
-        }
-        if dbid is not None:
-            pl['params']['item'] = {'%sid' % mediatype: int(dbid)}
-        else:
-            pl['params']['item'] = {'file': url}
-
-        result = xbmc.executeJSONRPC(json.dumps(pl))
-        self.logMsg(result, 2)
-
-    def addtoPlaylist_xbmc(self, playlist, item):
-
-        itemid = item['Id']
-        playurl = playutils.PlayUtils(item).getPlayUrl()
-        if not playurl:
-            # Playurl failed
-            self.logMsg("Failed to retrieve playurl.", 1)
-            return
-
-        self.logMsg("Playurl: %s" % playurl)
-        listitem = xbmcgui.ListItem()
-        playbackutils.PlaybackUtils(item).setProperties(playurl, listitem)
-
-        playlist.add(playurl, listitem)
-
-    def insertintoPlaylist(self, position, dbid=None, mediatype=None, url=None):
-
-        pl = {
-
-            'jsonrpc': "2.0",
-            'id': 1,
-            'method': "Playlist.Insert",
-            'params': {
-
-                'playlistid': 1,
-                'position': position
-            }
-        }
-        if dbid is not None:
-            pl['params']['item'] = {'%sid' % mediatype: int(dbid)}
-        else:
-            pl['params']['item'] = {'file': url}
-
-        result = xbmc.executeJSONRPC(json.dumps(pl))
-        self.logMsg(result, 2)
-
-    def verifyPlaylist(self):
-
-        pl = {
-
-            'jsonrpc': "2.0",
-            'id': 1,
-            'method': "Playlist.GetItems",
-            'params': {
-
-                'playlistid': 1
-            }
-        }
-        result = xbmc.executeJSONRPC(json.dumps(pl))
-        self.logMsg(result, 2)
-
-    def removefromPlaylist(self, position):
-
-        pl = {
-
-            'jsonrpc': "2.0",
-            'id': 1,
-            'method': "Playlist.Remove",
-            'params': {
-
-                'playlistid': 1,
-                'position': position
-            }
-        }
-        result = xbmc.executeJSONRPC(json.dumps(pl))
-        self.logMsg(result, 2)
\ No newline at end of file
+# -*- coding: utf-8 -*-
+
+#################################################################################################
+
+import json
+
+import xbmc
+import xbmcgui
+import xbmcplugin
+
+import clientinfo
+import playutils
+import playbackutils
+import embydb_functions as embydb
+import read_embyserver as embyserver
+import utils
+
+#################################################################################################
+
+
+class Playlist():
+
+
+    def __init__(self):
+
+        self.clientInfo = clientinfo.ClientInfo()
+        self.addonName = self.clientInfo.getAddonName()
+
+        self.userid = utils.window('emby_currUser')
+        self.server = utils.window('emby_server%s' % self.userid)
+
+        self.emby = embyserver.Read_EmbyServer()
+
+    def logMsg(self, msg, lvl=1):
+
+        self.className = self.__class__.__name__
+        utils.logMsg("%s %s" % (self.addonName, self.className), msg, lvl)
+
+
+    def playAll(self, itemids, startat):
+
+        window = utils.window
+
+        embyconn = utils.kodiSQL('emby')
+        embycursor = embyconn.cursor()
+        emby_db = embydb.Embydb_Functions(embycursor)
+
+        player = xbmc.Player()
+        playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
+        playlist.clear()
+
+        self.logMsg("---*** PLAY ALL ***---", 1)
+        self.logMsg("Items: %s and start at: %s" % (itemids, startat), 1)
+
+        started = False
+        window('emby_customplaylist', value="true")
+
+        if startat != 0:
+            # Seek to the starting position
+            window('emby_customplaylist.seektime', str(startat))
+
+        for itemid in itemids:
+            embydb_item = emby_db.getItem_byId(itemid)
+            try:
+                dbid = embydb_item[0]
+                mediatype = embydb_item[4]
+            except TypeError:
+                # Item is not found in our database, add item manually
+                self.logMsg("Item was not found in the database, manually adding item.", 1)
+                item = self.emby.getItem(itemid)
+                self.addtoPlaylist_xbmc(playlist, item)
+            else:
+                # Add to playlist
+                self.addtoPlaylist(dbid, mediatype)
+
+            self.logMsg("Adding %s to playlist." % itemid, 1)
+
+            if not started:
+                started = True
+                player.play(playlist)
+
+        self.verifyPlaylist()
+        embycursor.close()
+
+    def modifyPlaylist(self, itemids):
+
+        embyconn = utils.kodiSQL('emby')
+        embycursor = embyconn.cursor()
+        emby_db = embydb.Embydb_Functions(embycursor)
+
+        self.logMsg("---*** ADD TO PLAYLIST ***---", 1)
+        self.logMsg("Items: %s" % itemids, 1)
+
+        player = xbmc.Player()
+        playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
+
+        for itemid in itemids:
+            embydb_item = emby_db.getItem_byId(itemid)
+            try:
+                dbid = embydb_item[0]
+                mediatype = embydb_item[4]
+            except TypeError:
+                # Item is not found in our database, add item manually
+                item = self.emby.getItem(itemid)
+                self.addtoPlaylist_xbmc(playlist, item)
+            else:
+                # Add to playlist
+                self.addtoPlaylist(dbid, mediatype)
+
+            self.logMsg("Adding %s to playlist." % itemid, 1)
+
+        self.verifyPlaylist()
+        embycursor.close()
+        return playlist
+    
+    def addtoPlaylist(self, dbid=None, mediatype=None, url=None):
+
+        pl = {
+
+            'jsonrpc': "2.0",
+            'id': 1,
+            'method': "Playlist.Add",
+            'params': {
+
+                'playlistid': 1
+            }
+        }
+        if dbid is not None:
+            pl['params']['item'] = {'%sid' % mediatype: int(dbid)}
+        else:
+            pl['params']['item'] = {'file': url}
+
+        self.logMsg(xbmc.executeJSONRPC(json.dumps(pl)), 2)
+
+    def addtoPlaylist_xbmc(self, playlist, item):
+
+        playurl = playutils.PlayUtils(item).getPlayUrl()
+        if not playurl:
+            # Playurl failed
+            self.logMsg("Failed to retrieve playurl.", 1)
+            return
+
+        self.logMsg("Playurl: %s" % playurl)
+        listitem = xbmcgui.ListItem()
+        playbackutils.PlaybackUtils(item).setProperties(playurl, listitem)
+
+        playlist.add(playurl, listitem)
+
+    def insertintoPlaylist(self, position, dbid=None, mediatype=None, url=None):
+
+        pl = {
+
+            'jsonrpc': "2.0",
+            'id': 1,
+            'method': "Playlist.Insert",
+            'params': {
+
+                'playlistid': 1,
+                'position': position
+            }
+        }
+        if dbid is not None:
+            pl['params']['item'] = {'%sid' % mediatype: int(dbid)}
+        else:
+            pl['params']['item'] = {'file': url}
+
+        self.logMsg(xbmc.executeJSONRPC(json.dumps(pl)), 2)
+
+    def verifyPlaylist(self):
+
+        pl = {
+
+            'jsonrpc': "2.0",
+            'id': 1,
+            'method': "Playlist.GetItems",
+            'params': {
+
+                'playlistid': 1
+            }
+        }
+        self.logMsg(xbmc.executeJSONRPC(json.dumps(pl)), 2)
+
+    def removefromPlaylist(self, position):
+
+        pl = {
+
+            'jsonrpc': "2.0",
+            'id': 1,
+            'method': "Playlist.Remove",
+            'params': {
+
+                'playlistid': 1,
+                'position': position
+            }
+        }
+        self.logMsg(xbmc.executeJSONRPC(json.dumps(pl)), 2)
diff --git a/resources/lib/playutils.py b/resources/lib/playutils.py
index 37332b16..622781d2 100644
--- a/resources/lib/playutils.py
+++ b/resources/lib/playutils.py
@@ -1,458 +1,426 @@
-# -*- coding: utf-8 -*-
-
-#################################################################################################
-
-import xbmc
-import xbmcgui
-import xbmcvfs
-
-import clientinfo
-import utils
-
-#################################################################################################
-
-
-class PlayUtils():
-    
-    
-    def __init__(self, item):
-
-        self.item = item
-
-        self.clientInfo = clientinfo.ClientInfo()
-        self.addonName = self.clientInfo.getAddonName()
-
-        self.userid = utils.window('emby_currUser')
-        self.server = utils.window('emby_server%s' % self.userid)
-
-    def logMsg(self, msg, lvl=1):
-
-        self.className = self.__class__.__name__
-        utils.logMsg("%s %s" % (self.addonName, self.className), msg, lvl)
-    
-
-    def getPlayUrl(self):
-
-        log = self.logMsg
-        window = utils.window
-
-        item = self.item
-        playurl = None
-        
-        if (item.get('Type') in ("Recording", "TvChannel") and
-                item.get('MediaSources') and item['MediaSources'][0]['Protocol'] == "Http"):
-            # Play LiveTV or recordings
-            log("File protocol is http (livetv).", 1)
-            playurl = "%s/emby/Videos/%s/live.m3u8?static=true" % (self.server, item['Id'])
-            window('emby_%s.playmethod' % playurl, value="Transcode")
-
-        elif item.get('MediaSources') and item['MediaSources'][0]['Protocol'] == "Http":
-            # Only play as http, used for channels, or online hosting of content
-            log("File protocol is http.", 1)
-            playurl = self.httpPlay()
-            window('emby_%s.playmethod' % playurl, value="DirectStream")
-
-        elif self.isDirectPlay():
-
-            log("File is direct playing.", 1)
-            playurl = self.directPlay()
-            playurl = playurl.encode('utf-8')
-            # Set playmethod property
-            window('emby_%s.playmethod' % playurl, value="DirectPlay")
-
-        elif self.isDirectStream():
-            
-            log("File is direct streaming.", 1)
-            playurl = self.directStream()
-            # Set playmethod property
-            window('emby_%s.playmethod' % playurl, value="DirectStream")
-
-        elif self.isTranscoding():
-            
-            log("File is transcoding.", 1)
-            playurl = self.transcoding()
-            # Set playmethod property
-            window('emby_%s.playmethod' % playurl, value="Transcode")
-
-        return playurl
-
-    def httpPlay(self):
-        # Audio, Video, Photo
-        item = self.item
-        server = self.server
-
-        itemid = item['Id']
-        mediatype = item['MediaType']
-
-        if mediatype == "Audio":
-            playurl = "%s/emby/Audio/%s/stream" % (server, itemid)
-        else:
-            playurl = "%s/emby/Videos/%s/stream?static=true" % (server, itemid)
-
-        return playurl
-
-    def isDirectPlay(self):
-
-        log = self.logMsg
-        lang = utils.language
-        settings = utils.settings
-        dialog = xbmcgui.Dialog()
-
-        item = self.item
-
-        # Requirement: Filesystem, Accessible path
-        if settings('playFromStream') == "true":
-            # User forcing to play via HTTP
-            log("Can't direct play, play from HTTP enabled.", 1)
-            return False
-
-        videotrack = item['MediaSources'][0]['Name']
-        transcodeH265 = settings('transcodeH265')
-
-        if transcodeH265 in ("1", "2", "3") and ("HEVC" in videotrack or "H265" in videotrack):
-            # Avoid H265/HEVC depending on the resolution
-            resolution = int(videotrack.split("P", 1)[0])
-            res = {
-
-                '1': 480,
-                '2': 720,
-                '3': 1080
-            }
-            log("Resolution is: %sP, transcode for resolution: %sP+"
-                % (resolution, res[transcodeH265]), 1)
-            if res[transcodeH265] <= resolution:
-                return False
-
-        canDirectPlay = item['MediaSources'][0]['SupportsDirectPlay']
-        # Make sure direct play is supported by the server
-        if not canDirectPlay:
-            log("Can't direct play, server doesn't allow/support it.", 1)
-            return False
-
-        location = item['LocationType']
-        if location == "FileSystem":
-            # Verify the path
-            if not self.fileExists():
-                log("Unable to direct play.")
-                try:
-                    count = int(settings('failCount'))
-                except ValueError:
-                    count = 0
-                log("Direct play failed: %s times." % count, 1)
-
-                if count < 2:
-                    # Let the user know that direct play failed
-                    settings('failCount', value=str(count+1))
-                    dialog.notification(
-                                heading="Emby for Kodi",
-                                message=lang(33011),
-                                icon="special://home/addons/plugin.video.emby/icon.png",
-                                sound=False)
-                elif settings('playFromStream') != "true":
-                    # Permanently set direct stream as true
-                    settings('playFromStream', value="true")
-                    settings('failCount', value="0")
-                    dialog.notification(
-                                heading="Emby for Kodi",
-                                message=lang(33012),
-                                icon="special://home/addons/plugin.video.emby/icon.png",
-                                sound=False)
-                return False
-
-        return True
-
-    def directPlay(self):
-
-        item = self.item
-
-        try:
-            playurl = item['MediaSources'][0]['Path']
-        except (IndexError, KeyError):
-            playurl = item['Path']
-
-        if item.get('VideoType'):
-            # Specific format modification
-            type = item['VideoType']
-
-            if type == "Dvd":
-                playurl = "%s/VIDEO_TS/VIDEO_TS.IFO" % playurl
-            elif type == "BluRay":
-                playurl = "%s/BDMV/index.bdmv" % playurl
-
-        # Assign network protocol
-        if playurl.startswith('\\\\'):
-            playurl = playurl.replace("\\\\", "smb://")
-            playurl = playurl.replace("\\", "/")
-
-        if "apple.com" in playurl:
-            USER_AGENT = "QuickTime/7.7.4"
-            playurl += "?|User-Agent=%s" % USER_AGENT
-
-        return playurl
-
-    def fileExists(self):
-
-        log = self.logMsg
-
-        if 'Path' not in self.item:
-            # File has no path defined in server
-            return False
-
-        # Convert path to direct play
-        path = self.directPlay()
-        log("Verifying path: %s" % path, 1)
-
-        if xbmcvfs.exists(path):
-            log("Path exists.", 1)
-            return True
-
-        elif ":" not in path:
-            log("Can't verify path, assumed linux. Still try to direct play.", 1)
-            return True
-
-        else:
-            log("Failed to find file.", 1)
-            return False
-
-    def isDirectStream(self):
-
-        log = self.logMsg
-
-        item = self.item
-
-        videotrack = item['MediaSources'][0]['Name']
-        transcodeH265 = utils.settings('transcodeH265')
-
-        if transcodeH265 in ("1", "2", "3") and ("HEVC" in videotrack or "H265" in videotrack):
-            # Avoid H265/HEVC depending on the resolution
-            resolution = int(videotrack.split("P", 1)[0])
-            res = {
-
-                '1': 480,
-                '2': 720,
-                '3': 1080
-            }
-            log("Resolution is: %sP, transcode for resolution: %sP+"
-                % (resolution, res[transcodeH265]), 1)
-            if res[transcodeH265] <= resolution:
-                return False
-
-        # Requirement: BitRate, supported encoding
-        canDirectStream = item['MediaSources'][0]['SupportsDirectStream']
-        # Make sure the server supports it
-        if not canDirectStream:
-            return False
-
-        # Verify the bitrate
-        if not self.isNetworkSufficient():
-            log("The network speed is insufficient to direct stream file.", 1)
-            return False
-
-        return True
-
-    def directStream(self):
-
-        item = self.item
-        server = self.server
-
-        itemid = item['Id']
-        itemtype = item['Type']
-
-        if 'Path' in item and item['Path'].endswith('.strm'):
-            # Allow strm loading when direct streaming
-            playurl = self.directPlay()
-        elif itemtype == "Audio":
-            playurl = "%s/emby/Audio/%s/stream.mp3" % (server, itemid)
-        else:
-            playurl = "%s/emby/Videos/%s/stream?static=true" % (server, itemid)
-
-        return playurl
-
-    def isNetworkSufficient(self):
-
-        log = self.logMsg
-
-        settings = self.getBitrate()*1000
-
-        try:
-            sourceBitrate = int(self.item['MediaSources'][0]['Bitrate'])
-        except (KeyError, TypeError):
-            log("Bitrate value is missing.", 1)
-        else:
-            log("The add-on settings bitrate is: %s, the video bitrate required is: %s"
-                % (settings, sourceBitrate), 1)
-            if settings < sourceBitrate:
-                return False
-
-        return True
-
-    def isTranscoding(self):
-
-        item = self.item
-
-        canTranscode = item['MediaSources'][0]['SupportsTranscoding']
-        # Make sure the server supports it
-        if not canTranscode:
-            return False
-
-        return True
-
-    def transcoding(self):
-
-        item = self.item
-
-        if 'Path' in item and item['Path'].endswith('.strm'):
-            # Allow strm loading when transcoding
-            playurl = self.directPlay()
-        else:
-            itemid = item['Id']
-            deviceId = self.clientInfo.getDeviceId()
-            playurl = (
-                "%s/emby/Videos/%s/master.m3u8?MediaSourceId=%s"
-                % (self.server, itemid, itemid)
-            )
-            playurl = (
-                "%s&VideoCodec=h264&AudioCodec=ac3&MaxAudioChannels=6&deviceId=%s&VideoBitrate=%s"
-                % (playurl, deviceId, self.getBitrate()*1000))
-
-        return playurl
-
-    def getBitrate(self):
-
-        # get the addon video quality
-        videoQuality = utils.settings('videoBitrate')
-        bitrate = {
-
-            '0': 664,
-            '1': 996,
-            '2': 1320,
-            '3': 2000,
-            '4': 3200,
-            '5': 4700,
-            '6': 6200,
-            '7': 7700,
-            '8': 9200,
-            '9': 10700,
-            '10': 12200,
-            '11': 13700,
-            '12': 15200,
-            '13': 16700,
-            '14': 18200,
-            '15': 20000,
-            '16': 40000,
-            '17': 100000,
-            '18': 1000000
-        }
-
-        # max bit rate supported by server (max signed 32bit integer)
-        return bitrate.get(videoQuality, 2147483)
-
-    def audioSubsPref(self, url, listitem):
-
-        log = self.logMsg
-        lang = utils.language
-        dialog = xbmcgui.Dialog()
-        # For transcoding only
-        # Present the list of audio to select from
-        audioStreamsList = {}
-        audioStreams = []
-        audioStreamsChannelsList = {}
-        subtitleStreamsList = {}
-        subtitleStreams = ['No subtitles']
-        downloadableStreams = []
-        selectAudioIndex = ""
-        selectSubsIndex = ""
-        playurlprefs = "%s" % url
-
-        item = self.item
-        try:
-            mediasources = item['MediaSources'][0]
-            mediastreams = mediasources['MediaStreams']
-        except (TypeError, KeyError, IndexError):
-            return
-
-        for stream in mediastreams:
-            # Since Emby returns all possible tracks together, have to sort them.
-            index = stream['Index']
-            type = stream['Type']
-
-            if 'Audio' in type:
-                codec = stream['Codec']
-                channelLayout = stream.get('ChannelLayout', "")
-               
-                try:
-                    track = "%s - %s - %s %s" % (index, stream['Language'], codec, channelLayout)
-                except:
-                    track = "%s - %s %s" % (index, codec, channelLayout)
-                
-                audioStreamsChannelsList[index] = stream['Channels']
-                audioStreamsList[track] = index
-                audioStreams.append(track)
-
-            elif 'Subtitle' in type:
-                try:
-                    track = "%s - %s" % (index, stream['Language'])
-                except:
-                    track = "%s - %s" % (index, stream['Codec'])
-
-                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)
-
-
-        if len(audioStreams) > 1:
-            resp = dialog.select(lang(33013), audioStreams)
-            if resp > -1:
-                # User selected audio
-                selected = audioStreams[resp]
-                selectAudioIndex = audioStreamsList[selected]
-                playurlprefs += "&AudioStreamIndex=%s" % selectAudioIndex
-            else: # User backed out of selection
-                playurlprefs += "&AudioStreamIndex=%s" % mediasources['DefaultAudioStreamIndex']
-        else: # There's only one audiotrack.
-            selectAudioIndex = audioStreamsList[audioStreams[0]]
-            playurlprefs += "&AudioStreamIndex=%s" % selectAudioIndex
-
-        if len(subtitleStreams) > 1:
-            resp = dialog.select(lang(33014), subtitleStreams)
-            if resp == 0:
-                # User selected no subtitles
-                pass
-            elif resp > -1:
-                # User selected subtitles
-                selected = subtitleStreams[resp]
-                selectSubsIndex = subtitleStreamsList[selected]
-
-                # 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))]
-                    log("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', "")
-
-        # Get number of channels for selected audio track
-        audioChannels = audioStreamsChannelsList.get(selectAudioIndex, 0)
-        if audioChannels > 2:
-            playurlprefs += "&AudioBitrate=384000"
-        else:
-            playurlprefs += "&AudioBitrate=192000"
-
+# -*- coding: utf-8 -*-
+
+#################################################################################################
+
+import xbmc
+import xbmcgui
+import xbmcvfs
+
+import clientinfo
+import utils
+
+#################################################################################################
+
+
+class PlayUtils():
+    
+    
+    def __init__(self, item):
+
+        self.item = item
+
+        self.clientInfo = clientinfo.ClientInfo()
+        self.addonName = self.clientInfo.getAddonName()
+
+        self.userid = utils.window('emby_currUser')
+        self.server = utils.window('emby_server%s' % self.userid)
+
+    def logMsg(self, msg, lvl=1):
+
+        self.className = self.__class__.__name__
+        utils.logMsg("%s %s" % (self.addonName, self.className), msg, lvl)
+    
+
+    def getPlayUrl(self):
+
+        window = utils.window
+
+        playurl = None
+        
+        if (self.item.get('Type') in ("Recording", "TvChannel") and
+                self.item.get('MediaSources') and self.item['MediaSources'][0]['Protocol'] == "Http"):
+            # Play LiveTV or recordings
+            self.logMsg("File protocol is http (livetv).", 1)
+            playurl = "%s/emby/Videos/%s/live.m3u8?static=true" % (self.server, self.item['Id'])
+            window('emby_%s.playmethod' % playurl, value="Transcode")
+
+        elif self.item.get('MediaSources') and self.item['MediaSources'][0]['Protocol'] == "Http":
+            # Only play as http, used for channels, or online hosting of content
+            self.logMsg("File protocol is http.", 1)
+            playurl = self.httpPlay()
+            window('emby_%s.playmethod' % playurl, value="DirectStream")
+
+        elif self.isDirectPlay():
+
+            self.logMsg("File is direct playing.", 1)
+            playurl = self.directPlay()
+            playurl = playurl.encode('utf-8')
+            # Set playmethod property
+            window('emby_%s.playmethod' % playurl, value="DirectPlay")
+
+        elif self.isDirectStream():
+            
+            self.logMsg("File is direct streaming.", 1)
+            playurl = self.directStream()
+            # Set playmethod property
+            window('emby_%s.playmethod' % playurl, value="DirectStream")
+
+        elif self.isTranscoding():
+            
+            self.logMsg("File is transcoding.", 1)
+            playurl = self.transcoding()
+            # Set playmethod property
+            window('emby_%s.playmethod' % playurl, value="Transcode")
+
+        return playurl
+
+    def httpPlay(self):
+        # Audio, Video, Photo
+
+        itemid = self.item['Id']
+        mediatype = self.item['MediaType']
+
+        if mediatype == "Audio":
+            playurl = "%s/emby/Audio/%s/stream" % (self.server, itemid)
+        else:
+            playurl = "%s/emby/Videos/%s/stream?static=true" % (self.server, itemid)
+
+        return playurl
+
+    def isDirectPlay(self):
+
+        lang = utils.language
+        settings = utils.settings
+        dialog = xbmcgui.Dialog()
+
+
+        # Requirement: Filesystem, Accessible path
+        if settings('playFromStream') == "true":
+            # User forcing to play via HTTP
+            self.logMsg("Can't direct play, play from HTTP enabled.", 1)
+            return False
+
+        videotrack = self.item['MediaSources'][0]['Name']
+        transcodeH265 = settings('transcodeH265')
+
+        if transcodeH265 in ("1", "2", "3") and ("HEVC" in videotrack or "H265" in videotrack):
+            # Avoid H265/HEVC depending on the resolution
+            resolution = int(videotrack.split("P", 1)[0])
+            res = {
+
+                '1': 480,
+                '2': 720,
+                '3': 1080
+            }
+            self.logMsg("Resolution is: %sP, transcode for resolution: %sP+"
+                % (resolution, res[transcodeH265]), 1)
+            if res[transcodeH265] <= resolution:
+                return False
+
+        canDirectPlay = self.item['MediaSources'][0]['SupportsDirectPlay']
+        # Make sure direct play is supported by the server
+        if not canDirectPlay:
+            self.logMsg("Can't direct play, server doesn't allow/support it.", 1)
+            return False
+
+        location = self.item['LocationType']
+        if location == "FileSystem":
+            # Verify the path
+            if not self.fileExists():
+                self.logMsg("Unable to direct play.")
+                try:
+                    count = int(settings('failCount'))
+                except ValueError:
+                    count = 0
+                self.logMsg("Direct play failed: %s times." % count, 1)
+
+                if count < 2:
+                    # Let the user know that direct play failed
+                    settings('failCount', value=str(count+1))
+                    dialog.notification(
+                                heading="Emby for Kodi",
+                                message=lang(33011),
+                                icon="special://home/addons/plugin.video.emby/icon.png",
+                                sound=False)
+                elif settings('playFromStream') != "true":
+                    # Permanently set direct stream as true
+                    settings('playFromStream', value="true")
+                    settings('failCount', value="0")
+                    dialog.notification(
+                                heading="Emby for Kodi",
+                                message=lang(33012),
+                                icon="special://home/addons/plugin.video.emby/icon.png",
+                                sound=False)
+                return False
+
+        return True
+
+    def directPlay(self):
+
+        try:
+            playurl = self.item['MediaSources'][0]['Path']
+        except (IndexError, KeyError):
+            playurl = self.item['Path']
+
+        if self.item.get('VideoType'):
+            # Specific format modification
+            if self.item['VideoType'] == "Dvd":
+                playurl = "%s/VIDEO_TS/VIDEO_TS.IFO" % playurl
+            elif self.item['VideoType'] == "BluRay":
+                playurl = "%s/BDMV/index.bdmv" % playurl
+
+        # Assign network protocol
+        if playurl.startswith('\\\\'):
+            playurl = playurl.replace("\\\\", "smb://")
+            playurl = playurl.replace("\\", "/")
+
+        if "apple.com" in playurl:
+            USER_AGENT = "QuickTime/7.7.4"
+            playurl += "?|User-Agent=%s" % USER_AGENT
+
+        return playurl
+
+    def fileExists(self):
+
+        if 'Path' not in self.item:
+            # File has no path defined in server
+            return False
+
+        # Convert path to direct play
+        path = self.directPlay()
+        self.logMsg("Verifying path: %s" % path, 1)
+
+        if xbmcvfs.exists(path):
+            self.logMsg("Path exists.", 1)
+            return True
+
+        elif ":" not in path:
+            self.logMsg("Can't verify path, assumed linux. Still try to direct play.", 1)
+            return True
+
+        else:
+            self.logMsg("Failed to find file.", 1)
+            return False
+
+    def isDirectStream(self):
+
+
+        videotrack = self.item['MediaSources'][0]['Name']
+        transcodeH265 = utils.settings('transcodeH265')
+
+        if transcodeH265 in ("1", "2", "3") and ("HEVC" in videotrack or "H265" in videotrack):
+            # Avoid H265/HEVC depending on the resolution
+            resolution = int(videotrack.split("P", 1)[0])
+            res = {
+
+                '1': 480,
+                '2': 720,
+                '3': 1080
+            }
+            self.logMsg("Resolution is: %sP, transcode for resolution: %sP+"
+                % (resolution, res[transcodeH265]), 1)
+            if res[transcodeH265] <= resolution:
+                return False
+
+        # Requirement: BitRate, supported encoding
+        canDirectStream = self.item['MediaSources'][0]['SupportsDirectStream']
+        # Make sure the server supports it
+        if not canDirectStream:
+            return False
+
+        # Verify the bitrate
+        if not self.isNetworkSufficient():
+            self.logMsg("The network speed is insufficient to direct stream file.", 1)
+            return False
+
+        return True
+
+    def directStream(self):
+
+        if 'Path' in self.item and self.item['Path'].endswith('.strm'):
+            # Allow strm loading when direct streaming
+            playurl = self.directPlay()
+        elif self.item['Type'] == "Audio":
+            playurl = "%s/emby/Audio/%s/stream.mp3" % (self.server, self.item['Id'])
+        else:
+            playurl = "%s/emby/Videos/%s/stream?static=true" % (self.server, self.item['Id'])
+
+        return playurl
+
+    def isNetworkSufficient(self):
+
+
+        settings = self.getBitrate()*1000
+
+        try:
+            sourceBitrate = int(self.item['MediaSources'][0]['Bitrate'])
+        except (KeyError, TypeError):
+            self.logMsg("Bitrate value is missing.", 1)
+        else:
+            self.logMsg("The add-on settings bitrate is: %s, the video bitrate required is: %s"
+                % (settings, sourceBitrate), 1)
+            if settings < sourceBitrate:
+                return False
+
+        return True
+
+    def isTranscoding(self):
+        # Make sure the server supports it
+        if not self.item['MediaSources'][0]['SupportsTranscoding']:
+            return False
+
+        return True
+
+    def transcoding(self):
+
+        if 'Path' in self.item and self.item['Path'].endswith('.strm'):
+            # Allow strm loading when transcoding
+            playurl = self.directPlay()
+        else:
+            itemid = self.item['Id']
+            deviceId = self.clientInfo.getDeviceId()
+            playurl = (
+                "%s/emby/Videos/%s/master.m3u8?MediaSourceId=%s"
+                % (self.server, itemid, itemid)
+            )
+            playurl = (
+                "%s&VideoCodec=h264&AudioCodec=ac3&MaxAudioChannels=6&deviceId=%s&VideoBitrate=%s"
+                % (playurl, deviceId, self.getBitrate()*1000))
+
+        return playurl
+
+    def getBitrate(self):
+
+        # get the addon video quality
+        bitrate = {
+
+            '0': 664,
+            '1': 996,
+            '2': 1320,
+            '3': 2000,
+            '4': 3200,
+            '5': 4700,
+            '6': 6200,
+            '7': 7700,
+            '8': 9200,
+            '9': 10700,
+            '10': 12200,
+            '11': 13700,
+            '12': 15200,
+            '13': 16700,
+            '14': 18200,
+            '15': 20000,
+            '16': 40000,
+            '17': 100000,
+            '18': 1000000
+        }
+
+        # max bit rate supported by server (max signed 32bit integer)
+        return bitrate.get(utils.settings('videoBitrate'), 2147483)
+
+    def audioSubsPref(self, url, listitem):
+
+        lang = utils.language
+        dialog = xbmcgui.Dialog()
+        # For transcoding only
+        # Present the list of audio to select from
+        audioStreamsList = {}
+        audioStreams = []
+        audioStreamsChannelsList = {}
+        subtitleStreamsList = {}
+        subtitleStreams = ['No subtitles']
+        downloadableStreams = []
+        selectAudioIndex = ""
+        selectSubsIndex = ""
+        playurlprefs = "%s" % url
+
+        try:
+            mediasources = self.item['MediaSources'][0]
+            mediastreams = mediasources['MediaStreams']
+        except (TypeError, KeyError, IndexError):
+            return
+
+        for stream in mediastreams:
+            # Since Emby returns all possible tracks together, have to sort them.
+            index = stream['Index']
+
+            if 'Audio' in stream['Type']:
+                codec = stream['Codec']
+                channelLayout = stream.get('ChannelLayout', "")
+               
+                try:
+                    track = "%s - %s - %s %s" % (index, stream['Language'], codec, channelLayout)
+                except:
+                    track = "%s - %s %s" % (index, codec, channelLayout)
+                
+                audioStreamsChannelsList[index] = stream['Channels']
+                audioStreamsList[track] = index
+                audioStreams.append(track)
+
+            elif 'Subtitle' in stream['Type']:
+                try:
+                    track = "%s - %s" % (index, stream['Language'])
+                except:
+                    track = "%s - %s" % (index, stream['Codec'])
+
+                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)
+
+
+        if len(audioStreams) > 1:
+            resp = dialog.select(lang(33013), audioStreams)
+            if resp > -1:
+                # User selected audio
+                selected = audioStreams[resp]
+                selectAudioIndex = audioStreamsList[selected]
+                playurlprefs += "&AudioStreamIndex=%s" % selectAudioIndex
+            else: # User backed out of selection
+                playurlprefs += "&AudioStreamIndex=%s" % mediasources['DefaultAudioStreamIndex']
+        else: # There's only one audiotrack.
+            selectAudioIndex = audioStreamsList[audioStreams[0]]
+            playurlprefs += "&AudioStreamIndex=%s" % selectAudioIndex
+
+        if len(subtitleStreams) > 1:
+            resp = dialog.select(lang(33014), subtitleStreams)
+            if resp == 0:
+                # User selected no subtitles
+                pass
+            elif resp > -1:
+                # User selected subtitles
+                selected = subtitleStreams[resp]
+                selectSubsIndex = subtitleStreamsList[selected]
+
+                # Load subtitles in the listitem if downloadable
+                if selectSubsIndex in downloadableStreams:
+
+                    itemid = self.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', "")
+
+        # Get number of channels for selected audio track
+        audioChannels = audioStreamsChannelsList.get(selectAudioIndex, 0)
+        if audioChannels > 2:
+            playurlprefs += "&AudioBitrate=384000"
+        else:
+            playurlprefs += "&AudioBitrate=192000"
+
         return playurlprefs
\ No newline at end of file
diff --git a/resources/lib/read_embyserver.py b/resources/lib/read_embyserver.py
index 13c976a1..6eeb5fb6 100644
--- a/resources/lib/read_embyserver.py
+++ b/resources/lib/read_embyserver.py
@@ -1,601 +1,538 @@
-# -*- coding: utf-8 -*-
-
-#################################################################################################
-
-import xbmc
-
-import utils
-import clientinfo
-import downloadutils
-
-#################################################################################################
-
-
-class Read_EmbyServer():
-
-    limitIndex = int(utils.settings('limitindex'))
-
-
-    def __init__(self):
-
-        window = utils.window
-
-        self.clientInfo = clientinfo.ClientInfo()
-        self.addonName = self.clientInfo.getAddonName()
-        self.doUtils = downloadutils.DownloadUtils().downloadUrl
-
-        self.userId = window('emby_currUser')
-        self.server = 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)
-
-
-    def split_list(self, itemlist, size):
-        # Split up list in pieces of size. Will generate a list of lists
-        return [itemlist[i:i+size] for i in range(0, len(itemlist), size)]
-
-
-    def getItem(self, itemid):
-        # This will return the full item
-        item = {}
-
-        url = "{server}/emby/Users/{UserId}/Items/%s?format=json" % itemid
-        result = self.doUtils(url)
-        if result:
-            item = result
-
-        return item
-
-    def getItems(self, itemlist):
-        
-        items = []
-
-        itemlists = self.split_list(itemlist, 50)
-        for itemlist in itemlists:
-            # Will return basic information
-            url = "{server}/emby/Users/{UserId}/Items?&format=json"
-            params = {
-
-                'Ids': ",".join(itemlist),
-                'Fields': "Etag"
-            }
-            result = self.doUtils(url, parameters=params)
-            if result:
-                items.extend(result['Items'])
-
-        return items
-
-    def getFullItems(self, itemlist):
-        
-        items = []
-
-        itemlists = self.split_list(itemlist, 50)
-        for itemlist in itemlists:
-
-            url = "{server}/emby/Users/{UserId}/Items?format=json"
-            params = {
-
-                "Ids": ",".join(itemlist),
-                "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,"
-                        "MediaSources"
-                )
-            }
-            result = self.doUtils(url, parameters=params)
-            if result:
-                items.extend(result['Items'])
-
-        return items
-
-    def getView_embyId(self, itemid):
-        # Returns ancestors using embyId
-        viewId = None
-        url = "{server}/emby/Items/%s/Ancestors?UserId={UserId}&format=json" % itemid
-        result = self.doUtils(url)
-
-        for view in result:
-
-            viewtype = view['Type']
-            if viewtype == "CollectionFolder":
-                # Found view
-                viewId = view['Id']
-
-        # Compare to view table in emby database
-        emby = utils.kodiSQL('emby')
-        cursor_emby = emby.cursor()
-        query = ' '.join((
-
-            "SELECT view_name, media_type",
-            "FROM view",
-            "WHERE view_id = ?"
-        ))
-        cursor_emby.execute(query, (viewId,))
-        result = cursor_emby.fetchone()
-        try:
-            viewName = result[0]
-            mediatype = result[1]
-        except TypeError:
-            viewName = None
-            mediatype = None
-
-        cursor_emby.close()
-
-        return [viewName, viewId, mediatype]
-    
-    def getFilteredSection(self, parentid, itemtype=None, sortby="SortName", recursive=True, limit=None, sortorder="Ascending", filter=""):
-        doUtils = self.doUtils
-        url = "{server}/emby/Users/{UserId}/Items?format=json"
-        params = {
-
-            'ParentId': parentid,
-            'IncludeItemTypes': itemtype,
-            'CollapseBoxSetItems': False,
-            'IsVirtualUnaired': False,
-            'IsMissing': False,
-            'Recursive': recursive,
-            'Limit': limit,
-            'SortBy': sortby,
-            'SortOrder': sortorder,
-            'Filters': filter,
-            '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(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(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(url, parameters=params)
-    
-    def getSection(self, parentid, itemtype=None, sortby="SortName", basic=False, dialog=None):
-
-        log = self.logMsg
-
-        doUtils = self.doUtils
-        items = {
-            
-            'Items': [],
-            'TotalRecordCount': 0
-        }
-
-        # Get total number of items
-        url = "{server}/emby/Users/{UserId}/Items?format=json"
-        params = {
-
-            'ParentId': parentid,
-            'IncludeItemTypes': itemtype,
-            'CollapseBoxSetItems': False,
-            'IsVirtualUnaired': False,
-            'IsMissing': False,
-            'Recursive': True,
-            'Limit': 1
-        }
-        result = doUtils(url, parameters=params)
-        try:
-            total = result['TotalRecordCount']
-            items['TotalRecordCount'] = total
-
-        except TypeError: # Failed to retrieve
-            log("%s:%s Failed to retrieve the server response." % (url, params), 2)
-
-        else:
-            index = 0
-            jump = self.limitIndex
-            throttled = False
-            highestjump = 0
-
-            while index < total:
-                # Get items by chunk to increase retrieval speed at scale
-                params = {
-
-                    'ParentId': parentid,
-                    'IncludeItemTypes': itemtype,
-                    'CollapseBoxSetItems': False,
-                    'IsVirtualUnaired': False,
-                    'IsMissing': False,
-                    'Recursive': True,
-                    'StartIndex': index,
-                    'Limit': jump,
-                    'SortBy': sortby,
-                    'SortOrder': "Ascending",
-                }
-                if basic:
-                    params['Fields'] = "Etag"
-                else:
-                    params['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,"
-                        "MediaSources"
-                    )
-                result = doUtils(url, parameters=params)
-                try:
-                    items['Items'].extend(result['Items'])
-                except TypeError:
-                    # Something happened to the connection
-                    if not throttled:
-                        throttled = True
-                        log("Throttle activated.", 1)
-                    
-                    if jump == highestjump:
-                        # We already tried with the highestjump, but it failed. Reset value.
-                        log("Reset highest value.", 1)
-                        highestjump = 0
-
-                    # Lower the number by half
-                    if highestjump:
-                        throttled = False
-                        jump = highestjump
-                        log("Throttle deactivated.", 1)
-                    else:
-                        jump = int(jump/4)
-                        log("Set jump limit to recover: %s" % jump, 2)
-                    
-                    retry = 0
-                    while utils.window('emby_online') != "true":
-                        # Wait server to come back online
-                        if retry == 5:
-                            log("Unable to reconnect to server. Abort process.", 1)
-                            return items
-                        
-                        retry += 1
-                        if xbmc.Monitor().waitForAbort(1):
-                            # Abort was requested while waiting.
-                            return items
-                else:
-                    # Request succeeded
-                    index += jump
-
-                    if dialog:
-                        percentage = int((float(index) / float(total))*100)
-                        dialog.update(percentage)
-
-                    if jump > highestjump:
-                        # Adjust with the latest number, if it's greater
-                        highestjump = jump
-
-                    if throttled:
-                        # We needed to adjust the number of item requested.
-                        # keep increasing until the connection times out again
-                        # to find the highest value
-                        increment = int(jump*0.33)
-                        if not increment: # Incase the increment is 0
-                            increment = 10
-
-                        jump += increment
-                        log("Increase jump limit to: %s" % jump, 1)
-        return items
-
-    def getViews(self, mediatype="", root=False, sortedlist=False):
-        # Build a list of user views
-        doUtils = self.doUtils
-        views = []
-        mediatype = mediatype.lower()
-
-        if not root:
-            url = "{server}/emby/Users/{UserId}/Views?format=json"
-        else: # Views ungrouped
-            url = "{server}/emby/Users/{UserId}/Items?Sortby=SortName&format=json"
-
-        result = doUtils(url)
-        try:
-            items = result['Items']
-        except TypeError:
-            self.logMsg("Error retrieving views for type: %s" % mediatype, 2)
-        else:
-            for item in items:
-
-                name = item['Name']
-                itemId = item['Id']
-                viewtype = item['Type']
-
-                if viewtype == "Channel":
-                    # Filter view types
-                    continue
-
-                # 3/4/2016 OriginalCollectionType is added
-                itemtype = item.get('OriginalCollectionType', item.get('CollectionType', "mixed"))
-
-                # 11/29/2015 Remove this once OriginalCollectionType is added to stable server.
-                # Assumed missing is mixed then.
-                '''if itemtype is None:
-                    url = "{server}/emby/Library/MediaFolders?format=json"
-                    result = doUtils(url)
-
-                    for folder in result['Items']:
-                        if itemId == folder['Id']:
-                            itemtype = folder.get('CollectionType', "mixed")'''
-                
-                if name not in ('Collections', 'Trailers'):
-                    
-                    if sortedlist:
-                        views.append({
-
-                            'name': name,
-                            'type': itemtype,
-                            'id': itemId
-                        })
-
-                    elif (itemtype == mediatype or 
-                        (itemtype == "mixed" and mediatype in ("movies", "tvshows"))):
-                    
-                        views.append({
-
-                            'name': name,
-                            'type': itemtype,
-                            'id': itemId
-                        })
-        
-        return views
-
-    def verifyView(self, parentid, itemid):
-
-        belongs = False
-
-        url = "{server}/emby/Users/{UserId}/Items?format=json"
-        params = {
-
-            'ParentId': parentid,
-            'CollapseBoxSetItems': False,
-            'IsVirtualUnaired': False,
-            'IsMissing': False,
-            'Recursive': True,
-            'Ids': itemid
-        }
-        result = self.doUtils(url, parameters=params)
-        try:
-            total = result['TotalRecordCount']
-        except TypeError:
-            # Something happened to the connection
-            pass
-        else:
-            if total:
-                belongs = True
-
-        return belongs
-
-    def getMovies(self, parentId, basic=False, dialog=None):
-
-        items = self.getSection(parentId, "Movie", basic=basic, dialog=dialog)
-        
-        return items
-
-    def getBoxset(self, dialog=None):
-
-        items = self.getSection(None, "BoxSet", dialog=dialog)
-
-        return items
-
-    def getMovies_byBoxset(self, boxsetid):
-
-        items = self.getSection(boxsetid, "Movie")
-
-        return items
-
-    def getMusicVideos(self, parentId, basic=False, dialog=None):
-
-        items = self.getSection(parentId, "MusicVideo", basic=basic, dialog=dialog)
-
-        return items
-
-    def getHomeVideos(self, parentId):
-
-        items = self.getSection(parentId, "Video")
-
-        return items
-
-    def getShows(self, parentId, basic=False, dialog=None):
-
-        items = self.getSection(parentId, "Series", basic=basic, dialog=dialog)
-
-        return items
-
-    def getSeasons(self, showId):
-
-        items = {
-            
-            'Items': [],
-            'TotalRecordCount': 0
-        }
-
-        url = "{server}/emby/Shows/%s/Seasons?UserId={UserId}&format=json" % showId
-        params = {
-
-            'IsVirtualUnaired': False,
-            'Fields': "Etag"
-        }
-        result = self.doUtils(url, parameters=params)
-        if result:
-            items = result
-
-        return items
-
-    def getEpisodes(self, parentId, basic=False, dialog=None):
-
-        items = self.getSection(parentId, "Episode", basic=basic, dialog=dialog)
-
-        return items
-
-    def getEpisodesbyShow(self, showId):
-
-        items = self.getSection(showId, "Episode")
-
-        return items
-
-    def getEpisodesbySeason(self, seasonId):
-
-        items = self.getSection(seasonId, "Episode")
-
-        return items
-
-    def getArtists(self, dialog=None):
-
-        doUtils = self.doUtils
-        items = {
-
-            'Items': [],
-            'TotalRecordCount': 0
-        }
-
-        # Get total number of items
-        url = "{server}/emby/Artists?UserId={UserId}&format=json"
-        params = {
-
-            'Recursive': True,
-            'Limit': 1
-        }
-        result = doUtils(url, parameters=params)
-        try:
-            total = result['TotalRecordCount']
-            items['TotalRecordCount'] = total
-
-        except TypeError: # Failed to retrieve
-            self.logMsg("%s:%s Failed to retrieve the server response." % (url, params), 2)
-
-        else:
-            index = 1
-            jump = self.limitIndex
-
-            while index < total:
-                # Get items by chunk to increase retrieval speed at scale
-                params = {
-
-                    'Recursive': True,
-                    'IsVirtualUnaired': False,
-                    'IsMissing': False,
-                    'StartIndex': index,
-                    'Limit': jump,
-                    'SortBy': "SortName",
-                    'SortOrder': "Ascending",
-                    'Fields': (
-
-                        "Etag,Genres,SortName,Studios,Writer,ProductionYear,"
-                        "CommunityRating,OfficialRating,CumulativeRunTimeTicks,Metascore,"
-                        "AirTime,DateCreated,MediaStreams,People,ProviderIds,Overview"
-                    )
-                }
-                result = doUtils(url, parameters=params)
-                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):
-
-        items = self.getSection(None, "MusicAlbum", sortby="DateCreated", basic=basic, dialog=dialog)
-
-        return items
-
-    def getAlbumsbyArtist(self, artistId):
-
-        items = self.getSection(artistId, "MusicAlbum", sortby="DateCreated")
-
-        return items
-
-    def getSongs(self, basic=False, dialog=None):
-
-        items = self.getSection(None, "Audio", basic=basic, dialog=dialog)
-
-        return items
-
-    def getSongsbyAlbum(self, albumId):
-
-        items = self.getSection(albumId, "Audio")
-
-        return items
-
-    def getAdditionalParts(self, itemId):
-
-        items = {
-            
-            'Items': [],
-            'TotalRecordCount': 0
-        }
-
-        url = "{server}/emby/Videos/%s/AdditionalParts?UserId={UserId}&format=json" % itemId
-        result = self.doUtils(url)
-        if result:
-            items = result
-
-        return items
-
-    def sortby_mediatype(self, itemids):
-
-        sorted_items = {}
-
-        # Sort items
-        items = self.getFullItems(itemids)
-        for item in items:
-
-            mediatype = item.get('Type')
-            if mediatype:
-                sorted_items.setdefault(mediatype, []).append(item)
-
-        return sorted_items
-
-    def updateUserRating(self, itemid, like=None, favourite=None, deletelike=False):
-        # Updates the user rating to Emby
-        doUtils = self.doUtils
-        
-        if favourite:
-            url = "{server}/emby/Users/{UserId}/FavoriteItems/%s?format=json" % itemid
-            doUtils(url, type="POST")
-        elif favourite == False:
-            url = "{server}/emby/Users/{UserId}/FavoriteItems/%s?format=json" % itemid
-            doUtils(url, type="DELETE")
-
-        if not deletelike and like:
-            url = "{server}/emby/Users/{UserId}/Items/%s/Rating?Likes=true&format=json" % itemid
-            doUtils(url, type="POST")
-        elif not deletelike and like == False:
-            url = "{server}/emby/Users/{UserId}/Items/%s/Rating?Likes=false&format=json" % itemid
-            doUtil(url, type="POST")
-        elif deletelike:
-            url = "{server}/emby/Users/{UserId}/Items/%s/Rating?format=json" % itemid
-            doUtils(url, type="DELETE")
-
-        self.logMsg("Update user rating to emby for itemid: %s "
-                    "| like: %s | favourite: %s | deletelike: %s"
+# -*- coding: utf-8 -*-
+
+#################################################################################################
+
+import xbmc
+
+import utils
+import clientinfo
+import downloadutils
+
+#################################################################################################
+
+
+class Read_EmbyServer():
+
+    limitIndex = int(utils.settings('limitindex'))
+
+
+    def __init__(self):
+
+        window = utils.window
+
+        self.clientInfo = clientinfo.ClientInfo()
+        self.addonName = self.clientInfo.getAddonName()
+        self.doUtils = downloadutils.DownloadUtils().downloadUrl
+
+        self.userId = window('emby_currUser')
+        self.server = 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)
+
+
+    def split_list(self, itemlist, size):
+        # Split up list in pieces of size. Will generate a list of lists
+        return [itemlist[i:i+size] for i in range(0, len(itemlist), size)]
+
+
+    def getItem(self, itemid):
+        # This will return the full item
+        item = {}
+
+        result = self.doUtils("{server}/metaman/Users/{UserId}/Items/%s?format=json" % itemid)
+        if result:
+            item = result
+
+        return item
+
+    def getItems(self, itemlist):
+        
+        items = []
+
+        itemlists = self.split_list(itemlist, 50)
+        for itemlist in itemlists:
+            # Will return basic information
+            params = {
+
+                'Ids': ",".join(itemlist),
+                'Fields': "Etag"
+            }
+            result = self.doUtils("{server}/emby/Users/{UserId}/Items?&format=json", parameters=params)
+            if result:
+                items.extend(result['Items'])
+
+        return items
+
+    def getFullItems(self, itemlist):
+        
+        items = []
+
+        itemlists = self.split_list(itemlist, 50)
+        for itemlist in itemlists:
+
+            params = {
+
+                "Ids": ",".join(itemlist),
+                "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,"
+                        "MediaSources"
+                )
+            }
+            result = self.doUtils("{server}/emby/Users/{UserId}/Items?format=json", parameters=params)
+            if result:
+                items.extend(result['Items'])
+
+        return items
+
+    def getView_embyId(self, itemid):
+        # Returns ancestors using embyId
+        viewId = None
+
+        for view in self.doUtils("{server}/emby/Items/%s/Ancestors?UserId={UserId}&format=json" % itemid):
+
+            if view['Type'] == "CollectionFolder":
+                # Found view
+                viewId = view['Id']
+
+        # Compare to view table in emby database
+        emby = utils.kodiSQL('emby')
+        cursor_emby = emby.cursor()
+        query = ' '.join((
+
+            "SELECT view_name, media_type",
+            "FROM view",
+            "WHERE view_id = ?"
+        ))
+        cursor_emby.execute(query, (viewId,))
+        result = cursor_emby.fetchone()
+        try:
+            viewName = result[0]
+            mediatype = result[1]
+        except TypeError:
+            viewName = None
+            mediatype = None
+
+        cursor_emby.close()
+
+        return [viewName, viewId, mediatype]
+    
+    def getFilteredSection(self, parentid, itemtype=None, sortby="SortName", recursive=True, limit=None, sortorder="Ascending", filter=""):
+        params = {
+
+            'ParentId': parentid,
+            'IncludeItemTypes': itemtype,
+            'CollapseBoxSetItems': False,
+            'IsVirtualUnaired': False,
+            'IsMissing': False,
+            'Recursive': recursive,
+            'Limit': limit,
+            'SortBy': sortby,
+            'SortOrder': sortorder,
+            'Filters': filter,
+            '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 self.doUtils("{server}/emby/Users/{UserId}/Items?format=json", parameters=params)
+    
+    def getTvChannels(self):
+        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 self.doUtils("{server}/emby/LiveTv/Channels/?userid={UserId}&format=json", parameters=params)
+    
+    def getTvRecordings(self, groupid):
+        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 self.doUtils("{server}/emby/LiveTv/Recordings/?userid={UserId}&format=json", parameters=params)
+    
+    def getSection(self, parentid, itemtype=None, sortby="SortName", basic=False, dialog=None):
+
+        items = {
+            
+            'Items': [],
+            'TotalRecordCount': 0
+        }
+
+        # Get total number of items
+        url = "{server}/emby/Users/{UserId}/Items?format=json"
+        params = {
+
+            'ParentId': parentid,
+            'IncludeItemTypes': itemtype,
+            'CollapseBoxSetItems': False,
+            'IsVirtualUnaired': False,
+            'IsMissing': False,
+            'Recursive': True,
+            'Limit': 1
+        }
+        result = self.doUtils(url, parameters=params)
+        try:
+            total = result['TotalRecordCount']
+            items['TotalRecordCount'] = total
+
+        except TypeError: # Failed to retrieve
+            self.logMsg("%s:%s Failed to retrieve the server response." % (url, params), 2)
+
+        else:
+            index = 0
+            jump = self.limitIndex
+            throttled = False
+            highestjump = 0
+
+            while index < total:
+                # Get items by chunk to increase retrieval speed at scale
+                params = {
+
+                    'ParentId': parentid,
+                    'IncludeItemTypes': itemtype,
+                    'CollapseBoxSetItems': False,
+                    'IsVirtualUnaired': False,
+                    'IsMissing': False,
+                    'Recursive': True,
+                    'StartIndex': index,
+                    'Limit': jump,
+                    'SortBy': sortby,
+                    'SortOrder': "Ascending",
+                }
+                if basic:
+                    params['Fields'] = "Etag"
+                else:
+                    params['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,"
+                        "MediaSources"
+                    )
+                result = self.doUtils(url, parameters=params)
+                try:
+                    items['Items'].extend(result['Items'])
+                except TypeError:
+                    # Something happened to the connection
+                    if not throttled:
+                        throttled = True
+                        self.logMsg("Throttle activated.", 1)
+                    
+                    if jump == highestjump:
+                        # We already tried with the highestjump, but it failed. Reset value.
+                        self.logMsg("Reset highest value.", 1)
+                        highestjump = 0
+
+                    # Lower the number by half
+                    if highestjump:
+                        throttled = False
+                        jump = highestjump
+                        self.logMsg("Throttle deactivated.", 1)
+                    else:
+                        jump = int(jump/4)
+                        self.logMsg("Set jump limit to recover: %s" % jump, 2)
+                    
+                    retry = 0
+                    while utils.window('emby_online') != "true":
+                        # Wait server to come back online
+                        if retry == 5:
+                            self.logMsg("Unable to reconnect to server. Abort process.", 1)
+                            return items
+                        
+                        retry += 1
+                        if xbmc.Monitor().waitForAbort(1):
+                            # Abort was requested while waiting.
+                            return items
+                else:
+                    # Request succeeded
+                    index += jump
+
+                    if dialog:
+                        percentage = int((float(index) / float(total))*100)
+                        dialog.update(percentage)
+
+                    if jump > highestjump:
+                        # Adjust with the latest number, if it's greater
+                        highestjump = jump
+
+                    if throttled:
+                        # We needed to adjust the number of item requested.
+                        # keep increasing until the connection times out again
+                        # to find the highest value
+                        increment = int(jump*0.33)
+                        if not increment: # Incase the increment is 0
+                            increment = 10
+
+                        jump += increment
+                        self.logMsg("Increase jump limit to: %s" % jump, 1)
+        return items
+
+    def getViews(self, mediatype="", root=False, sortedlist=False):
+        # Build a list of user views
+        views = []
+        mediatype = mediatype.lower()
+
+        if not root:
+            url = "{server}/emby/Users/{UserId}/Views?format=json"
+        else: # Views ungrouped
+            url = "{server}/emby/Users/{UserId}/Items?Sortby=SortName&format=json"
+
+        result = self.doUtils(url)
+        try:
+            items = result['Items']
+        except TypeError:
+            self.logMsg("Error retrieving views for type: %s" % mediatype, 2)
+        else:
+            for item in items:
+
+                item['Name'] = item['Name']
+                if item['Type'] == "Channel":
+                    # Filter view types
+                    continue
+
+                # 3/4/2016 OriginalCollectionType is added
+                itemtype = item.get('OriginalCollectionType', item.get('CollectionType', "mixed"))
+
+                # 11/29/2015 Remove this once OriginalCollectionType is added to stable server.
+                # Assumed missing is mixed then.
+                '''if itemtype is None:
+                    url = "{server}/emby/Library/MediaFolders?format=json"
+                    result = self.doUtils(url)
+
+                    for folder in result['Items']:
+                        if item['Id'] == folder['Id']:
+                            itemtype = folder.get('CollectionType', "mixed")'''
+                
+                if item['Name'] not in ('Collections', 'Trailers'):
+                    
+                    if sortedlist:
+                        views.append({
+
+                            'name': item['Name'],
+                            'type': itemtype,
+                            'id': item['Id']
+                        })
+
+                    elif (itemtype == mediatype or 
+                        (itemtype == "mixed" and mediatype in ("movies", "tvshows"))):
+                    
+                        views.append({
+
+                            'name': item['Name'],
+                            'type': itemtype,
+                            'id': item['Id']
+                        })
+        
+        return views
+
+    def verifyView(self, parentid, itemid):
+
+        belongs = False
+        params = {
+
+            'ParentId': parentid,
+            'CollapseBoxSetItems': False,
+            'IsVirtualUnaired': False,
+            'IsMissing': False,
+            'Recursive': True,
+            'Ids': itemid
+        }
+        result = self.doUtils("{server}/emby/Users/{UserId}/Items?format=json", parameters=params)
+        try:
+            total = result['TotalRecordCount']
+        except TypeError:
+            # Something happened to the connection
+            pass
+        else:
+            if total:
+                belongs = True
+
+        return belongs
+
+    def getMovies(self, parentId, basic=False, dialog=None):
+        return self.getSection(parentId, "Movie", basic=basic, dialog=dialog)
+
+    def getBoxset(self, dialog=None):
+        return self.getSection(None, "BoxSet", dialog=dialog)
+
+    def getMovies_byBoxset(self, boxsetid):
+        return self.getSection(boxsetid, "Movie")
+
+    def getMusicVideos(self, parentId, basic=False, dialog=None):
+        return self.getSection(parentId, "MusicVideo", basic=basic, dialog=dialog)
+
+    def getHomeVideos(self, parentId):
+
+        return self.getSection(parentId, "Video")
+
+    def getShows(self, parentId, basic=False, dialog=None):
+        return self.getSection(parentId, "Series", basic=basic, dialog=dialog)
+
+    def getSeasons(self, showId):
+
+        items = {
+            
+            'Items': [],
+            'TotalRecordCount': 0
+        }
+
+        params = {
+
+            'IsVirtualUnaired': False,
+            'Fields': "Etag"
+        }
+        result = self.doUtils("{server}/emby/Shows/%s/Seasons?UserId={UserId}&format=json" % showId, parameters=params)
+        if result:
+            items = result
+
+        return items
+
+    def getEpisodes(self, parentId, basic=False, dialog=None):
+
+        return self.getSection(parentId, "Episode", basic=basic, dialog=dialog)
+
+    def getEpisodesbyShow(self, showId):
+
+        return self.getSection(showId, "Episode")
+
+    def getEpisodesbySeason(self, seasonId):
+
+        return self.getSection(seasonId, "Episode")
+
+
+    def getArtists(self, dialog=None):
+
+        items = {
+
+            'Items': [],
+            'TotalRecordCount': 0
+        }
+
+        # Get total number of items
+        url = "{server}/emby/Artists?UserId={UserId}&format=json"
+        params = {
+
+            'Recursive': True,
+            'Limit': 1
+        }
+        result = self.doUtils(url, parameters=params)
+        try:
+            total = result['TotalRecordCount']
+            items['TotalRecordCount'] = total
+
+        except TypeError: # Failed to retrieve
+            self.logMsg("%s:%s Failed to retrieve the server response." % (url, params), 2)
+
+        else:
+            index = 1
+            jump = self.limitIndex
+
+            while index < total:
+                # Get items by chunk to increase retrieval speed at scale
+                params = {
+
+                    'Recursive': True,
+                    'IsVirtualUnaired': False,
+                    'IsMissing': False,
+                    'StartIndex': index,
+                    'Limit': jump,
+                    'SortBy': "SortName",
+                    'SortOrder': "Ascending",
+                    'Fields': (
+
+                        "Etag,Genres,SortName,Studios,Writer,ProductionYear,"
+                        "CommunityRating,OfficialRating,CumulativeRunTimeTicks,Metascore,"
+                        "AirTime,DateCreated,MediaStreams,People,ProviderIds,Overview"
+                    )
+                }
+                result = self.doUtils(url, parameters=params)
+                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):
+        return self.getSection(None, "MusicAlbum", sortby="DateCreated", basic=basic, dialog=dialog)
+
+    def getAlbumsbyArtist(self, artistId):
+        return self.getSection(artistId, "MusicAlbum", sortby="DateCreated")
+
+    def getSongs(self, basic=False, dialog=None):
+        return self.getSection(None, "Audio", basic=basic, dialog=dialog)
+
+    def getSongsbyAlbum(self, albumId):
+        return self.getSection(albumId, "Audio")
+
+
+    def getAdditionalParts(self, itemId):
+
+        items = {
+            
+            'Items': [],
+            'TotalRecordCount': 0
+        }
+
+        result = self.doUtils("{server}/emby/Videos/%s/AdditionalParts?UserId={UserId}&format=json" % itemId)
+        if result:
+            items = result
+
+        return items
+
+    def sortby_mediatype(self, itemids):
+
+        sorted_items = {}
+
+        # Sort items
+        items = self.getFullItems(itemids)
+        for item in items:
+
+            mediatype = item.get('Type')
+            if mediatype:
+                sorted_items.setdefault(mediatype, []).append(item)
+
+        return sorted_items
+
+    def updateUserRating(self, itemid, like=None, favourite=None, deletelike=False):
+        # Updates the user rating to Emby
+        
+        if favourite:
+            self.doUtils("{server}/emby/Users/{UserId}/FavoriteItems/%s?format=json" % itemid, action_type="POST")
+        elif favourite == False:
+            self.doUtils("{server}/emby/Users/{UserId}/FavoriteItems/%s?format=json" % itemid, action_type="DELETE")
+
+        if not deletelike and like:
+            self.doUtils("{server}/emby/Users/{UserId}/Items/%s/Rating?Likes=true&format=json" % itemid, action_type="POST")
+        elif not deletelike and like is False:
+            self.doUtils("{server}/emby/Users/{UserId}/Items/%s/Rating?Likes=false&format=json" % itemid, action_type="POST")
+        elif deletelike:
+            self.doUtils("{server}/emby/Users/{UserId}/Items/%s/Rating?format=json" % itemid, action_type="DELETE")
+
+        self.logMsg("Update user rating to emby for itemid: %s "
+                    "| like: %s | favourite: %s | deletelike: %s"
                     % (itemid, like, favourite, deletelike), 1)
\ No newline at end of file
diff --git a/resources/lib/userclient.py b/resources/lib/userclient.py
index ecda0a9c..f068b772 100644
--- a/resources/lib/userclient.py
+++ b/resources/lib/userclient.py
@@ -1,487 +1,468 @@
-# -*- coding: utf-8 -*-
-
-##################################################################################################
-
-import hashlib
-import threading
-
-import xbmc
-import xbmcgui
-import xbmcaddon
-import xbmcvfs
-
-import artwork
-import utils
-import clientinfo
-import downloadutils
-
-##################################################################################################
-
-
-class UserClient(threading.Thread):
-
-    # Borg - multiple instances, shared state
-    _shared_state = {}
-
-    stopClient = False
-    auth = True
-    retry = 0
-
-    currUser = None
-    currUserId = None
-    currServer = None
-    currToken = None
-    HasAccess = True
-    AdditionalUser = []
-
-    userSettings = None
-
-
-    def __init__(self):
-
-        self.__dict__ = self._shared_state
-        self.addon = xbmcaddon.Addon()
-
-        self.addonName = clientinfo.ClientInfo().getAddonName()
-        self.doUtils = downloadutils.DownloadUtils()
-        
-        threading.Thread.__init__(self)
-
-    def logMsg(self, msg, lvl=1):
-        
-        className = self.__class__.__name__
-        utils.logMsg("%s %s" % (self.addonName, className), msg, lvl)
-
-
-    def getAdditionalUsers(self):
-
-        additionalUsers = utils.settings('additionalUsers')
-        
-        if additionalUsers:
-            self.AdditionalUser = additionalUsers.split(',')
-
-    def getUsername(self):
-
-        username = utils.settings('username')
-
-        if not username:
-            self.logMsg("No username saved.", 2)
-            return ""
-
-        return username
-
-    def getLogLevel(self):
-
-        try:
-            logLevel = int(utils.settings('logLevel'))
-        except ValueError:
-            logLevel = 0
-        
-        return logLevel
-
-    def getUserId(self):
-
-        log = self.logMsg
-        window = utils.window
-        settings = utils.settings
-
-        username = self.getUsername()
-        w_userId = window('emby_currUser')
-        s_userId = settings('userId%s' % username)
-
-        # Verify the window property
-        if w_userId:
-            if not s_userId:
-                # Save access token if it's missing from settings
-                settings('userId%s' % username, value=w_userId)
-            log("Returning userId from WINDOW for username: %s UserId: %s"
-                % (username, w_userId), 2)
-            return w_userId
-        # Verify the settings
-        elif s_userId:
-            log("Returning userId from SETTINGS for username: %s userId: %s"
-                % (username, s_userId), 2)
-            return s_userId
-        # No userId found
-        else:
-            log("No userId saved for username: %s." % username, 1)
-
-    def getServer(self, prefix=True):
-
-        settings = utils.settings
-
-        alternate = settings('altip') == "true"
-        if alternate:
-            # Alternate host
-            HTTPS = settings('secondhttps') == "true"
-            host = settings('secondipaddress')
-            port = settings('secondport')
-        else:
-            # Original host
-            HTTPS = settings('https') == "true"
-            host = settings('ipaddress')
-            port = settings('port')
-
-        server = host + ":" + port
-        
-        if not host:
-            self.logMsg("No server information saved.", 2)
-            return False
-
-        # If https is true
-        if prefix and HTTPS:
-            server = "https://%s" % server
-            return server
-        # If https is false
-        elif prefix and not HTTPS:
-            server = "http://%s" % server
-            return server
-        # If only the host:port is required
-        elif not prefix:
-            return server
-
-    def getToken(self):
-
-        log = self.logMsg
-        window = utils.window
-        settings = utils.settings
-
-        username = self.getUsername()
-        userId = self.getUserId()
-        w_token = window('emby_accessToken%s' % userId)
-        s_token = settings('accessToken')
-        
-        # Verify the window property
-        if w_token:
-            if not s_token:
-                # Save access token if it's missing from settings
-                settings('accessToken', value=w_token)
-            log("Returning accessToken from WINDOW for username: %s accessToken: %s"
-                % (username, w_token), 2)
-            return w_token
-        # Verify the settings
-        elif s_token:
-            log("Returning accessToken from SETTINGS for username: %s accessToken: %s"
-                % (username, s_token), 2)
-            window('emby_accessToken%s' % username, value=s_token)
-            return s_token
-        else:
-            log("No token found.", 1)
-            return ""
-
-    def getSSLverify(self):
-        # Verify host certificate
-        settings = utils.settings
-
-        s_sslverify = settings('sslverify')
-        if settings('altip') == "true":
-            s_sslverify = settings('secondsslverify')
-
-        if s_sslverify == "true":
-            return True
-        else:
-            return False
-
-    def getSSL(self):
-        # Client side certificate
-        settings = utils.settings
-
-        s_cert = settings('sslcert')
-        if settings('altip') == "true":
-            s_cert = settings('secondsslcert')
-
-        if s_cert == "None":
-            return None
-        else:
-            return s_cert
-
-    def setUserPref(self):
-
-        doUtils = self.doUtils.downloadUrl
-        art = artwork.Artwork()
-
-        url = "{server}/emby/Users/{UserId}?format=json"
-        result = doUtils(url)
-        self.userSettings = result
-        # Set user image for skin display
-        if result.get('PrimaryImageTag'):
-            utils.window('EmbyUserImage', value=art.getUserArtwork(result['Id'], 'Primary'))
-
-        # Set resume point max
-        url = "{server}/emby/System/Configuration?format=json"
-        result = doUtils(url)
-
-        utils.settings('markPlayed', value=str(result['MaxResumePct']))
-
-    def getPublicUsers(self):
-
-        server = self.getServer()
-
-        # Get public Users
-        url = "%s/emby/Users/Public?format=json" % server
-        result = self.doUtils.downloadUrl(url, authenticate=False)
-        
-        if result != "":
-            return result
-        else:
-            # Server connection failed
-            return False
-
-    def hasAccess(self):
-        # hasAccess is verified in service.py
-        log = self.logMsg
-        window = utils.window
-
-        url = "{server}/emby/Users?format=json"
-        result = self.doUtils.downloadUrl(url)
-        
-        if result == False:
-            # Access is restricted, set in downloadutils.py via exception
-            log("Access is restricted.", 1)
-            self.HasAccess = False
-        
-        elif window('emby_online') != "true":
-            # Server connection failed
-            pass
-
-        elif window('emby_serverStatus') == "restricted":
-            log("Access is granted.", 1)
-            self.HasAccess = True
-            window('emby_serverStatus', clear=True)
-            xbmcgui.Dialog().notification("Emby for Kodi", utils.language(33007))
-
-    def loadCurrUser(self, authenticated=False):
-
-        window = utils.window
-
-        doUtils = self.doUtils
-        username = self.getUsername()
-        userId = self.getUserId()
-        
-        # Only to be used if token exists
-        self.currUserId = userId
-        self.currServer = self.getServer()
-        self.currToken = self.getToken()
-        self.ssl = self.getSSLverify()
-        self.sslcert = self.getSSL()
-
-        # Test the validity of current token
-        if authenticated == False:
-            url = "%s/emby/Users/%s?format=json" % (self.currServer, userId)
-            window('emby_currUser', value=userId)
-            window('emby_accessToken%s' % userId, value=self.currToken)
-            result = doUtils.downloadUrl(url)
-
-            if result == 401:
-                # Token is no longer valid
-                self.resetClient()
-                return False
-
-        # Set to windows property
-        window('emby_currUser', value=userId)
-        window('emby_accessToken%s' % userId, value=self.currToken)
-        window('emby_server%s' % userId, value=self.currServer)
-        window('emby_server_%s' % userId, value=self.getServer(prefix=False))
-
-        # Set DownloadUtils values
-        doUtils.setUsername(username)
-        doUtils.setUserId(self.currUserId)
-        doUtils.setServer(self.currServer)
-        doUtils.setToken(self.currToken)
-        doUtils.setSSL(self.ssl, self.sslcert)
-        # parental control - let's verify if access is restricted
-        self.hasAccess()
-        # Start DownloadUtils session
-        doUtils.startSession()
-        self.getAdditionalUsers()
-        # Set user preferences in settings
-        self.currUser = username
-        self.setUserPref()
-        
-
-    def authenticate(self):
-        
-        log = self.logMsg
-        lang = utils.language
-        window = utils.window
-        settings = utils.settings
-        dialog = xbmcgui.Dialog()
-
-        # Get /profile/addon_data
-        addondir = xbmc.translatePath(self.addon.getAddonInfo('profile')).decode('utf-8')
-        hasSettings = xbmcvfs.exists("%ssettings.xml" % addondir)
-
-        username = self.getUsername()
-        server = self.getServer()
-
-        # If there's no settings.xml
-        if not hasSettings:
-            log("No settings.xml found.", 1)
-            self.auth = False
-            return
-        # If no user information
-        elif not server or not username:
-            log("Missing server information.", 1)
-            self.auth = False
-            return
-        # If there's a token, load the user
-        elif self.getToken():
-            result = self.loadCurrUser()
-
-            if result == False:
-                pass
-            else:
-                log("Current user: %s" % self.currUser, 1)
-                log("Current userId: %s" % self.currUserId, 1)
-                log("Current accessToken: %s" % self.currToken, 2)
-                return
-        
-        ##### AUTHENTICATE USER #####
-
-        users = self.getPublicUsers()
-        password = ""
-        
-        # Find user in list
-        for user in users:
-            name = user['Name']
-
-            if username.decode('utf-8') in name:
-                # If user has password
-                if user['HasPassword'] == True:
-                    password = dialog.input(
-                        heading="%s %s" % (lang(33008), username.decode('utf-8')),
-                        option=xbmcgui.ALPHANUM_HIDE_INPUT)
-                    # If password dialog is cancelled
-                    if not password:
-                        log("No password entered.", 0)
-                        window('emby_serverStatus', value="Stop")
-                        self.auth = False
-                        return
-                break
-        else:
-            # Manual login, user is hidden
-            password = dialog.input(
-                            heading="%s %s" % (lang(33008), username),
-                            option=xbmcgui.ALPHANUM_HIDE_INPUT)
-        sha1 = hashlib.sha1(password)
-        sha1 = sha1.hexdigest()    
-
-        # Authenticate username and password
-        url = "%s/emby/Users/AuthenticateByName?format=json" % server
-        data = {'username': username, 'password': sha1}
-        log(data, 2)
-
-        result = self.doUtils.downloadUrl(url, postBody=data, type="POST", authenticate=False)
-
-        try:
-            log("Auth response: %s" % result, 1)
-            accessToken = result['AccessToken']
-        
-        except (KeyError, TypeError):
-            log("Failed to retrieve the api key.", 1)
-            accessToken = None
-
-        if accessToken is not None:
-            self.currUser = username
-            dialog.notification("Emby for Kodi",
-                                "%s %s!" % (lang(33000), self.currUser.decode('utf-8')))
-            userId = result['User']['Id']
-            settings('accessToken', value=accessToken)
-            settings('userId%s' % username, value=userId)
-            log("User Authenticated: %s" % accessToken, 1)
-            self.loadCurrUser(authenticated=True)
-            window('emby_serverStatus', clear=True)
-            self.retry = 0
-        else:
-            log("User authentication failed.", 1)
-            settings('accessToken', value="")
-            settings('userId%s' % username, value="")
-            dialog.ok(lang(33001), lang(33009))
-            
-            # Give two attempts at entering password
-            if self.retry == 2:
-                log("Too many retries. "
-                    "You can retry by resetting attempts in the addon settings.", 1)
-                window('emby_serverStatus', value="Stop")
-                dialog.ok(lang(33001), lang(33010))
-
-            self.retry += 1
-            self.auth = False
-
-    def resetClient(self):
-
-        log = self.logMsg
-
-        log("Reset UserClient authentication.", 1)
-        userId = self.getUserId()
-        
-        if self.currToken is not None:
-            # In case of 401, removed saved token
-            utils.settings('accessToken', value="")
-            utils.window('emby_accessToken%s' % userId, clear=True)
-            self.currToken = None
-            log("User token has been removed.", 1)
-        
-        self.auth = True
-        self.currUser = None
-        
-    def run(self):
-
-        log = self.logMsg
-        window = utils.window
-
-        monitor = xbmc.Monitor()
-        log("----===## Starting UserClient ##===----", 0)
-
-        while not monitor.abortRequested():
-
-            status = window('emby_serverStatus')
-            if status:
-                # Verify the connection status to server
-                if status == "restricted":
-                    # Parental control is restricting access
-                    self.HasAccess = False
-
-                elif status == "401":
-                    # Unauthorized access, revoke token
-                    window('emby_serverStatus', value="Auth")
-                    self.resetClient()
-
-            if self.auth and (self.currUser is None):
-                # Try to authenticate user
-                status = window('emby_serverStatus')
-                if not status or status == "Auth":
-                    # Set auth flag because we no longer need
-                    # to authenticate the user
-                    self.auth = False
-                    self.authenticate()
-                
-
-            if not self.auth and (self.currUser is None):
-                # If authenticate failed.
-                server = self.getServer()
-                username = self.getUsername()
-                status = window('emby_serverStatus')
-                
-                # The status Stop is for when user cancelled password dialog.
-                if server and username and status != "Stop":
-                    # Only if there's information found to login
-                    log("Server found: %s" % server, 2)
-                    log("Username found: %s" % username, 2)
-                    self.auth = True
-
-
-            if self.stopClient == True:
-                # If stopping the client didn't work
-                break
-                
-            if monitor.waitForAbort(1):
-                # Abort was requested while waiting. We should exit
-                break
-        
-        self.doUtils.stopSession()
-        log("##===---- UserClient Stopped ----===##", 0)
-
-    def stopClient(self):
-        # When emby for kodi terminates
+# -*- coding: utf-8 -*-
+
+##################################################################################################
+
+import hashlib
+import threading
+
+import xbmc
+import xbmcgui
+import xbmcaddon
+import xbmcvfs
+
+import artwork
+import utils
+import clientinfo
+import downloadutils
+
+##################################################################################################
+
+
+class UserClient(threading.Thread):
+
+    # Borg - multiple instances, shared state
+    _shared_state = {}
+
+    stopClient = False
+    auth = True
+    retry = 0
+
+    currUser = None
+    currUserId = None
+    currServer = None
+    currToken = None
+    HasAccess = True
+    AdditionalUser = []
+
+    userSettings = None
+
+
+    def __init__(self):
+
+        self.__dict__ = self._shared_state
+        self.addon = xbmcaddon.Addon()
+
+        self.addonName = clientinfo.ClientInfo().getAddonName()
+        self.doUtils = downloadutils.DownloadUtils()
+
+        threading.Thread.__init__(self)
+
+    def logMsg(self, msg, lvl=1):
+
+        className = self.__class__.__name__
+        utils.logMsg("%s %s" % (self.addonName, className), msg, lvl)
+
+
+    def getAdditionalUsers(self):
+
+        additionalUsers = utils.settings('additionalUsers')
+
+        if additionalUsers:
+            self.AdditionalUser = additionalUsers.split(',')
+
+    def getUsername(self):
+
+        username = utils.settings('username')
+
+        if not username:
+            self.logMsg("No username saved.", 2)
+            return ""
+
+        return username
+
+    def getLogLevel(self):
+
+        try:
+            logLevel = int(utils.settings('logLevel'))
+        except ValueError:
+            logLevel = 0
+
+        return logLevel
+
+    def getUserId(self):
+
+        window = utils.window
+        settings = utils.settings
+
+        username = self.getUsername()
+        w_userId = window('emby_currUser')
+        s_userId = settings('userId%s' % username)
+
+        # Verify the window property
+        if w_userId:
+            if not s_userId:
+                # Save access token if it's missing from settings
+                settings('userId%s' % username, value=w_userId)
+            self.logMsg("Returning userId from WINDOW for username: %s UserId: %s"
+                % (username, w_userId), 2)
+            return w_userId
+        # Verify the settings
+        elif s_userId:
+            self.logMsg("Returning userId from SETTINGS for username: %s userId: %s"
+                % (username, s_userId), 2)
+            return s_userId
+        # No userId found
+        else:
+            self.logMsg("No userId saved for username: %s." % username, 1)
+
+    def getServer(self, prefix=True):
+
+        settings = utils.settings
+
+        alternate = settings('altip') == "true"
+        if alternate:
+            # Alternate host
+            HTTPS = settings('secondhttps') == "true"
+            host = settings('secondipaddress')
+            port = settings('secondport')
+        else:
+            # Original host
+            HTTPS = settings('https') == "true"
+            host = settings('ipaddress')
+            port = settings('port')
+
+        server = host + ":" + port
+
+        if not host:
+            self.logMsg("No server information saved.", 2)
+            return False
+
+        # If https is true
+        if prefix and HTTPS:
+            server = "https://%s" % server
+            return server
+        # If https is false
+        elif prefix and not HTTPS:
+            server = "http://%s" % server
+            return server
+        # If only the host:port is required
+        elif not prefix:
+            return server
+
+    def getToken(self):
+
+        window = utils.window
+        settings = utils.settings
+
+        username = self.getUsername()
+        userId = self.getUserId()
+        w_token = window('emby_accessToken%s' % userId)
+        s_token = settings('accessToken')
+
+        # Verify the window property
+        if w_token:
+            if not s_token:
+                # Save access token if it's missing from settings
+                settings('accessToken', value=w_token)
+            self.logMsg("Returning accessToken from WINDOW for username: %s accessToken: %s"
+                % (username, w_token), 2)
+            return w_token
+        # Verify the settings
+        elif s_token:
+            self.logMsg("Returning accessToken from SETTINGS for username: %s accessToken: %s"
+                % (username, s_token), 2)
+            window('emby_accessToken%s' % username, value=s_token)
+            return s_token
+        else:
+            self.logMsg("No token found.", 1)
+            return ""
+
+    def getSSLverify(self):
+        # Verify host certificate
+        settings = utils.settings
+
+        s_sslverify = settings('sslverify')
+        if settings('altip') == "true":
+            s_sslverify = settings('secondsslverify')
+
+        if s_sslverify == "true":
+            return True
+        else:
+            return False
+
+    def getSSL(self):
+        # Client side certificate
+        settings = utils.settings
+
+        s_cert = settings('sslcert')
+        if settings('altip') == "true":
+            s_cert = settings('secondsslcert')
+
+        if s_cert == "None":
+            return None
+        else:
+            return s_cert
+
+    def setUserPref(self):
+
+        doUtils = self.doUtils.downloadUrl
+
+        result = doUtils("{server}/emby/Users/{UserId}?format=json")
+        self.userSettings = result
+        # Set user image for skin display
+        if result.get('PrimaryImageTag'):
+            utils.window('EmbyUserImage', value=artwork.Artwork().getUserArtwork(result['Id'], 'Primary'))
+
+        # Set resume point max
+        result = doUtils("{server}/emby/System/Configuration?format=json")
+
+        utils.settings('markPlayed', value=str(result['MaxResumePct']))
+
+    def getPublicUsers(self):
+        # Get public Users
+        result = self.doUtils.downloadUrl("%s/emby/Users/Public?format=json" % self.getServer(), authenticate=False)
+        if result != "":
+            return result
+        else:
+            # Server connection failed
+            return False
+
+
+    def hasAccess(self):
+        # hasAccess is verified in service.py
+        window = utils.window
+
+        result = self.doUtils.downloadUrl("{server}/emby/Users?format=json")
+
+        if result == False:
+            # Access is restricted, set in downloadutils.py via exception
+            self.logMsg("Access is restricted.", 1)
+            self.HasAccess = False
+
+        elif window('emby_online') != "true":
+            # Server connection failed
+            pass
+
+        elif window('emby_serverStatus') == "restricted":
+            self.logMsg("Access is granted.", 1)
+            self.HasAccess = True
+            window('emby_serverStatus', clear=True)
+            xbmcgui.Dialog().notification("Emby for Kodi", utils.language(33007))
+
+    def loadCurrUser(self, authenticated=False):
+
+        window = utils.window
+
+        doUtils = self.doUtils
+        username = self.getUsername()
+        userId = self.getUserId()
+
+        # Only to be used if token exists
+        self.currUserId = userId
+        self.currServer = self.getServer()
+        self.currToken = self.getToken()
+        self.ssl = self.getSSLverify()
+        self.sslcert = self.getSSL()
+
+        # Test the validity of current token
+        if authenticated == False:
+            url = "%s/emby/Users/%s?format=json" % (self.currServer, userId)
+            window('emby_currUser', value=userId)
+            window('emby_accessToken%s' % userId, value=self.currToken)
+            result = doUtils.downloadUrl(url)
+
+            if result == 401:
+                # Token is no longer valid
+                self.resetClient()
+                return False
+
+        # Set to windows property
+        window('emby_currUser', value=userId)
+        window('emby_accessToken%s' % userId, value=self.currToken)
+        window('emby_server%s' % userId, value=self.currServer)
+        window('emby_server_%s' % userId, value=self.getServer(prefix=False))
+
+        # Set DownloadUtils values
+        doUtils.setUsername(username)
+        doUtils.setUserId(self.currUserId)
+        doUtils.setServer(self.currServer)
+        doUtils.setToken(self.currToken)
+        doUtils.setSSL(self.ssl, self.sslcert)
+        # parental control - let's verify if access is restricted
+        self.hasAccess()
+        # Start DownloadUtils session
+        doUtils.startSession()
+        self.getAdditionalUsers()
+        # Set user preferences in settings
+        self.currUser = username
+        self.setUserPref()
+
+
+    def authenticate(self):
+
+        lang = utils.language
+        window = utils.window
+        settings = utils.settings
+        dialog = xbmcgui.Dialog()
+
+        # Get /profile/addon_data
+        addondir = xbmc.translatePath(self.addon.getAddonInfo('profile')).decode('utf-8')
+        hasSettings = xbmcvfs.exists("%ssettings.xml" % addondir)
+
+        username = self.getUsername()
+        server = self.getServer()
+
+        # If there's no settings.xml
+        if not hasSettings:
+            self.logMsg("No settings.xml found.", 1)
+            self.auth = False
+            return
+        # If no user information
+        elif not server or not username:
+            self.logMsg("Missing server information.", 1)
+            self.auth = False
+            return
+        # If there's a token, load the user
+        elif self.getToken():
+            result = self.loadCurrUser()
+
+            if result is False:
+                pass
+            else:
+                self.logMsg("Current user: %s" % self.currUser, 1)
+                self.logMsg("Current userId: %s" % self.currUserId, 1)
+                self.logMsg("Current accessToken: %s" % self.currToken, 2)
+                return
+
+        ##### AUTHENTICATE USER #####
+
+        users = self.getPublicUsers()
+        password = ""
+
+        # Find user in list
+        for user in users:
+            name = user['Name']
+
+            if username.decode('utf-8') in name:
+                # If user has password
+                if user['HasPassword'] == True:
+                    password = dialog.input(
+                        heading="%s %s" % (lang(33008), username.decode('utf-8')),
+                        option=xbmcgui.ALPHANUM_HIDE_INPUT)
+                    # If password dialog is cancelled
+                    if not password:
+                        self.logMsg("No password entered.", 0)
+                        window('emby_serverStatus', value="Stop")
+                        self.auth = False
+                        return
+                break
+        else:
+            # Manual login, user is hidden
+            password = dialog.input(
+                            heading="%s %s" % (lang(33008), username),
+                            option=xbmcgui.ALPHANUM_HIDE_INPUT)
+        sha1 = hashlib.sha1(password)
+        sha1 = sha1.hexdigest()
+
+        # Authenticate username and password
+        data = {'username': username, 'password': sha1}
+        self.logMsg(data, 2)
+
+        result = self.doUtils.downloadUrl("%s/emby/Users/AuthenticateByName?format=json" % server, postBody=data, action_type="POST", authenticate=False)
+
+        try:
+            self.logMsg("Auth response: %s" % result, 1)
+            accessToken = result['AccessToken']
+
+        except (KeyError, TypeError):
+            self.logMsg("Failed to retrieve the api key.", 1)
+            accessToken = None
+
+        if accessToken is not None:
+            self.currUser = username
+            dialog.notification("Emby for Kodi",
+                                "%s %s!" % (lang(33000), self.currUser.decode('utf-8')))
+            settings('accessToken', value=accessToken)
+            settings('userId%s' % username, value=result['User']['Id'])
+            self.logMsg("User Authenticated: %s" % accessToken, 1)
+            self.loadCurrUser(authenticated=True)
+            window('emby_serverStatus', clear=True)
+            self.retry = 0
+        else:
+            self.logMsg("User authentication failed.", 1)
+            settings('accessToken', value="")
+            settings('userId%s' % username, value="")
+            dialog.ok(lang(33001), lang(33009))
+
+            # Give two attempts at entering password
+            if self.retry == 2:
+                self.logMsg("Too many retries. "
+                    "You can retry by resetting attempts in the addon settings.", 1)
+                window('emby_serverStatus', value="Stop")
+                dialog.ok(lang(33001), lang(33010))
+
+            self.retry += 1
+            self.auth = False
+
+    def resetClient(self):
+
+        self.logMsg("Reset UserClient authentication.", 1)
+        if self.currToken is not None:
+            # In case of 401, removed saved token
+            utils.settings('accessToken', value="")
+            utils.window('emby_accessToken%s' % self.getUserId(), clear=True)
+            self.currToken = None
+            self.logMsg("User token has been removed.", 1)
+
+        self.auth = True
+        self.currUser = None
+
+    def run(self):
+
+        window = utils.window
+
+        monitor = xbmc.Monitor()
+        self.logMsg("----===## Starting UserClient ##===----", 0)
+
+        while not monitor.abortRequested():
+
+            status = window('emby_serverStatus')
+            if status:
+                # Verify the connection status to server
+                if status == "restricted":
+                    # Parental control is restricting access
+                    self.HasAccess = False
+
+                elif status == "401":
+                    # Unauthorized access, revoke token
+                    window('emby_serverStatus', value="Auth")
+                    self.resetClient()
+
+            if self.auth and (self.currUser is None):
+                # Try to authenticate user
+                status = window('emby_serverStatus')
+                if not status or status == "Auth":
+                    # Set auth flag because we no longer need
+                    # to authenticate the user
+                    self.auth = False
+                    self.authenticate()
+
+
+            if not self.auth and (self.currUser is None):
+                # If authenticate failed.
+                server = self.getServer()
+                username = self.getUsername()
+                status = window('emby_serverStatus')
+
+                # The status Stop is for when user cancelled password dialog.
+                if server and username and status != "Stop":
+                    # Only if there's information found to login
+                    self.logMsg("Server found: %s" % server, 2)
+                    self.logMsg("Username found: %s" % username, 2)
+                    self.auth = True
+
+
+            if self.stopClient == True:
+                # If stopping the client didn't work
+                break
+
+            if monitor.waitForAbort(1):
+                # Abort was requested while waiting. We should exit
+                break
+
+        self.doUtils.stopSession()
+        self.logMsg("##===---- UserClient Stopped ----===##", 0)
+
+    def stopClient(self):
+        # When emby for kodi terminates
         self.stopClient = True
\ No newline at end of file
diff --git a/resources/lib/utils.py b/resources/lib/utils.py
index c9b3474f..a8b97c0b 100644
--- a/resources/lib/utils.py
+++ b/resources/lib/utils.py
@@ -1,577 +1,564 @@
-# -*- coding: utf-8 -*-
-
-#################################################################################################
-
-import cProfile
-import inspect
-import json
-import pstats
-import sqlite3
-import StringIO
-import os
-from datetime import datetime, time
-import time
-import unicodedata
-import xml.etree.ElementTree as etree
-
-import xbmc
-import xbmcaddon
-import xbmcgui
-import xbmcvfs
-
-#################################################################################################
-
-
-def logMsg(title, msg, level=1):
-    
-    # Get the logLevel set in UserClient
-    try:
-        logLevel = int(window('emby_logLevel'))
-    except ValueError:
-        logLevel = 0
-    
-    if logLevel >= level:
-        
-        if logLevel == 2: # inspect.stack() is expensive
-            try:
-                xbmc.log("%s -> %s : %s" % (title, inspect.stack()[1][3], msg))
-            except UnicodeEncodeError:
-                xbmc.log("%s -> %s : %s" % (title, inspect.stack()[1][3], msg.encode('utf-8')))
-        else:
-            try:
-                xbmc.log("%s -> %s" % (title, msg))
-            except UnicodeEncodeError:
-                xbmc.log("%s -> %s" % (title, msg.encode('utf-8')))
-
-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:
-        WINDOW.setProperty(property, value)
-    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
-    addon = xbmcaddon.Addon(id='plugin.video.emby')
-    
-    if value is not None:
-        addon.setSetting(setting, value)
-    else:
-        return addon.getSetting(setting) #returns unicode object
-
-def language(stringid):
-    # Central string retrieval
-    addon = xbmcaddon.Addon(id='plugin.video.emby')
-    string = addon.getLocalizedString(stringid) #returns unicode object
-    return string
-
-def kodiSQL(type="video"):
-    
-    if type == "emby":
-        dbPath = xbmc.translatePath("special://database/emby.db").decode('utf-8')
-    elif type == "music":
-        dbPath = getKodiMusicDBPath()
-    elif type == "texture":
-        dbPath = xbmc.translatePath("special://database/Textures13.db").decode('utf-8')
-    else:
-        dbPath = getKodiVideoDBPath()
-    
-    connection = sqlite3.connect(dbPath)
-    return connection
-
-def getKodiVideoDBPath():
-
-    kodibuild = xbmc.getInfoLabel('System.BuildVersion')[:2]
-    dbVersion = {
-
-        "13": 78,   # Gotham
-        "14": 90,   # Helix
-        "15": 93,   # Isengard
-        "16": 99    # Jarvis
-    }
-
-    dbPath = xbmc.translatePath(
-                    "special://database/MyVideos%s.db"
-                    % dbVersion.get(kodibuild, "")).decode('utf-8')
-    return dbPath
-
-def getKodiMusicDBPath():
-
-    kodibuild = xbmc.getInfoLabel('System.BuildVersion')[:2]
-    dbVersion = {
-
-        "13": 46,   # Gotham
-        "14": 48,   # Helix
-        "15": 52,   # Isengard
-        "16": 56    # Jarvis
-    }
-
-    dbPath = xbmc.translatePath(
-                    "special://database/MyMusic%s.db"
-                    % dbVersion.get(kodibuild, "")).decode('utf-8')
-    return dbPath
-
-def getScreensaver():
-    # Get the current screensaver value
-    query = {
-
-        'jsonrpc': "2.0",
-        'id': 0,
-        'method': "Settings.getSettingValue",
-        'params': {
-
-            'setting': "screensaver.mode"
-        }
-    }
-    result = xbmc.executeJSONRPC(json.dumps(query))
-    result = json.loads(result)
-    screensaver = result['result']['value']
-
-    return screensaver
-
-def setScreensaver(value):
-    # Toggle the screensaver
-    query = {
-
-        'jsonrpc': "2.0",
-        'id': 0,
-        'method': "Settings.setSettingValue",
-        'params': {
-
-            'setting': "screensaver.mode",
-            'value': value
-        }
-    }
-    result = xbmc.executeJSONRPC(json.dumps(query))
-    logMsg("EMBY", "Toggling screensaver: %s %s" % (value, result), 1)    
-
-def reset():
-
-    dialog = xbmcgui.Dialog()
-
-    resp = dialog.yesno("Warning", "Are you sure you want to reset your local Kodi database?")
-    if resp == 0:
-        return
-
-    # first stop any db sync
-    window('emby_shouldStop', value="true")
-    count = 10
-    while window('emby_dbScan') == "true":
-        logMsg("EMBY", "Sync is running, will retry: %s..." % count)
-        count -= 1
-        if count == 0:
-            dialog.ok("Warning", "Could not stop the database from running. Try again.")
-            return
-        xbmc.sleep(1000)
-
-    # Clean up the playlists
-    deletePlaylists()
-
-    # Clean up the video nodes
-    deleteNodes()
-
-    # Wipe the kodi databases
-    logMsg("EMBY", "Resetting the Kodi video database.", 0)
-    connection = kodiSQL('video')
-    cursor = connection.cursor()
-    cursor.execute('SELECT tbl_name FROM sqlite_master WHERE type="table"')
-    rows = cursor.fetchall()
-    for row in rows:
-        tablename = row[0]
-        if tablename != "version":
-            cursor.execute("DELETE FROM " + tablename)
-    connection.commit()
-    cursor.close()
-
-    if settings('enableMusic') == "true":
-        logMsg("EMBY", "Resetting the Kodi music database.")
-        connection = kodiSQL('music')
-        cursor = connection.cursor()
-        cursor.execute('SELECT tbl_name FROM sqlite_master WHERE type="table"')
-        rows = cursor.fetchall()
-        for row in rows:
-            tablename = row[0]
-            if tablename != "version":
-                cursor.execute("DELETE FROM " + tablename)
-        connection.commit()
-        cursor.close()
-
-    # Wipe the emby database
-    logMsg("EMBY", "Resetting the Emby database.", 0)
-    connection = kodiSQL('emby')
-    cursor = connection.cursor()
-    cursor.execute('SELECT tbl_name FROM sqlite_master WHERE type="table"')
-    rows = cursor.fetchall()
-    for row in rows:
-        tablename = row[0]
-        if tablename != "version":
-            cursor.execute("DELETE FROM " + tablename)
-    cursor.execute('DROP table IF EXISTS emby')
-    cursor.execute('DROP table IF EXISTS view')
-    connection.commit()
-    cursor.close()
-
-    # Offer to wipe cached thumbnails
-    resp = dialog.yesno("Warning", "Removed all cached artwork?")
-    if resp:
-        logMsg("EMBY", "Resetting all cached artwork.", 0)
-        # 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:
-                    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
-        connection = kodiSQL('texture')
-        cursor = connection.cursor()
-        cursor.execute('SELECT tbl_name FROM sqlite_master WHERE type="table"')
-        rows = cursor.fetchall()
-        for row in rows:
-            tableName = row[0]
-            if(tableName != "version"):
-                cursor.execute("DELETE FROM " + tableName)
-        connection.commit()
-        cursor.close()
-    
-    # reset the install run flag  
-    settings('SyncInstallRunDone', value="false")
-
-    # Remove emby info
-    resp = dialog.yesno("Warning", "Reset all Emby Addon settings?")
-    if resp:
-        # Delete the settings
-        addon = xbmcaddon.Addon()
-        addondir = xbmc.translatePath(addon.getAddonInfo('profile')).decode('utf-8')
-        dataPath = "%ssettings.xml" % addondir
-        xbmcvfs.delete(dataPath)
-        logMsg("EMBY", "Deleting: settings.xml", 1)
-
-    dialog.ok(
-        heading="Emby for Kodi",
-        line1="Database reset has completed, Kodi will now restart to apply the changes.")
-    xbmc.executebuiltin('RestartApp')
-
-def profiling(sortby="cumulative"):
-    # Will print results to Kodi log
-    def decorator(func):
-        def wrapper(*args, **kwargs):
-            
-            pr = cProfile.Profile()
-
-            pr.enable()
-            result = func(*args, **kwargs)
-            pr.disable()
-
-            s = StringIO.StringIO()
-            ps = pstats.Stats(pr, stream=s).sort_stats(sortby)
-            ps.print_stats()
-            logMsg("EMBY Profiling", s.getvalue(), 1)
-
-            return result
-
-        return wrapper
-    return decorator
-
-def convertdate(date):
-    try:
-        date = datetime.strptime(date, "%Y-%m-%dT%H:%M:%SZ")
-    except TypeError:
-        # TypeError: attribute of type 'NoneType' is not callable
-        # Known Kodi/python error
-        date = datetime(*(time.strptime(date, "%Y-%m-%dT%H:%M:%SZ")[0:6]))
-
-    return date
-
-def normalize_nodes(text):
-    # For video nodes
-    text = text.replace(":", "")
-    text = text.replace("/", "-")
-    text = text.replace("\\", "-")
-    text = text.replace("<", "")
-    text = text.replace(">", "")
-    text = text.replace("*", "")
-    text = text.replace("?", "")
-    text = text.replace('|', "")
-    text = text.replace('(', "")
-    text = text.replace(')', "")
-    text = text.strip()
-    # Remove dots from the last character as windows can not have directories
-    # with dots at the end
-    text = text.rstrip('.')
-    text = unicodedata.normalize('NFKD', unicode(text, 'utf-8')).encode('ascii', 'ignore')
-    
-    return text
-
-def normalize_string(text):
-    # For theme media, do not modify unless
-    # modified in TV Tunes
-    text = text.replace(":", "")
-    text = text.replace("/", "-")
-    text = text.replace("\\", "-")
-    text = text.replace("<", "")
-    text = text.replace(">", "")
-    text = text.replace("*", "")
-    text = text.replace("?", "")
-    text = text.replace('|', "")
-    text = text.strip()
-    # Remove dots from the last character as windows can not have directories
-    # with dots at the end
-    text = text.rstrip('.')
-    text = unicodedata.normalize('NFKD', unicode(text, 'utf-8')).encode('ascii', 'ignore')
-
-    return text
-
-def indent(elem, level=0):
-    # Prettify xml trees
-    i = "\n" + level*"  "
-    if len(elem):
-        if not elem.text or not elem.text.strip():
-          elem.text = i + "  "
-        if not elem.tail or not elem.tail.strip():
-          elem.tail = i
-        for elem in elem:
-          indent(elem, level+1)
-        if not elem.tail or not elem.tail.strip():
-          elem.tail = i
-    else:
-        if level and (not elem.tail or not elem.tail.strip()):
-          elem.tail = i
-
-def sourcesXML():
-    # To make Master lock compatible
-    path = xbmc.translatePath("special://profile/").decode('utf-8')
-    xmlpath = "%ssources.xml" % path
-
-    try:
-        xmlparse = etree.parse(xmlpath)
-    except: # Document is blank or missing
-        root = etree.Element('sources')
-    else:
-        root = xmlparse.getroot()
-        
-
-    video = root.find('video')
-    if video is None:
-        video = etree.SubElement(root, 'video')
-        etree.SubElement(video, 'default', attrib={'pathversion': "1"})
-
-    # Add elements
-    count = 2
-    for source in root.findall('.//path'):
-        if source.text == "smb://":
-            count -= 1
-        
-        if count == 0:
-            # sources already set
-            break
-    else:
-        # Missing smb:// occurences, re-add.
-        for i in range(0, count):
-            source = etree.SubElement(video, 'source')
-            etree.SubElement(source, 'name').text = "Emby"
-            etree.SubElement(source, 'path', attrib={'pathversion': "1"}).text = "smb://"
-            etree.SubElement(source, 'allowsharing').text = "true"
-    # Prettify and write to file
-    try:
-        indent(root)
-    except: pass
-    etree.ElementTree(root).write(xmlpath)
-
-def passwordsXML():
-
-    # To add network credentials
-    path = xbmc.translatePath("special://userdata/").decode('utf-8')
-    xmlpath = "%spasswords.xml" % path
-
-    try:
-        xmlparse = etree.parse(xmlpath)
-    except: # Document is blank or missing
-        root = etree.Element('passwords')
-    else:
-        root = xmlparse.getroot()
-
-    dialog = xbmcgui.Dialog()
-    credentials = settings('networkCreds')
-    if credentials:
-        # Present user with options
-        option = dialog.select("Modify/Remove network credentials", ["Modify", "Remove"])
-
-        if option < 0:
-            # User cancelled dialog
-            return
-
-        elif option == 1:
-            # User selected remove
-            iterator = root.getiterator('passwords')
-
-            for paths in iterator:
-                for path in paths:
-                    if path.find('.//from').text == "smb://%s/" % credentials:
-                        paths.remove(path)
-                        logMsg("EMBY", "Successfully removed credentials for: %s"
-                                % credentials, 1)
-                        etree.ElementTree(root).write(xmlpath)
-                        break
-            else:
-                logMsg("EMBY", "Failed to find saved server: %s in passwords.xml" % credentials, 1)
-            
-            settings('networkCreds', value="")
-            xbmcgui.Dialog().notification(
-                                heading="Emby for Kodi",
-                                message="%s removed from passwords.xml" % credentials,
-                                icon="special://home/addons/plugin.video.emby/icon.png",
-                                time=1000,
-                                sound=False)
-            return
-
-        elif option == 0:
-            # User selected to modify
-            server = dialog.input("Modify the computer name or ip address", credentials)
-            if not server:
-                return
-    else:
-        # No credentials added
-        dialog.ok(
-            heading="Network credentials",
-            line1= (
-                "Input the server name or IP address as indicated in your emby library paths. "
-                'For example, the server name: \\\\SERVER-PC\\path\\ is "SERVER-PC".'))
-        server = dialog.input("Enter the server name or IP address")
-        if not server:
-            return
-
-    # Network username
-    user = dialog.input("Enter the network username")
-    if not user:
-        return
-    # Network password
-    password = dialog.input(
-                        heading="Enter the network password",
-                        option=xbmcgui.ALPHANUM_HIDE_INPUT)
-    if not password:
-        return
-
-    # Add elements
-    for path in root.findall('.//path'):
-        if path.find('.//from').text.lower() == "smb://%s/" % server.lower():
-            # Found the server, rewrite credentials
-            path.find('.//to').text = "smb://%s:%s@%s/" % (user, password, server)
-            break
-    else:
-        # Server not found, add it.
-        path = etree.SubElement(root, 'path')
-        etree.SubElement(path, 'from', attrib={'pathversion': "1"}).text = "smb://%s/" % server
-        topath = "smb://%s:%s@%s/" % (user, password, server)
-        etree.SubElement(path, 'to', attrib={'pathversion': "1"}).text = topath
-        # Force Kodi to see the credentials without restarting
-        xbmcvfs.exists(topath)
-
-    # Add credentials    
-    settings('networkCreds', value="%s" % server)
-    logMsg("EMBY", "Added server: %s to passwords.xml" % server, 1)
-    # Prettify and write to file
-    try:
-        indent(root)
-    except: pass
-    etree.ElementTree(root).write(xmlpath)
-    
-    dialog.notification(
-            heading="Emby for Kodi",
-            message="%s added to passwords.xml" % server,
-            icon="special://home/addons/plugin.video.emby/icon.png",
-            time=1000,
-            sound=False)
-
-def playlistXSP(mediatype, tagname, viewid, viewtype="", delete=False):
-    # Tagname is in unicode - actions: add or delete
-    tagname = tagname.encode('utf-8')
-
-    path = xbmc.translatePath("special://profile/playlists/video/").decode('utf-8')
-    if viewtype == "mixed":
-        plname = "%s - %s" % (tagname, mediatype)
-        xsppath = "%sEmby %s - %s.xsp" % (path, viewid, mediatype)
-    else:
-        plname = tagname
-        xsppath = "%sEmby %s.xsp" % (path, viewid)
-
-    # Create the playlist directory
-    if not xbmcvfs.exists(path):
-        logMsg("EMBY", "Creating directory: %s" % path, 1)
-        xbmcvfs.mkdirs(path)
-
-    # Only add the playlist if it doesn't already exists
-    if xbmcvfs.exists(xsppath):
-
-        if delete:
-            xbmcvfs.delete(xsppath)
-            logMsg("EMBY", "Successfully removed playlist: %s." % tagname, 1)
-        
-        return
-
-    # Using write process since there's no guarantee the xml declaration works with etree
-    itemtypes = {
-        'homevideos': "movies"
-    }
-    logMsg("EMBY", "Writing playlist file to: %s" % xsppath, 1)
-    try:
-        f = xbmcvfs.File(xsppath, 'w')
-    except:
-        logMsg("EMBY", "Failed to create playlist: %s" % xsppath, 1)
-        return
-    else:
-        f.write(
-            '<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>\n'
-            '<smartplaylist type="%s">\n\t'
-                '<name>Emby %s</name>\n\t'
-                '<match>all</match>\n\t'
-                '<rule field="tag" operator="is">\n\t\t'
-                    '<value>%s</value>\n\t'
-                '</rule>'
-            '</smartplaylist>'
-            % (itemtypes.get(mediatype, mediatype), plname, tagname))
-        f.close()
-    logMsg("EMBY", "Successfully added playlist: %s" % tagname)
-
-def deletePlaylists():
-
-    # Clean up the playlists
-    path = xbmc.translatePath("special://profile/playlists/video/").decode('utf-8')
-    dirs, files = xbmcvfs.listdir(path)
-    for file in files:
-        if file.decode('utf-8').startswith('Emby'):
-            xbmcvfs.delete("%s%s" % (path, file))
-
-def deleteNodes():
-
-    # Clean up video nodes
-    import shutil
-    path = xbmc.translatePath("special://profile/library/video/").decode('utf-8')
-    dirs, files = xbmcvfs.listdir(path)
-    for dir in dirs:
-        if dir.decode('utf-8').startswith('Emby'):
-            try:
-                shutil.rmtree("%s%s" % (path, dir.decode('utf-8')))
-            except:
-                logMsg("EMBY", "Failed to delete directory: %s" % dir.decode('utf-8'))
-    for file in files:
-        if file.decode('utf-8').startswith('emby'):
-            try:
-                xbmcvfs.delete("%s%s" % (path, file.decode('utf-8')))
-            except:
+# -*- coding: utf-8 -*-
+
+#################################################################################################
+
+import cProfile
+import inspect
+import json
+import pstats
+import sqlite3
+import StringIO
+import os
+from datetime import datetime, time
+import time
+import unicodedata
+import xml.etree.ElementTree as etree
+
+import xbmc
+import xbmcaddon
+import xbmcgui
+import xbmcvfs
+
+#################################################################################################
+
+
+def logMsg(title, msg, level=1):
+
+    # Get the logLevel set in UserClient
+    try:
+        logLevel = int(window('emby_logLevel'))
+    except ValueError:
+        logLevel = 0
+
+    if logLevel >= level:
+
+        if logLevel == 2: # inspect.stack() is expensive
+            try:
+                xbmc.log("%s -> %s : %s" % (title, inspect.stack()[1][3], msg))
+            except UnicodeEncodeError:
+                xbmc.log("%s -> %s : %s" % (title, inspect.stack()[1][3], msg.encode('utf-8')))
+        else:
+            try:
+                xbmc.log("%s -> %s" % (title, msg))
+            except UnicodeEncodeError:
+                xbmc.log("%s -> %s" % (title, msg.encode('utf-8')))
+
+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:
+        WINDOW.setProperty(property, value)
+    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
+    if value is not None:
+        xbmcaddon.Addon(id='plugin.video.metaman').setSetting(setting, value)
+    else:
+        return xbmcaddon.Addon(id='plugin.video.metaman').getSetting(setting) #returns unicode object
+
+def language(stringid):
+    # Central string retrieval
+    string = xbmcaddon.Addon(id='plugin.video.emby').getLocalizedString(stringid) #returns unicode object
+    return string
+
+def kodiSQL(media_type="video"):
+
+    if media_type == "emby":
+        dbPath = xbmc.translatePath("special://database/emby.db").decode('utf-8')
+    elif media_type == "music":
+        dbPath = getKodiMusicDBPath()
+    elif media_type == "texture":
+        dbPath = xbmc.translatePath("special://database/Textures13.db").decode('utf-8')
+    else:
+        dbPath = getKodiVideoDBPath()
+
+    connection = sqlite3.connect(dbPath)
+    return connection
+
+def getKodiVideoDBPath():
+
+    dbVersion = {
+
+        "13": 78,   # Gotham
+        "14": 90,   # Helix
+        "15": 93,   # Isengard
+        "16": 99    # Jarvis
+    }
+
+    dbPath = xbmc.translatePath(
+                    "special://database/MyVideos%s.db"
+                    % dbVersion.get(xbmc.getInfoLabel('System.BuildVersion')[:2], "")).decode('utf-8')
+    return dbPath
+
+def getKodiMusicDBPath():
+
+    dbVersion = {
+
+        "13": 46,   # Gotham
+        "14": 48,   # Helix
+        "15": 52,   # Isengard
+        "16": 56    # Jarvis
+    }
+
+    dbPath = xbmc.translatePath(
+                    "special://database/MyMusic%s.db"
+                    % dbVersion.get(xbmc.getInfoLabel('System.BuildVersion')[:2], "")).decode('utf-8')
+    return dbPath
+
+def getScreensaver():
+    # Get the current screensaver value
+    query = {
+
+        'jsonrpc': "2.0",
+        'id': 0,
+        'method': "Settings.getSettingValue",
+        'params': {
+
+            'setting': "screensaver.mode"
+        }
+    }
+    return json.loads(xbmc.executeJSONRPC(json.dumps(query)))['result']['value']
+
+def setScreensaver(value):
+    # Toggle the screensaver
+    query = {
+
+        'jsonrpc': "2.0",
+        'id': 0,
+        'method': "Settings.setSettingValue",
+        'params': {
+
+            'setting': "screensaver.mode",
+            'value': value
+        }
+    }
+    logMsg("EMBY", "Toggling screensaver: %s %s" % (value, xbmc.executeJSONRPC(json.dumps(query))), 1)
+
+def reset():
+
+    dialog = xbmcgui.Dialog()
+
+    if dialog.yesno("Warning", "Are you sure you want to reset your local Kodi database?") == 0:
+        return
+
+    # first stop any db sync
+    window('emby_shouldStop', value="true")
+    count = 10
+    while window('emby_dbScan') == "true":
+        logMsg("EMBY", "Sync is running, will retry: %s..." % count)
+        count -= 1
+        if count == 0:
+            dialog.ok("Warning", "Could not stop the database from running. Try again.")
+            return
+        xbmc.sleep(1000)
+
+    # Clean up the playlists
+    deletePlaylists()
+
+    # Clean up the video nodes
+    deleteNodes()
+
+    # Wipe the kodi databases
+    logMsg("EMBY", "Resetting the Kodi video database.", 0)
+    connection = kodiSQL('video')
+    cursor = connection.cursor()
+    cursor.execute('SELECT tbl_name FROM sqlite_master WHERE type="table"')
+    rows = cursor.fetchall()
+    for row in rows:
+        tablename = row[0]
+        if tablename != "version":
+            cursor.execute("DELETE FROM " + tablename)
+    connection.commit()
+    cursor.close()
+
+    if settings('enableMusic') == "true":
+        logMsg("EMBY", "Resetting the Kodi music database.")
+        connection = kodiSQL('music')
+        cursor = connection.cursor()
+        cursor.execute('SELECT tbl_name FROM sqlite_master WHERE type="table"')
+        rows = cursor.fetchall()
+        for row in rows:
+            tablename = row[0]
+            if tablename != "version":
+                cursor.execute("DELETE FROM " + tablename)
+        connection.commit()
+        cursor.close()
+
+    # Wipe the emby database
+    logMsg("EMBY", "Resetting the Emby database.", 0)
+    connection = kodiSQL('emby')
+    cursor = connection.cursor()
+    cursor.execute('SELECT tbl_name FROM sqlite_master WHERE type="table"')
+    rows = cursor.fetchall()
+    for row in rows:
+        tablename = row[0]
+        if tablename != "version":
+            cursor.execute("DELETE FROM " + tablename)
+    cursor.execute('DROP table IF EXISTS emby')
+    cursor.execute('DROP table IF EXISTS view')
+    connection.commit()
+    cursor.close()
+
+    # Offer to wipe cached thumbnails
+    resp = dialog.yesno("Warning", "Remove all cached artwork?")
+    if resp:
+        logMsg("EMBY", "Resetting all cached artwork.", 0)
+        # 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:
+                    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
+        connection = kodiSQL('texture')
+        cursor = connection.cursor()
+        cursor.execute('SELECT tbl_name FROM sqlite_master WHERE type="table"')
+        rows = cursor.fetchall()
+        for row in rows:
+            tableName = row[0]
+            if(tableName != "version"):
+                cursor.execute("DELETE FROM " + tableName)
+        connection.commit()
+        cursor.close()
+
+    # reset the install run flag
+    settings('SyncInstallRunDone', value="false")
+
+    # Remove emby info
+    resp = dialog.yesno("Warning", "Reset all Emby Addon settings?")
+    if resp:
+        # Delete the settings
+        addon = xbmcaddon.Addon()
+        addondir = xbmc.translatePath(addon.getAddonInfo('profile')).decode('utf-8')
+        dataPath = "%ssettings.xml" % addondir
+        xbmcvfs.delete(dataPath)
+        logMsg("EMBY", "Deleting: settings.xml", 1)
+
+    dialog.ok(
+        heading="Emby for Kodi",
+        line1="Database reset has completed, Kodi will now restart to apply the changes.")
+    xbmc.executebuiltin('RestartApp')
+
+def profiling(sortby="cumulative"):
+    # Will print results to Kodi log
+    def decorator(func):
+        def wrapper(*args, **kwargs):
+
+            pr = cProfile.Profile()
+
+            pr.enable()
+            result = func(*args, **kwargs)
+            pr.disable()
+
+            s = StringIO.StringIO()
+            ps = pstats.Stats(pr, stream=s).sort_stats(sortby)
+            ps.print_stats()
+            logMsg("EMBY Profiling", s.getvalue(), 1)
+
+            return result
+
+        return wrapper
+    return decorator
+
+def convertdate(date):
+    try:
+        date = datetime.strptime(date, "%Y-%m-%dT%H:%M:%SZ")
+    except TypeError:
+        # TypeError: attribute of type 'NoneType' is not callable
+        # Known Kodi/python error
+        date = datetime(*(time.strptime(date, "%Y-%m-%dT%H:%M:%SZ")[0:6]))
+
+    return date
+
+def normalize_nodes(text):
+    # For video nodes
+    text = text.replace(":", "")
+    text = text.replace("/", "-")
+    text = text.replace("\\", "-")
+    text = text.replace("<", "")
+    text = text.replace(">", "")
+    text = text.replace("*", "")
+    text = text.replace("?", "")
+    text = text.replace('|', "")
+    text = text.replace('(', "")
+    text = text.replace(')', "")
+    text = text.strip()
+    # Remove dots from the last character as windows can not have directories
+    # with dots at the end
+    text = text.rstrip('.')
+    text = unicodedata.normalize('NFKD', unicode(text, 'utf-8')).encode('ascii', 'ignore')
+
+    return text
+
+def normalize_string(text):
+    # For theme media, do not modify unless
+    # modified in TV Tunes
+    text = text.replace(":", "")
+    text = text.replace("/", "-")
+    text = text.replace("\\", "-")
+    text = text.replace("<", "")
+    text = text.replace(">", "")
+    text = text.replace("*", "")
+    text = text.replace("?", "")
+    text = text.replace('|', "")
+    text = text.strip()
+    # Remove dots from the last character as windows can not have directories
+    # with dots at the end
+    text = text.rstrip('.')
+    text = unicodedata.normalize('NFKD', unicode(text, 'utf-8')).encode('ascii', 'ignore')
+
+    return text
+
+def indent(elem, level=0):
+    # Prettify xml trees
+    i = "\n" + level*"  "
+    if len(elem):
+        if not elem.text or not elem.text.strip():
+          elem.text = i + "  "
+        if not elem.tail or not elem.tail.strip():
+          elem.tail = i
+        for elem in elem:
+          indent(elem, level+1)
+        if not elem.tail or not elem.tail.strip():
+          elem.tail = i
+    else:
+        if level and (not elem.tail or not elem.tail.strip()):
+          elem.tail = i
+
+def sourcesXML():
+    # To make Master lock compatible
+    path = xbmc.translatePath("special://profile/").decode('utf-8')
+    xmlpath = "%ssources.xml" % path
+
+    try:
+        xmlparse = etree.parse(xmlpath)
+    except: # Document is blank or missing
+        root = etree.Element('sources')
+    else:
+        root = xmlparse.getroot()
+
+
+    video = root.find('video')
+    if video is None:
+        video = etree.SubElement(root, 'video')
+        etree.SubElement(video, 'default', attrib={'pathversion': "1"})
+
+    # Add elements
+    count = 2
+    for source in root.findall('.//path'):
+        if source.text == "smb://":
+            count -= 1
+
+        if count == 0:
+            # sources already set
+            break
+    else:
+        # Missing smb:// occurences, re-add.
+        for i in range(0, count):
+            source = etree.SubElement(video, 'source')
+            etree.SubElement(source, 'name').text = "Emby"
+            etree.SubElement(source, 'path', attrib={'pathversion': "1"}).text = "smb://"
+            etree.SubElement(source, 'allowsharing').text = "true"
+    # Prettify and write to file
+    try:
+        indent(root)
+    except: pass
+    etree.ElementTree(root).write(xmlpath)
+
+def passwordsXML():
+
+    # To add network credentials
+    path = xbmc.translatePath("special://userdata/").decode('utf-8')
+    xmlpath = "%spasswords.xml" % path
+
+    try:
+        xmlparse = etree.parse(xmlpath)
+    except: # Document is blank or missing
+        root = etree.Element('passwords')
+    else:
+        root = xmlparse.getroot()
+
+    dialog = xbmcgui.Dialog()
+    credentials = settings('networkCreds')
+    if credentials:
+        # Present user with options
+        option = dialog.select("Modify/Remove network credentials", ["Modify", "Remove"])
+
+        if option < 0:
+            # User cancelled dialog
+            return
+
+        elif option == 1:
+            # User selected remove
+            for paths in root.getiterator('passwords'):
+                for path in paths:
+                    if path.find('.//from').text == "smb://%s/" % credentials:
+                        paths.remove(path)
+                        logMsg("EMBY", "Successfully removed credentials for: %s"
+                                % credentials, 1)
+                        etree.ElementTree(root).write(xmlpath)
+                        break
+            else:
+                logMsg("EMBY", "Failed to find saved server: %s in passwords.xml" % credentials, 1)
+
+            settings('networkCreds', value="")
+            xbmcgui.Dialog().notification(
+                                heading="Emby for Kodi",
+                                message="%s removed from passwords.xml" % credentials,
+                                icon="special://home/addons/plugin.video.emby/icon.png",
+                                time=1000,
+                                sound=False)
+            return
+
+        elif option == 0:
+            # User selected to modify
+            server = dialog.input("Modify the computer name or ip address", credentials)
+            if not server:
+                return
+    else:
+        # No credentials added
+        dialog.ok(
+            heading="Network credentials",
+            line1= (
+                "Input the server name or IP address as indicated in your emby library paths. "
+                'For example, the server name: \\\\SERVER-PC\\path\\ is "SERVER-PC".'))
+        server = dialog.input("Enter the server name or IP address")
+        if not server:
+            return
+
+    # Network username
+    user = dialog.input("Enter the network username")
+    if not user:
+        return
+    # Network password
+    password = dialog.input(
+                        heading="Enter the network password",
+                        option=xbmcgui.ALPHANUM_HIDE_INPUT)
+    if not password:
+        return
+
+    # Add elements
+    for path in root.findall('.//path'):
+        if path.find('.//from').text.lower() == "smb://%s/" % server.lower():
+            # Found the server, rewrite credentials
+            path.find('.//to').text = "smb://%s:%s@%s/" % (user, password, server)
+            break
+    else:
+        # Server not found, add it.
+        path = etree.SubElement(root, 'path')
+        etree.SubElement(path, 'from', attrib={'pathversion': "1"}).text = "smb://%s/" % server
+        topath = "smb://%s:%s@%s/" % (user, password, server)
+        etree.SubElement(path, 'to', attrib={'pathversion': "1"}).text = topath
+        # Force Kodi to see the credentials without restarting
+        xbmcvfs.exists(topath)
+
+    # Add credentials
+    settings('networkCreds', value="%s" % server)
+    logMsg("EMBY", "Added server: %s to passwords.xml" % server, 1)
+    # Prettify and write to file
+    try:
+        indent(root)
+    except: pass
+    etree.ElementTree(root).write(xmlpath)
+
+    dialog.notification(
+            heading="Emby for Kodi",
+            message="%s added to passwords.xml" % server,
+            icon="special://home/addons/plugin.video.emby/icon.png",
+            time=1000,
+            sound=False)
+
+def playlistXSP(mediatype, tagname, viewid, viewtype="", delete=False):
+    # Tagname is in unicode - actions: add or delete
+    tagname = tagname.encode('utf-8')
+
+    path = xbmc.translatePath("special://profile/playlists/video/").decode('utf-8')
+    if viewtype == "mixed":
+        plname = "%s - %s" % (tagname, mediatype)
+        xsppath = "%sEmby %s - %s.xsp" % (path, viewid, mediatype)
+    else:
+        plname = tagname
+        xsppath = "%sEmby %s.xsp" % (path, viewid)
+
+    # Create the playlist directory
+    if not xbmcvfs.exists(path):
+        logMsg("EMBY", "Creating directory: %s" % path, 1)
+        xbmcvfs.mkdirs(path)
+
+    # Only add the playlist if it doesn't already exists
+    if xbmcvfs.exists(xsppath):
+
+        if delete:
+            xbmcvfs.delete(xsppath)
+            logMsg("EMBY", "Successfully removed playlist: %s." % tagname, 1)
+
+        return
+
+    # Using write process since there's no guarantee the xml declaration works with etree
+    itemtypes = {
+        'homevideos': "movies"
+    }
+    logMsg("EMBY", "Writing playlist file to: %s" % xsppath, 1)
+    try:
+        f = xbmcvfs.File(xsppath, 'w')
+    except:
+        logMsg("EMBY", "Failed to create playlist: %s" % xsppath, 1)
+        return
+    else:
+        f.write(
+            '<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>\n'
+            '<smartplaylist type="%s">\n\t'
+                '<name>Emby %s</name>\n\t'
+                '<match>all</match>\n\t'
+                '<rule field="tag" operator="is">\n\t\t'
+                    '<value>%s</value>\n\t'
+                '</rule>'
+            '</smartplaylist>'
+            % (itemtypes.get(mediatype, mediatype), plname, tagname))
+        f.close()
+    logMsg("EMBY", "Successfully added playlist: %s" % tagname)
+
+def deletePlaylists():
+
+    # Clean up the playlists
+    path = xbmc.translatePath("special://profile/playlists/video/").decode('utf-8')
+    dirs, files = xbmcvfs.listdir(path)
+    for file in files:
+        if file.decode('utf-8').startswith('Emby'):
+            xbmcvfs.delete("%s%s" % (path, file))
+
+def deleteNodes():
+
+    # Clean up video nodes
+    import shutil
+    path = xbmc.translatePath("special://profile/library/video/").decode('utf-8')
+    dirs, files = xbmcvfs.listdir(path)
+    for dir in dirs:
+        if dir.decode('utf-8').startswith('Emby'):
+            try:
+                shutil.rmtree("%s%s" % (path, dir.decode('utf-8')))
+            except:
+                logMsg("EMBY", "Failed to delete directory: %s" % dir.decode('utf-8'))
+    for file in files:
+        if file.decode('utf-8').startswith('emby'):
+            try:
+                xbmcvfs.delete("%s%s" % (path, file.decode('utf-8')))
+            except:
                 logMsg("EMBY", "Failed to file: %s" % file.decode('utf-8'))
\ No newline at end of file
diff --git a/resources/lib/videonodes.py b/resources/lib/videonodes.py
index 53c18385..f7f63c3c 100644
--- a/resources/lib/videonodes.py
+++ b/resources/lib/videonodes.py
@@ -1,395 +1,394 @@
-# -*- coding: utf-8 -*-
-
-#################################################################################################
-
-import shutil
-import xml.etree.ElementTree as etree
-
-import xbmc
-import xbmcaddon
-import xbmcvfs
-
-import clientinfo
-import utils
-
-#################################################################################################
-
-
-class VideoNodes(object):
-
-
-    def __init__(self):
-
-        clientInfo = clientinfo.ClientInfo()
-        self.addonName = clientInfo.getAddonName()
-
-        self.kodiversion = int(xbmc.getInfoLabel('System.BuildVersion')[:2])
-
-    def logMsg(self, msg, lvl=1):
-
-        className = self.__class__.__name__
-        utils.logMsg("%s %s" % (self.addonName, className), msg, lvl)
-
-
-    def commonRoot(self, order, label, tagname, roottype=1):
-
-        if roottype == 0:
-            # Index
-            root = etree.Element('node', attrib={'order': "%s" % order})
-        elif roottype == 1:
-            # Filter
-            root = etree.Element('node', attrib={'order': "%s" % order, 'type': "filter"})
-            etree.SubElement(root, 'match').text = "all"
-            # Add tag rule
-            rule = etree.SubElement(root, 'rule', attrib={'field': "tag", 'operator': "is"})
-            etree.SubElement(rule, 'value').text = tagname
-        else:
-            # Folder
-            root = etree.Element('node', attrib={'order': "%s" % order, 'type': "folder"})
-
-        etree.SubElement(root, 'label').text = label
-        etree.SubElement(root, 'icon').text = "special://home/addons/plugin.video.emby/icon.png"
-
-        return root
-
-    def viewNode(self, indexnumber, tagname, mediatype, viewtype, viewid, delete=False):
-
-        window = utils.window
-        kodiversion = self.kodiversion
-
-        if viewtype == "mixed":
-            dirname = "%s - %s" % (viewid, mediatype)
-        else:
-            dirname = viewid
-        
-        path = xbmc.translatePath("special://profile/library/video/").decode('utf-8')
-        nodepath = xbmc.translatePath(
-                    "special://profile/library/video/Emby - %s/" % dirname).decode('utf-8')
-
-        # Verify the video directory
-        if not xbmcvfs.exists(path):
-            shutil.copytree(
-                src=xbmc.translatePath("special://xbmc/system/library/video").decode('utf-8'),
-                dst=xbmc.translatePath("special://profile/library/video").decode('utf-8'))
-            xbmcvfs.exists(path)
-
-        # Create the node directory
-        if not xbmcvfs.exists(nodepath) and not mediatype == "photos":
-            # We need to copy over the default items
-            xbmcvfs.mkdirs(nodepath)
-        else:
-            if delete:
-                dirs, files = xbmcvfs.listdir(nodepath)
-                for file in files:
-                    xbmcvfs.delete(nodepath + file)
-
-                self.logMsg("Sucessfully removed videonode: %s." % tagname, 1)
-                return
-
-        # Create index entry
-        nodeXML = "%sindex.xml" % nodepath
-        # Set windows property
-        path = "library://video/Emby - %s/" % dirname
-        for i in range(1, indexnumber):
-            # Verify to make sure we don't create duplicates
-            if window('Emby.nodes.%s.index' % i) == path:
-                return
-
-        if mediatype == "photos":
-            path = "plugin://plugin.video.emby/?id=%s&mode=getsubfolders" % indexnumber
-            
-        window('Emby.nodes.%s.index' % indexnumber, value=path)
-        
-        # Root
-        if not mediatype == "photos":
-            if viewtype == "mixed":
-                specialtag = "%s - %s" % (tagname, mediatype)
-                root = self.commonRoot(order=0, label=specialtag, tagname=tagname, roottype=0)
-            else:
-                root = self.commonRoot(order=0, label=tagname, tagname=tagname, roottype=0)
-            try:
-                utils.indent(root)
-            except: pass
-            etree.ElementTree(root).write(nodeXML)
-
-        nodetypes = {
-
-            '1': "all",
-            '2': "recent",
-            '3': "recentepisodes",
-            '4': "inprogress",
-            '5': "inprogressepisodes",
-            '6': "unwatched",
-            '7': "nextepisodes",
-            '8': "sets",
-            '9': "genres",
-            '10': "random",
-            '11': "recommended",
-        }
-        mediatypes = {
-            # label according to nodetype per mediatype
-            'movies': 
-                {
-                '1': tagname,
-                '2': 30174,
-                '4': 30177,
-                '6': 30189,
-                '8': 20434,
-                '9': 135,
-                '10': 30229,
-                '11': 30230
-                },
-
-            'tvshows': 
-                {
-                '1': tagname,
-                '2': 30170,
-                '3': 30175,
-                '4': 30171,
-                '5': 30178,
-                '7': 30179,
-                '9': 135,
-                '10': 30229,
-                '11': 30230
-                },
-                
-            'homevideos': 
-                {
-                '1': tagname,
-                '2': 30251,
-                '11': 30253
-                },
-                
-            'photos': 
-                {
-                '1': tagname,
-                '2': 30252,
-                '8': 30255,
-                '11': 30254
-                },
-
-            'musicvideos': 
-                {
-                '1': tagname,
-                '2': 30256,
-                '4': 30257,
-                '6': 30258
-                }
-        }
-
-        nodes = mediatypes[mediatype]
-        for node in nodes:
-
-            nodetype = nodetypes[node]
-            nodeXML = "%s%s_%s.xml" % (nodepath, viewid, nodetype)
-            # Get label
-            stringid = nodes[node]
-            if node != "1":
-                label = utils.language(stringid)
-                if not label:
-                    label = xbmc.getLocalizedString(stringid)
-            else:
-                label = stringid
-
-            # Set window properties
-            if (mediatype == "homevideos" or mediatype == "photos") and nodetype == "all":
-                # Custom query
-                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&folderid=%s"
-                        % (tagname, mediatype, nodetype))
-            elif nodetype == "nextepisodes":
-                # Custom query
-                path = "plugin://plugin.video.emby/?id=%s&mode=nextup&limit=25" % tagname
-            elif kodiversion == 14 and nodetype == "recentepisodes":
-                # Custom query
-                path = "plugin://plugin.video.emby/?id=%s&mode=recentepisodes&limit=25" % tagname
-            elif kodiversion == 14 and nodetype == "inprogressepisodes":
-                # Custom query
-                path = "plugin://plugin.video.emby/?id=%s&mode=inprogressepisodes&limit=25"% tagname
-            else:
-                path = "library://video/Emby - %s/%s_%s.xml" % (dirname, viewid, nodetype)
-            
-            if mediatype == "photos":
-                windowpath = "ActivateWindow(Pictures,%s,return)" % path
-            else:
-                windowpath = "ActivateWindow(Video,%s,return)" % path
-            
-            if nodetype == "all":
-
-                if viewtype == "mixed":
-                    templabel = "%s - %s" % (tagname, mediatype)
-                else:
-                    templabel = label
-
-                embynode = "Emby.nodes.%s" % indexnumber
-                window('%s.title' % embynode, value=templabel)
-                window('%s.path' % embynode, value=windowpath)
-                window('%s.content' % embynode, value=path)
-                window('%s.type' % embynode, value=mediatype)
-            else:
-                embynode = "Emby.nodes.%s.%s" % (indexnumber, nodetype)
-                window('%s.title' % embynode, value=label)
-                window('%s.path' % embynode, value=windowpath)
-                window('%s.content' % embynode, value=path)
-
-            if mediatype == "photos":
-                # For photos, we do not create a node in videos but we do want the window props
-                # to be created.
-                # To do: add our photos nodes to kodi picture sources somehow
-                continue
-            
-            if xbmcvfs.exists(nodeXML):
-                # Don't recreate xml if already exists
-                continue
-
-            # Create the root
-            if (nodetype == "nextepisodes" or mediatype == "homevideos" or
-                    (kodiversion == 14 and nodetype in ('recentepisodes', 'inprogressepisodes'))):
-                # Folder type with plugin path
-                root = self.commonRoot(order=node, label=label, tagname=tagname, roottype=2)
-                etree.SubElement(root, 'path').text = path
-                etree.SubElement(root, 'content').text = "episodes"
-            else:
-                root = self.commonRoot(order=node, label=label, tagname=tagname)
-                if nodetype in ('recentepisodes', 'inprogressepisodes'):
-                    etree.SubElement(root, 'content').text = "episodes"
-                else:
-                    etree.SubElement(root, 'content').text = mediatype
-
-                limit = "25"
-                # Elements per nodetype
-                if nodetype == "all":
-                    etree.SubElement(root, 'order', {'direction': "ascending"}).text = "sorttitle"
-                
-                elif nodetype == "recent":
-                    etree.SubElement(root, 'order', {'direction': "descending"}).text = "dateadded"
-                    etree.SubElement(root, 'limit').text = limit
-                    rule = etree.SubElement(root, 'rule', {'field': "playcount", 'operator': "is"})
-                    etree.SubElement(rule, 'value').text = "0"
-                
-                elif nodetype == "inprogress":
-                    etree.SubElement(root, 'rule', {'field': "inprogress", 'operator': "true"})
-                    etree.SubElement(root, 'limit').text = limit
-
-                elif nodetype == "genres":
-                    etree.SubElement(root, 'order', {'direction': "ascending"}).text = "sorttitle"
-                    etree.SubElement(root, 'group').text = "genres"
-                
-                elif nodetype == "unwatched":
-                    etree.SubElement(root, 'order', {'direction': "ascending"}).text = "sorttitle"
-                    rule = etree.SubElement(root, "rule", {'field': "playcount", 'operator': "is"})
-                    etree.SubElement(rule, 'value').text = "0"
-
-                elif nodetype == "sets":
-                    etree.SubElement(root, 'order', {'direction': "ascending"}).text = "sorttitle"
-                    etree.SubElement(root, 'group').text = "sets"
-
-                elif nodetype == "random":
-                    etree.SubElement(root, 'order', {'direction': "ascending"}).text = "random"
-                    etree.SubElement(root, 'limit').text = limit
-
-                elif nodetype == "recommended":
-                    etree.SubElement(root, 'order', {'direction': "descending"}).text = "rating"
-                    etree.SubElement(root, 'limit').text = limit
-                    rule = etree.SubElement(root, 'rule', {'field': "playcount", 'operator': "is"})
-                    etree.SubElement(rule, 'value').text = "0"
-                    rule2 = etree.SubElement(root, 'rule',
-                        attrib={'field': "rating", 'operator': "greaterthan"})
-                    etree.SubElement(rule2, 'value').text = "7"
-
-                elif nodetype == "recentepisodes":
-                    # Kodi Isengard, Jarvis
-                    etree.SubElement(root, 'order', {'direction': "descending"}).text = "dateadded"
-                    etree.SubElement(root, 'limit').text = limit
-                    rule = etree.SubElement(root, 'rule', {'field': "playcount", 'operator': "is"})
-                    etree.SubElement(rule, 'value').text = "0"
-
-                elif nodetype == "inprogressepisodes":
-                    # Kodi Isengard, Jarvis
-                    etree.SubElement(root, 'limit').text = "25"
-                    rule = etree.SubElement(root, 'rule',
-                        attrib={'field': "inprogress", 'operator':"true"})
-
-            try:
-                utils.indent(root)
-            except: pass
-            etree.ElementTree(root).write(nodeXML)
-
-    def singleNode(self, indexnumber, tagname, mediatype, itemtype):
-
-        window = utils.window
-
-        tagname = tagname.encode('utf-8')
-        cleantagname = utils.normalize_nodes(tagname)
-        nodepath = xbmc.translatePath("special://profile/library/video/").decode('utf-8')
-        nodeXML = "%semby_%s.xml" % (nodepath, cleantagname)
-        path = "library://video/emby_%s.xml" % cleantagname
-        windowpath = "ActivateWindow(Video,%s,return)" % path
-        
-        # Create the video node directory
-        if not xbmcvfs.exists(nodepath):
-            # We need to copy over the default items
-            shutil.copytree(
-                src=xbmc.translatePath("special://xbmc/system/library/video").decode('utf-8'),
-                dst=xbmc.translatePath("special://profile/library/video").decode('utf-8'))
-            xbmcvfs.exists(path)
-
-        labels = {
-
-            'Favorite movies': 30180,
-            'Favorite tvshows': 30181,
-            'channels': 30173
-        }
-        label = utils.language(labels[tagname])
-        embynode = "Emby.nodes.%s" % indexnumber
-        window('%s.title' % embynode, value=label)
-        window('%s.path' % embynode, value=windowpath)
-        window('%s.content' % embynode, value=path)
-        window('%s.type' % embynode, value=itemtype)
-
-        if xbmcvfs.exists(nodeXML):
-            # Don't recreate xml if already exists
-            return
-
-        if itemtype == "channels":
-            root = self.commonRoot(order=1, label=label, tagname=tagname, roottype=2)
-            etree.SubElement(root, 'path').text = "plugin://plugin.video.emby/?id=0&mode=channels"
-        else:
-            root = self.commonRoot(order=1, label=label, tagname=tagname)
-            etree.SubElement(root, 'order', {'direction': "ascending"}).text = "sorttitle"
-
-        etree.SubElement(root, 'content').text = mediatype
-
-        try:
-            utils.indent(root)
-        except: pass
-        etree.ElementTree(root).write(nodeXML)
-
-    def clearProperties(self):
-
-        window = utils.window
-
-        self.logMsg("Clearing nodes properties.", 1)
-        embyprops = window('Emby.nodes.total')
-        propnames = [
-        
-            "index","path","title","content",
-            "inprogress.content","inprogress.title",
-            "inprogress.content","inprogress.path",
-            "nextepisodes.title","nextepisodes.content",
-            "nextepisodes.path","unwatched.title",
-            "unwatched.content","unwatched.path",
-            "recent.title","recent.content","recent.path",
-            "recentepisodes.title","recentepisodes.content",
-            "recentepisodes.path","inprogressepisodes.title",
-            "inprogressepisodes.content","inprogressepisodes.path"
-        ]
-
-        if embyprops:
-            totalnodes = int(embyprops)
-            for i in range(totalnodes):
-                for prop in propnames:
+# -*- coding: utf-8 -*-
+
+#################################################################################################
+
+import shutil
+import xml.etree.ElementTree as etree
+
+import xbmc
+import xbmcaddon
+import xbmcvfs
+
+import clientinfo
+import utils
+
+#################################################################################################
+
+
+class VideoNodes(object):
+
+
+    def __init__(self):
+
+        clientInfo = clientinfo.ClientInfo()
+        self.addonName = clientInfo.getAddonName()
+
+        self.kodiversion = int(xbmc.getInfoLabel('System.BuildVersion')[:2])
+
+    def logMsg(self, msg, lvl=1):
+
+        className = self.__class__.__name__
+        utils.logMsg("%s %s" % (self.addonName, className), msg, lvl)
+
+
+    def commonRoot(self, order, label, tagname, roottype=1):
+
+        if roottype == 0:
+            # Index
+            root = etree.Element('node', attrib={'order': "%s" % order})
+        elif roottype == 1:
+            # Filter
+            root = etree.Element('node', attrib={'order': "%s" % order, 'type': "filter"})
+            etree.SubElement(root, 'match').text = "all"
+            # Add tag rule
+            rule = etree.SubElement(root, 'rule', attrib={'field': "tag", 'operator': "is"})
+            etree.SubElement(rule, 'value').text = tagname
+        else:
+            # Folder
+            root = etree.Element('node', attrib={'order': "%s" % order, 'type': "folder"})
+
+        etree.SubElement(root, 'label').text = label
+        etree.SubElement(root, 'icon').text = "special://home/addons/plugin.video.emby/icon.png"
+
+        return root
+
+    def viewNode(self, indexnumber, tagname, mediatype, viewtype, viewid, delete=False):
+
+        window = utils.window
+
+        if viewtype == "mixed":
+            dirname = "%s - %s" % (viewid, mediatype)
+        else:
+            dirname = viewid
+        
+        path = xbmc.translatePath("special://profile/library/video/").decode('utf-8')
+        nodepath = xbmc.translatePath(
+                    "special://profile/library/video/Emby - %s/" % dirname).decode('utf-8')
+
+        # Verify the video directory
+        if not xbmcvfs.exists(path):
+            shutil.copytree(
+                src=xbmc.translatePath("special://xbmc/system/library/video").decode('utf-8'),
+                dst=xbmc.translatePath("special://profile/library/video").decode('utf-8'))
+            xbmcvfs.exists(path)
+
+        # Create the node directory
+        if not xbmcvfs.exists(nodepath) and not mediatype == "photos":
+            # We need to copy over the default items
+            xbmcvfs.mkdirs(nodepath)
+        else:
+            if delete:
+                dirs, files = xbmcvfs.listdir(nodepath)
+                for file in files:
+                    xbmcvfs.delete(nodepath + file)
+
+                self.logMsg("Sucessfully removed videonode: %s." % tagname, 1)
+                return
+
+        # Create index entry
+        nodeXML = "%sindex.xml" % nodepath
+        # Set windows property
+        path = "library://video/Emby - %s/" % dirname
+        for i in range(1, indexnumber):
+            # Verify to make sure we don't create duplicates
+            if window('Emby.nodes.%s.index' % i) == path:
+                return
+
+        if mediatype == "photos":
+            path = "plugin://plugin.video.emby/?id=%s&mode=getsubfolders" % indexnumber
+            
+        window('Emby.nodes.%s.index' % indexnumber, value=path)
+        
+        # Root
+        if not mediatype == "photos":
+            if viewtype == "mixed":
+                specialtag = "%s - %s" % (tagname, mediatype)
+                root = self.commonRoot(order=0, label=specialtag, tagname=tagname, roottype=0)
+            else:
+                root = self.commonRoot(order=0, label=tagname, tagname=tagname, roottype=0)
+            try:
+                utils.indent(root)
+            except: pass
+            etree.ElementTree(root).write(nodeXML)
+
+        nodetypes = {
+
+            '1': "all",
+            '2': "recent",
+            '3': "recentepisodes",
+            '4': "inprogress",
+            '5': "inprogressepisodes",
+            '6': "unwatched",
+            '7': "nextepisodes",
+            '8': "sets",
+            '9': "genres",
+            '10': "random",
+            '11': "recommended",
+        }
+        mediatypes = {
+            # label according to nodetype per mediatype
+            'movies': 
+                {
+                '1': tagname,
+                '2': 30174,
+                '4': 30177,
+                '6': 30189,
+                '8': 20434,
+                '9': 135,
+                '10': 30229,
+                '11': 30230
+                },
+
+            'tvshows': 
+                {
+                '1': tagname,
+                '2': 30170,
+                '3': 30175,
+                '4': 30171,
+                '5': 30178,
+                '7': 30179,
+                '9': 135,
+                '10': 30229,
+                '11': 30230
+                },
+                
+            'homevideos': 
+                {
+                '1': tagname,
+                '2': 30251,
+                '11': 30253
+                },
+                
+            'photos': 
+                {
+                '1': tagname,
+                '2': 30252,
+                '8': 30255,
+                '11': 30254
+                },
+
+            'musicvideos': 
+                {
+                '1': tagname,
+                '2': 30256,
+                '4': 30257,
+                '6': 30258
+                }
+        }
+
+        nodes = mediatypes[mediatype]
+        for node in nodes:
+
+            nodetype = nodetypes[node]
+            nodeXML = "%s%s_%s.xml" % (nodepath, viewid, nodetype)
+            # Get label
+            stringid = nodes[node]
+            if node != "1":
+                label = utils.language(stringid)
+                if not label:
+                    label = xbmc.getLocalizedString(stringid)
+            else:
+                label = stringid
+
+            # Set window properties
+            if (mediatype == "homevideos" or mediatype == "photos") and nodetype == "all":
+                # Custom query
+                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&folderid=%s"
+                        % (tagname, mediatype, nodetype))
+            elif nodetype == "nextepisodes":
+                # Custom query
+                path = "plugin://plugin.video.emby/?id=%s&mode=nextup&limit=25" % tagname
+            elif self.kodiversion == 14 and nodetype == "recentepisodes":
+                # Custom query
+                path = "plugin://plugin.video.emby/?id=%s&mode=recentepisodes&limit=25" % tagname
+            elif self.kodiversion == 14 and nodetype == "inprogressepisodes":
+                # Custom query
+                path = "plugin://plugin.video.emby/?id=%s&mode=inprogressepisodes&limit=25"% tagname
+            else:
+                path = "library://video/Emby - %s/%s_%s.xml" % (dirname, viewid, nodetype)
+            
+            if mediatype == "photos":
+                windowpath = "ActivateWindow(Pictures,%s,return)" % path
+            else:
+                windowpath = "ActivateWindow(Video,%s,return)" % path
+            
+            if nodetype == "all":
+
+                if viewtype == "mixed":
+                    templabel = "%s - %s" % (tagname, mediatype)
+                else:
+                    templabel = label
+
+                embynode = "Emby.nodes.%s" % indexnumber
+                window('%s.title' % embynode, value=templabel)
+                window('%s.path' % embynode, value=windowpath)
+                window('%s.content' % embynode, value=path)
+                window('%s.type' % embynode, value=mediatype)
+            else:
+                embynode = "Emby.nodes.%s.%s" % (indexnumber, nodetype)
+                window('%s.title' % embynode, value=label)
+                window('%s.path' % embynode, value=windowpath)
+                window('%s.content' % embynode, value=path)
+
+            if mediatype == "photos":
+                # For photos, we do not create a node in videos but we do want the window props
+                # to be created.
+                # To do: add our photos nodes to kodi picture sources somehow
+                continue
+            
+            if xbmcvfs.exists(nodeXML):
+                # Don't recreate xml if already exists
+                continue
+
+            # Create the root
+            if (nodetype == "nextepisodes" or mediatype == "homevideos" or
+                    (self.kodiversion == 14 and nodetype in ('recentepisodes', 'inprogressepisodes'))):
+                # Folder type with plugin path
+                root = self.commonRoot(order=node, label=label, tagname=tagname, roottype=2)
+                etree.SubElement(root, 'path').text = path
+                etree.SubElement(root, 'content').text = "episodes"
+            else:
+                root = self.commonRoot(order=node, label=label, tagname=tagname)
+                if nodetype in ('recentepisodes', 'inprogressepisodes'):
+                    etree.SubElement(root, 'content').text = "episodes"
+                else:
+                    etree.SubElement(root, 'content').text = mediatype
+
+                limit = "25"
+                # Elements per nodetype
+                if nodetype == "all":
+                    etree.SubElement(root, 'order', {'direction': "ascending"}).text = "sorttitle"
+                
+                elif nodetype == "recent":
+                    etree.SubElement(root, 'order', {'direction': "descending"}).text = "dateadded"
+                    etree.SubElement(root, 'limit').text = limit
+                    rule = etree.SubElement(root, 'rule', {'field': "playcount", 'operator': "is"})
+                    etree.SubElement(rule, 'value').text = "0"
+                
+                elif nodetype == "inprogress":
+                    etree.SubElement(root, 'rule', {'field': "inprogress", 'operator': "true"})
+                    etree.SubElement(root, 'limit').text = limit
+
+                elif nodetype == "genres":
+                    etree.SubElement(root, 'order', {'direction': "ascending"}).text = "sorttitle"
+                    etree.SubElement(root, 'group').text = "genres"
+                
+                elif nodetype == "unwatched":
+                    etree.SubElement(root, 'order', {'direction': "ascending"}).text = "sorttitle"
+                    rule = etree.SubElement(root, "rule", {'field': "playcount", 'operator': "is"})
+                    etree.SubElement(rule, 'value').text = "0"
+
+                elif nodetype == "sets":
+                    etree.SubElement(root, 'order', {'direction': "ascending"}).text = "sorttitle"
+                    etree.SubElement(root, 'group').text = "sets"
+
+                elif nodetype == "random":
+                    etree.SubElement(root, 'order', {'direction': "ascending"}).text = "random"
+                    etree.SubElement(root, 'limit').text = limit
+
+                elif nodetype == "recommended":
+                    etree.SubElement(root, 'order', {'direction': "descending"}).text = "rating"
+                    etree.SubElement(root, 'limit').text = limit
+                    rule = etree.SubElement(root, 'rule', {'field': "playcount", 'operator': "is"})
+                    etree.SubElement(rule, 'value').text = "0"
+                    rule2 = etree.SubElement(root, 'rule',
+                        attrib={'field': "rating", 'operator': "greaterthan"})
+                    etree.SubElement(rule2, 'value').text = "7"
+
+                elif nodetype == "recentepisodes":
+                    # Kodi Isengard, Jarvis
+                    etree.SubElement(root, 'order', {'direction': "descending"}).text = "dateadded"
+                    etree.SubElement(root, 'limit').text = limit
+                    rule = etree.SubElement(root, 'rule', {'field': "playcount", 'operator': "is"})
+                    etree.SubElement(rule, 'value').text = "0"
+
+                elif nodetype == "inprogressepisodes":
+                    # Kodi Isengard, Jarvis
+                    etree.SubElement(root, 'limit').text = "25"
+                    rule = etree.SubElement(root, 'rule',
+                        attrib={'field': "inprogress", 'operator':"true"})
+
+            try:
+                utils.indent(root)
+            except: pass
+            etree.ElementTree(root).write(nodeXML)
+
+    def singleNode(self, indexnumber, tagname, mediatype, itemtype):
+
+        window = utils.window
+
+        tagname = tagname.encode('utf-8')
+        cleantagname = utils.normalize_nodes(tagname)
+        nodepath = xbmc.translatePath("special://profile/library/video/").decode('utf-8')
+        nodeXML = "%semby_%s.xml" % (nodepath, cleantagname)
+        path = "library://video/emby_%s.xml" % cleantagname
+        windowpath = "ActivateWindow(Video,%s,return)" % path
+        
+        # Create the video node directory
+        if not xbmcvfs.exists(nodepath):
+            # We need to copy over the default items
+            shutil.copytree(
+                src=xbmc.translatePath("special://xbmc/system/library/video").decode('utf-8'),
+                dst=xbmc.translatePath("special://profile/library/video").decode('utf-8'))
+            xbmcvfs.exists(path)
+
+        labels = {
+
+            'Favorite movies': 30180,
+            'Favorite tvshows': 30181,
+            'channels': 30173
+        }
+        label = utils.language(labels[tagname])
+        embynode = "Emby.nodes.%s" % indexnumber
+        window('%s.title' % embynode, value=label)
+        window('%s.path' % embynode, value=windowpath)
+        window('%s.content' % embynode, value=path)
+        window('%s.type' % embynode, value=itemtype)
+
+        if xbmcvfs.exists(nodeXML):
+            # Don't recreate xml if already exists
+            return
+
+        if itemtype == "channels":
+            root = self.commonRoot(order=1, label=label, tagname=tagname, roottype=2)
+            etree.SubElement(root, 'path').text = "plugin://plugin.video.emby/?id=0&mode=channels"
+        else:
+            root = self.commonRoot(order=1, label=label, tagname=tagname)
+            etree.SubElement(root, 'order', {'direction': "ascending"}).text = "sorttitle"
+
+        etree.SubElement(root, 'content').text = mediatype
+
+        try:
+            utils.indent(root)
+        except: pass
+        etree.ElementTree(root).write(nodeXML)
+
+    def clearProperties(self):
+
+        window = utils.window
+
+        self.logMsg("Clearing nodes properties.", 1)
+        embyprops = window('Emby.nodes.total')
+        propnames = [
+        
+            "index","path","title","content",
+            "inprogress.content","inprogress.title",
+            "inprogress.content","inprogress.path",
+            "nextepisodes.title","nextepisodes.content",
+            "nextepisodes.path","unwatched.title",
+            "unwatched.content","unwatched.path",
+            "recent.title","recent.content","recent.path",
+            "recentepisodes.title","recentepisodes.content",
+            "recentepisodes.path","inprogressepisodes.title",
+            "inprogressepisodes.content","inprogressepisodes.path"
+        ]
+
+        if embyprops:
+            totalnodes = int(embyprops)
+            for i in range(totalnodes):
+                for prop in propnames:
                     window('Emby.nodes.%s.%s' % (str(i), prop), clear=True)
\ No newline at end of file
diff --git a/resources/lib/websocket.py b/resources/lib/websocket.py
index 3d777a97..e35d1966 100644
--- a/resources/lib/websocket.py
+++ b/resources/lib/websocket.py
@@ -1,912 +1,911 @@
-"""
-websocket - WebSocket client library for Python
-
-Copyright (C) 2010 Hiroki Ohtani(liris)
-
-    This library is free software; you can redistribute it and/or
-    modify it under the terms of the GNU Lesser General Public
-    License as published by the Free Software Foundation; either
-    version 2.1 of the License, or (at your option) any later version.
-
-    This library is distributed in the hope that it will be useful,
-    but WITHOUT ANY WARRANTY; without even the implied warranty of
-    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-    Lesser General Public License for more details.
-
-    You should have received a copy of the GNU Lesser General Public
-    License along with this library; if not, write to the Free Software
-    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
-
-"""
-
-
-import socket
-
-try:
-    import ssl
-    from ssl import SSLError
-    HAVE_SSL = True
-except ImportError:
-    # dummy class of SSLError for ssl none-support environment.
-    class SSLError(Exception):
-        pass
-
-    HAVE_SSL = False
-
-from urlparse import urlparse
-import os
-import array
-import struct
-import uuid
-import hashlib
-import base64
-import threading
-import time
-import logging
-import traceback
-import sys
-
-"""
-websocket python client.
-=========================
-
-This version support only hybi-13.
-Please see http://tools.ietf.org/html/rfc6455 for protocol.
-"""
-
-
-# websocket supported version.
-VERSION = 13
-
-# closing frame status codes.
-STATUS_NORMAL = 1000
-STATUS_GOING_AWAY = 1001
-STATUS_PROTOCOL_ERROR = 1002
-STATUS_UNSUPPORTED_DATA_TYPE = 1003
-STATUS_STATUS_NOT_AVAILABLE = 1005
-STATUS_ABNORMAL_CLOSED = 1006
-STATUS_INVALID_PAYLOAD = 1007
-STATUS_POLICY_VIOLATION = 1008
-STATUS_MESSAGE_TOO_BIG = 1009
-STATUS_INVALID_EXTENSION = 1010
-STATUS_UNEXPECTED_CONDITION = 1011
-STATUS_TLS_HANDSHAKE_ERROR = 1015
-
-logger = logging.getLogger()
-
-
-class WebSocketException(Exception):
-    """
-    websocket exeception class.
-    """
-    pass
-
-
-class WebSocketConnectionClosedException(WebSocketException):
-    """
-    If remote host closed the connection or some network error happened,
-    this exception will be raised.
-    """
-    pass
-
-class WebSocketTimeoutException(WebSocketException):
-    """
-    WebSocketTimeoutException will be raised at socket timeout during read/write data.
-    """
-    pass
-
-default_timeout = None
-traceEnabled = False
-
-
-def enableTrace(tracable):
-    """
-    turn on/off the tracability.
-
-    tracable: boolean value. if set True, tracability is enabled.
-    """
-    global traceEnabled
-    traceEnabled = tracable
-    if tracable:
-        if not logger.handlers:
-            logger.addHandler(logging.StreamHandler())
-        logger.setLevel(logging.DEBUG)
-
-
-def setdefaulttimeout(timeout):
-    """
-    Set the global timeout setting to connect.
-
-    timeout: default socket timeout time. This value is second.
-    """
-    global default_timeout
-    default_timeout = timeout
-
-
-def getdefaulttimeout():
-    """
-    Return the global timeout setting(second) to connect.
-    """
-    return default_timeout
-
-
-def _parse_url(url):
-    """
-    parse url and the result is tuple of
-    (hostname, port, resource path and the flag of secure mode)
-
-    url: url string.
-    """
-    if ":" not in url:
-        raise ValueError("url is invalid")
-
-    scheme, url = url.split(":", 1)
-
-    parsed = urlparse(url, scheme="http")
-    if parsed.hostname:
-        hostname = parsed.hostname
-    else:
-        raise ValueError("hostname is invalid")
-    port = 0
-    if parsed.port:
-        port = parsed.port
-
-    is_secure = False
-    if scheme == "ws":
-        if not port:
-            port = 80
-    elif scheme == "wss":
-        is_secure = True
-        if not port:
-            port = 443
-    else:
-        raise ValueError("scheme %s is invalid" % scheme)
-
-    if parsed.path:
-        resource = parsed.path
-    else:
-        resource = "/"
-
-    if parsed.query:
-        resource += "?" + parsed.query
-
-    return (hostname, port, resource, is_secure)
-
-
-def create_connection(url, timeout=None, **options):
-    """
-    connect to url and return websocket object.
-
-    Connect to url and return the WebSocket object.
-    Passing optional timeout parameter will set the timeout on the socket.
-    If no timeout is supplied, the global default timeout setting returned by getdefauttimeout() is used.
-    You can customize using 'options'.
-    If you set "header" list object, you can set your own custom header.
-
-    >>> conn = create_connection("ws://echo.websocket.org/",
-         ...     header=["User-Agent: MyProgram",
-         ...             "x-custom: header"])
-
-
-    timeout: socket timeout time. This value is integer.
-             if you set None for this value, it means "use default_timeout value"
-
-    options: current support option is only "header".
-             if you set header as dict value, the custom HTTP headers are added.
-    """
-    sockopt = options.get("sockopt", [])
-    sslopt = options.get("sslopt", {})
-    websock = WebSocket(sockopt=sockopt, sslopt=sslopt)
-    websock.settimeout(timeout if timeout is not None else default_timeout)
-    websock.connect(url, **options)
-    return websock
-
-_MAX_INTEGER = (1 << 32) -1
-_AVAILABLE_KEY_CHARS = range(0x21, 0x2f + 1) + range(0x3a, 0x7e + 1)
-_MAX_CHAR_BYTE = (1<<8) -1
-
-# ref. Websocket gets an update, and it breaks stuff.
-# http://axod.blogspot.com/2010/06/websocket-gets-update-and-it-breaks.html
-
-
-def _create_sec_websocket_key():
-    uid = uuid.uuid4()
-    return base64.encodestring(uid.bytes).strip()
-
-
-_HEADERS_TO_CHECK = {
-    "upgrade": "websocket",
-    "connection": "upgrade",
-    }
-
-
-class ABNF(object):
-    """
-    ABNF frame class.
-    see http://tools.ietf.org/html/rfc5234
-    and http://tools.ietf.org/html/rfc6455#section-5.2
-    """
-
-    # operation code values.
-    OPCODE_CONT   = 0x0
-    OPCODE_TEXT   = 0x1
-    OPCODE_BINARY = 0x2
-    OPCODE_CLOSE  = 0x8
-    OPCODE_PING   = 0x9
-    OPCODE_PONG   = 0xa
-
-    # available operation code value tuple
-    OPCODES = (OPCODE_CONT, OPCODE_TEXT, OPCODE_BINARY, OPCODE_CLOSE,
-                OPCODE_PING, OPCODE_PONG)
-
-    # opcode human readable string
-    OPCODE_MAP = {
-        OPCODE_CONT: "cont",
-        OPCODE_TEXT: "text",
-        OPCODE_BINARY: "binary",
-        OPCODE_CLOSE: "close",
-        OPCODE_PING: "ping",
-        OPCODE_PONG: "pong"
-        }
-
-    # data length threashold.
-    LENGTH_7  = 0x7d
-    LENGTH_16 = 1 << 16
-    LENGTH_63 = 1 << 63
-
-    def __init__(self, fin=0, rsv1=0, rsv2=0, rsv3=0,
-                 opcode=OPCODE_TEXT, mask=1, data=""):
-        """
-        Constructor for ABNF.
-        please check RFC for arguments.
-        """
-        self.fin = fin
-        self.rsv1 = rsv1
-        self.rsv2 = rsv2
-        self.rsv3 = rsv3
-        self.opcode = opcode
-        self.mask = mask
-        self.data = data
-        self.get_mask_key = os.urandom
-
-    def __str__(self):
-        return "fin=" + str(self.fin) \
-                + " opcode=" + str(self.opcode) \
-                + " data=" + str(self.data)
-
-    @staticmethod
-    def create_frame(data, opcode):
-        """
-        create frame to send text, binary and other data.
-
-        data: data to send. This is string value(byte array).
-            if opcode is OPCODE_TEXT and this value is uniocde,
-            data value is conveted into unicode string, automatically.
-
-        opcode: operation code. please see OPCODE_XXX.
-        """
-        if opcode == ABNF.OPCODE_TEXT and isinstance(data, unicode):
-            data = data.encode("utf-8")
-        # mask must be set if send data from client
-        return ABNF(1, 0, 0, 0, opcode, 1, data)
-
-    def format(self):
-        """
-        format this object to string(byte array) to send data to server.
-        """
-        if any(x not in (0, 1) for x in [self.fin, self.rsv1, self.rsv2, self.rsv3]):
-            raise ValueError("not 0 or 1")
-        if self.opcode not in ABNF.OPCODES:
-            raise ValueError("Invalid OPCODE")
-        length = len(self.data)
-        if length >= ABNF.LENGTH_63:
-            raise ValueError("data is too long")
-
-        frame_header = chr(self.fin << 7
-                           | self.rsv1 << 6 | self.rsv2 << 5 | self.rsv3 << 4
-                           | self.opcode)
-        if length < ABNF.LENGTH_7:
-            frame_header += chr(self.mask << 7 | length)
-        elif length < ABNF.LENGTH_16:
-            frame_header += chr(self.mask << 7 | 0x7e)
-            frame_header += struct.pack("!H", length)
-        else:
-            frame_header += chr(self.mask << 7 | 0x7f)
-            frame_header += struct.pack("!Q", length)
-
-        if not self.mask:
-            return frame_header + self.data
-        else:
-            mask_key = self.get_mask_key(4)
-            return frame_header + self._get_masked(mask_key)
-
-    def _get_masked(self, mask_key):
-        s = ABNF.mask(mask_key, self.data)
-        return mask_key + "".join(s)
-
-    @staticmethod
-    def mask(mask_key, data):
-        """
-        mask or unmask data. Just do xor for each byte
-
-        mask_key: 4 byte string(byte).
-
-        data: data to mask/unmask.
-        """
-        _m = array.array("B", mask_key)
-        _d = array.array("B", data)
-        for i in xrange(len(_d)):
-            _d[i] ^= _m[i % 4]
-        return _d.tostring()
-
-
-class WebSocket(object):
-    """
-    Low level WebSocket interface.
-    This class is based on
-      The WebSocket protocol draft-hixie-thewebsocketprotocol-76
-      http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76
-
-    We can connect to the websocket server and send/recieve data.
-    The following example is a echo client.
-
-    >>> import websocket
-    >>> ws = websocket.WebSocket()
-    >>> ws.connect("ws://echo.websocket.org")
-    >>> ws.send("Hello, Server")
-    >>> ws.recv()
-    'Hello, Server'
-    >>> ws.close()
-
-    get_mask_key: a callable to produce new mask keys, see the set_mask_key
-      function's docstring for more details
-    sockopt: values for socket.setsockopt.
-        sockopt must be tuple and each element is argument of sock.setscokopt.
-    sslopt: dict object for ssl socket option.
-    """
-
-    def __init__(self, get_mask_key=None, sockopt=None, sslopt=None):
-        """
-        Initalize WebSocket object.
-        """
-        if sockopt is None:
-            sockopt = []
-        if sslopt is None:
-            sslopt = {}
-        self.connected = False
-        self.sock = socket.socket()
-        for opts in sockopt:
-            self.sock.setsockopt(*opts)
-        self.sslopt = sslopt
-        self.get_mask_key = get_mask_key
-        # Buffers over the packets from the layer beneath until desired amount
-        # bytes of bytes are received.
-        self._recv_buffer = []
-        # These buffer over the build-up of a single frame.
-        self._frame_header = None
-        self._frame_length = None
-        self._frame_mask = None
-        self._cont_data = None
-
-    def fileno(self):
-        return self.sock.fileno()
-
-    def set_mask_key(self, func):
-        """
-        set function to create musk key. You can custumize mask key generator.
-        Mainly, this is for testing purpose.
-
-        func: callable object. the fuct must 1 argument as integer.
-              The argument means length of mask key.
-              This func must be return string(byte array),
-              which length is argument specified.
-        """
-        self.get_mask_key = func
-
-    def gettimeout(self):
-        """
-        Get the websocket timeout(second).
-        """
-        return self.sock.gettimeout()
-
-    def settimeout(self, timeout):
-        """
-        Set the timeout to the websocket.
-
-        timeout: timeout time(second).
-        """
-        self.sock.settimeout(timeout)
-
-    timeout = property(gettimeout, settimeout)
-
-    def connect(self, url, **options):
-        """
-        Connect to url. url is websocket url scheme. ie. ws://host:port/resource
-        You can customize using 'options'.
-        If you set "header" dict object, you can set your own custom header.
-
-        >>> ws = WebSocket()
-        >>> ws.connect("ws://echo.websocket.org/",
-                ...     header={"User-Agent: MyProgram",
-                ...             "x-custom: header"})
-
-        timeout: socket timeout time. This value is integer.
-                 if you set None for this value,
-                 it means "use default_timeout value"
-
-        options: current support option is only "header".
-                 if you set header as dict value,
-                 the custom HTTP headers are added.
-
-        """
-        hostname, port, resource, is_secure = _parse_url(url)
-        # TODO: we need to support proxy
-        self.sock.connect((hostname, port))
-        if is_secure:
-            if HAVE_SSL:
-                if self.sslopt is None:
-                    sslopt = {}
-                else:
-                    sslopt = self.sslopt
-                self.sock = ssl.wrap_socket(self.sock, **sslopt)
-            else:
-                raise WebSocketException("SSL not available.")
-
-        self._handshake(hostname, port, resource, **options)
-
-    def _handshake(self, host, port, resource, **options):
-        sock = self.sock
-        headers = []
-        headers.append("GET %s HTTP/1.1" % resource)
-        headers.append("Upgrade: websocket")
-        headers.append("Connection: Upgrade")
-        if port == 80:
-            hostport = host
-        else:
-            hostport = "%s:%d" % (host, port)
-        headers.append("Host: %s" % hostport)
-
-        if "origin" in options:
-            headers.append("Origin: %s" % options["origin"])
-        else:
-            headers.append("Origin: http://%s" % hostport)
-
-        key = _create_sec_websocket_key()
-        headers.append("Sec-WebSocket-Key: %s" % key)
-        headers.append("Sec-WebSocket-Version: %s" % VERSION)
-        if "header" in options:
-            headers.extend(options["header"])
-
-        headers.append("")
-        headers.append("")
-
-        header_str = "\r\n".join(headers)
-        self._send(header_str)
-        if traceEnabled:
-            logger.debug("--- request header ---")
-            logger.debug(header_str)
-            logger.debug("-----------------------")
-
-        status, resp_headers = self._read_headers()
-        if status != 101:
-            self.close()
-            raise WebSocketException("Handshake Status %d" % status)
-
-        success = self._validate_header(resp_headers, key)
-        if not success:
-            self.close()
-            raise WebSocketException("Invalid WebSocket Header")
-
-        self.connected = True
-
-    def _validate_header(self, headers, key):
-        for k, v in _HEADERS_TO_CHECK.iteritems():
-            r = headers.get(k, None)
-            if not r:
-                return False
-            r = r.lower()
-            if v != r:
-                return False
-
-        result = headers.get("sec-websocket-accept", None)
-        if not result:
-            return False
-        result = result.lower()
-
-        value = key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
-        hashed = base64.encodestring(hashlib.sha1(value).digest()).strip().lower()
-        return hashed == result
-
-    def _read_headers(self):
-        status = None
-        headers = {}
-        if traceEnabled:
-            logger.debug("--- response header ---")
-
-        while True:
-            line = self._recv_line()
-            if line == "\r\n":
-                break
-            line = line.strip()
-            if traceEnabled:
-                logger.debug(line)
-            if not status:
-                status_info = line.split(" ", 2)
-                status = int(status_info[1])
-            else:
-                kv = line.split(":", 1)
-                if len(kv) == 2:
-                    key, value = kv
-                    headers[key.lower()] = value.strip().lower()
-                else:
-                    raise WebSocketException("Invalid header")
-
-        if traceEnabled:
-            logger.debug("-----------------------")
-
-        return status, headers
-
-    def send(self, payload, opcode=ABNF.OPCODE_TEXT):
-        """
-        Send the data as string.
-
-        payload: Payload must be utf-8 string or unicoce,
-                  if the opcode is OPCODE_TEXT.
-                  Otherwise, it must be string(byte array)
-
-        opcode: operation code to send. Please see OPCODE_XXX.
-        """
-        frame = ABNF.create_frame(payload, opcode)
-        if self.get_mask_key:
-            frame.get_mask_key = self.get_mask_key
-        data = frame.format()
-        length = len(data)
-        if traceEnabled:
-            logger.debug("send: " + repr(data))
-        while data:
-            l = self._send(data)
-            data = data[l:]
-        return length
-
-    def send_binary(self, payload):
-        return self.send(payload, ABNF.OPCODE_BINARY)
-
-    def ping(self, payload=""):
-        """
-        send ping data.
-
-        payload: data payload to send server.
-        """
-        self.send(payload, ABNF.OPCODE_PING)
-
-    def pong(self, payload):
-        """
-        send pong data.
-
-        payload: data payload to send server.
-        """
-        self.send(payload, ABNF.OPCODE_PONG)
-
-    def recv(self):
-        """
-        Receive string data(byte array) from the server.
-
-        return value: string(byte array) value.
-        """
-        opcode, data = self.recv_data()
-        return data
-
-    def recv_data(self):
-        """
-        Recieve data with operation code.
-
-        return  value: tuple of operation code and string(byte array) value.
-        """
-        while True:
-            frame = self.recv_frame()
-            if not frame:
-                # handle error:
-                # 'NoneType' object has no attribute 'opcode'
-                raise WebSocketException("Not a valid frame %s" % frame)
-            elif frame.opcode in (ABNF.OPCODE_TEXT, ABNF.OPCODE_BINARY, ABNF.OPCODE_CONT):
-                if frame.opcode == ABNF.OPCODE_CONT and not self._cont_data:
-                    raise WebSocketException("Illegal frame")
-                if self._cont_data:
-                    self._cont_data[1] += frame.data
-                else:
-                    self._cont_data = [frame.opcode, frame.data]
-                
-                if frame.fin:
-                    data = self._cont_data
-                    self._cont_data = None
-                    return data
-            elif frame.opcode == ABNF.OPCODE_CLOSE:
-                self.send_close()
-                return (frame.opcode, None)
-            elif frame.opcode == ABNF.OPCODE_PING:
-                self.pong(frame.data)
-
-    def recv_frame(self):
-        """
-        recieve data as frame from server.
-
-        return value: ABNF frame object.
-        """
-        # Header
-        if self._frame_header is None:
-            self._frame_header = self._recv_strict(2)
-        b1 = ord(self._frame_header[0])
-        fin = b1 >> 7 & 1
-        rsv1 = b1 >> 6 & 1
-        rsv2 = b1 >> 5 & 1
-        rsv3 = b1 >> 4 & 1
-        opcode = b1 & 0xf
-        b2 = ord(self._frame_header[1])
-        has_mask = b2 >> 7 & 1
-        # Frame length
-        if self._frame_length is None:
-            length_bits = b2 & 0x7f
-            if length_bits == 0x7e:
-                length_data = self._recv_strict(2)
-                self._frame_length = struct.unpack("!H", length_data)[0]
-            elif length_bits == 0x7f:
-                length_data = self._recv_strict(8)
-                self._frame_length = struct.unpack("!Q", length_data)[0]
-            else:
-                self._frame_length = length_bits
-        # Mask
-        if self._frame_mask is None:
-            self._frame_mask = self._recv_strict(4) if has_mask else ""
-        # Payload
-        payload = self._recv_strict(self._frame_length)
-        if has_mask:
-            payload = ABNF.mask(self._frame_mask, payload)
-        # Reset for next frame
-        self._frame_header = None
-        self._frame_length = None
-        self._frame_mask = None
-        return ABNF(fin, rsv1, rsv2, rsv3, opcode, has_mask, payload)
-
-
-    def send_close(self, status=STATUS_NORMAL, reason=""):
-        """
-        send close data to the server.
-
-        status: status code to send. see STATUS_XXX.
-
-        reason: the reason to close. This must be string.
-        """
-        if status < 0 or status >= ABNF.LENGTH_16:
-            raise ValueError("code is invalid range")
-        self.send(struct.pack('!H', status) + reason, ABNF.OPCODE_CLOSE)
-
-    def close(self, status=STATUS_NORMAL, reason=""):
-        """
-        Close Websocket object
-
-        status: status code to send. see STATUS_XXX.
-
-        reason: the reason to close. This must be string.
-        """
-        
-        try:
-            self.sock.shutdown(socket.SHUT_RDWR)
-        except:
-            pass
-            
-        '''
-        if self.connected:
-            if status < 0 or status >= ABNF.LENGTH_16:
-                raise ValueError("code is invalid range")
-
-            try:
-                self.send(struct.pack('!H', status) + reason, ABNF.OPCODE_CLOSE)
-                timeout = self.sock.gettimeout()
-                self.sock.settimeout(3)
-                try:
-                    frame = self.recv_frame()
-                    if logger.isEnabledFor(logging.ERROR):
-                        recv_status = struct.unpack("!H", frame.data)[0]
-                        if recv_status != STATUS_NORMAL:
-                            logger.error("close status: " + repr(recv_status))
-                except:
-                    pass
-                self.sock.settimeout(timeout)
-                self.sock.shutdown(socket.SHUT_RDWR)
-            except:
-                pass
-        '''
-        self._closeInternal()
-
-    def _closeInternal(self):
-        self.connected = False
-        self.sock.close()
-
-    def _send(self, data):
-        try:
-            return self.sock.send(data)
-        except socket.timeout as e:
-            raise WebSocketTimeoutException(e.args[0])
-        except Exception as e:
-            if "timed out" in e.args[0]:
-                raise WebSocketTimeoutException(e.args[0])
-            else:
-                raise e
-
-    def _recv(self, bufsize):
-        try:
-            bytes = self.sock.recv(bufsize)
-        except socket.timeout as e:
-            raise WebSocketTimeoutException(e.args[0])
-        except SSLError as e:
-            if e.args[0] == "The read operation timed out":
-                raise WebSocketTimeoutException(e.args[0])
-            else:
-                raise
-        if not bytes:
-            raise WebSocketConnectionClosedException()
-        return bytes
-
-
-    def _recv_strict(self, bufsize):
-        shortage = bufsize - sum(len(x) for x in self._recv_buffer)
-        while shortage > 0:
-            bytes = self._recv(shortage)
-            self._recv_buffer.append(bytes)
-            shortage -= len(bytes)
-        unified = "".join(self._recv_buffer)
-        if shortage == 0:
-            self._recv_buffer = []
-            return unified
-        else:
-            self._recv_buffer = [unified[bufsize:]]
-            return unified[:bufsize]
-
-
-    def _recv_line(self):
-        line = []
-        while True:
-            c = self._recv(1)
-            line.append(c)
-            if c == "\n":
-                break
-        return "".join(line)
-
-
-class WebSocketApp(object):
-    """
-    Higher level of APIs are provided.
-    The interface is like JavaScript WebSocket object.
-    """
-    def __init__(self, url, header=[],
-                 on_open=None, on_message=None, on_error=None,
-                 on_close=None, keep_running=True, get_mask_key=None):
-        """
-        url: websocket url.
-        header: custom header for websocket handshake.
-        on_open: callable object which is called at opening websocket.
-          this function has one argument. The arugment is this class object.
-        on_message: callbale object which is called when recieved data.
-         on_message has 2 arguments.
-         The 1st arugment is this class object.
-         The passing 2nd arugment is utf-8 string which we get from the server.
-       on_error: callable object which is called when we get error.
-         on_error has 2 arguments.
-         The 1st arugment is this class object.
-         The passing 2nd arugment is exception object.
-       on_close: callable object which is called when closed the connection.
-         this function has one argument. The arugment is this class object.
-       keep_running: a boolean flag indicating whether the app's main loop should
-         keep running, defaults to True
-       get_mask_key: a callable to produce new mask keys, see the WebSocket.set_mask_key's
-         docstring for more information
-        """
-        self.url = url
-        self.header = header
-        self.on_open = on_open
-        self.on_message = on_message
-        self.on_error = on_error
-        self.on_close = on_close
-        self.keep_running = keep_running
-        self.get_mask_key = get_mask_key
-        self.sock = None
-
-    def send(self, data, opcode=ABNF.OPCODE_TEXT):
-        """
-        send message.
-        data: message to send. If you set opcode to OPCODE_TEXT, data must be utf-8 string or unicode.
-        opcode: operation code of data. default is OPCODE_TEXT.
-        """
-        if self.sock.send(data, opcode) == 0:
-            raise WebSocketConnectionClosedException()
-
-    def close(self):
-        """
-        close websocket connection.
-        """
-        self.keep_running = False
-        if(self.sock != None):
-            self.sock.close()        
-
-    def _send_ping(self, interval):
-        while True:
-            for i in range(interval):
-                time.sleep(1)
-                if not self.keep_running:
-                    return
-            self.sock.ping()
-
-    def run_forever(self, sockopt=None, sslopt=None, ping_interval=0):
-        """
-        run event loop for WebSocket framework.
-        This loop is infinite loop and is alive during websocket is available.
-        sockopt: values for socket.setsockopt.
-            sockopt must be tuple and each element is argument of sock.setscokopt.
-        sslopt: ssl socket optional dict.
-        ping_interval: automatically send "ping" command every specified period(second)
-            if set to 0, not send automatically.
-        """
-        if sockopt is None:
-            sockopt = []
-        if sslopt is None:
-            sslopt = {}
-        if self.sock:
-            raise WebSocketException("socket is already opened")
-        thread = None
-        self.keep_running = True
-
-        try:
-            self.sock = WebSocket(self.get_mask_key, sockopt=sockopt, sslopt=sslopt)
-            self.sock.settimeout(default_timeout)
-            self.sock.connect(self.url, header=self.header)
-            self._callback(self.on_open)
-
-            if ping_interval:
-                thread = threading.Thread(target=self._send_ping, args=(ping_interval,))
-                thread.setDaemon(True)
-                thread.start()
-
-            while self.keep_running:
-            
-                try:
-                    data = self.sock.recv()
-                    
-                    if data is None or self.keep_running == False:
-                        break
-                    self._callback(self.on_message, data)                    
-                    
-                except Exception, e:
-                    #print str(e.args[0])
-                    if "timed out" not in e.args[0]:
-                        raise e
-
-        except Exception, e:
-            self._callback(self.on_error, e)
-        finally:
-            if thread:
-                self.keep_running = False
-            self.sock.close()
-            self._callback(self.on_close)
-            self.sock = None
-
-    def _callback(self, callback, *args):
-        if callback:
-            try:
-                callback(self, *args)
-            except Exception, e:
-                logger.error(e)
-                if True:#logger.isEnabledFor(logging.DEBUG):
-                    _, _, tb = sys.exc_info()
-                    traceback.print_tb(tb)
-
-
-if __name__ == "__main__":
-    enableTrace(True)
-    ws = create_connection("ws://echo.websocket.org/")
-    print("Sending 'Hello, World'...")
-    ws.send("Hello, World")
-    print("Sent")
-    print("Receiving...")
-    result = ws.recv()
-    print("Received '%s'" % result)
-    ws.close()
+"""
+websocket - WebSocket client library for Python
+
+Copyright (C) 2010 Hiroki Ohtani(liris)
+
+    This library is free software; you can redistribute it and/or
+    modify it under the terms of the GNU Lesser General Public
+    License as published by the Free Software Foundation; either
+    version 2.1 of the License, or (at your option) any later version.
+
+    This library is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+    Lesser General Public License for more details.
+
+    You should have received a copy of the GNU Lesser General Public
+    License along with this library; if not, write to the Free Software
+    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+"""
+
+
+import socket
+
+try:
+    import ssl
+    from ssl import SSLError
+    HAVE_SSL = True
+except ImportError:
+    # dummy class of SSLError for ssl none-support environment.
+    class SSLError(Exception):
+        pass
+
+    HAVE_SSL = False
+
+from urlparse import urlparse
+import os
+import array
+import struct
+import uuid
+import hashlib
+import base64
+import threading
+import time
+import logging
+import traceback
+import sys
+
+"""
+websocket python client.
+=========================
+
+This version support only hybi-13.
+Please see http://tools.ietf.org/html/rfc6455 for protocol.
+"""
+
+
+# websocket supported version.
+VERSION = 13
+
+# closing frame status codes.
+STATUS_NORMAL = 1000
+STATUS_GOING_AWAY = 1001
+STATUS_PROTOCOL_ERROR = 1002
+STATUS_UNSUPPORTED_DATA_TYPE = 1003
+STATUS_STATUS_NOT_AVAILABLE = 1005
+STATUS_ABNORMAL_CLOSED = 1006
+STATUS_INVALID_PAYLOAD = 1007
+STATUS_POLICY_VIOLATION = 1008
+STATUS_MESSAGE_TOO_BIG = 1009
+STATUS_INVALID_EXTENSION = 1010
+STATUS_UNEXPECTED_CONDITION = 1011
+STATUS_TLS_HANDSHAKE_ERROR = 1015
+
+logger = logging.getLogger()
+
+
+class WebSocketException(Exception):
+    """
+    websocket exeception class.
+    """
+    pass
+
+
+class WebSocketConnectionClosedException(WebSocketException):
+    """
+    If remote host closed the connection or some network error happened,
+    this exception will be raised.
+    """
+    pass
+
+class WebSocketTimeoutException(WebSocketException):
+    """
+    WebSocketTimeoutException will be raised at socket timeout during read/write data.
+    """
+    pass
+
+default_timeout = None
+traceEnabled = False
+
+
+def enableTrace(tracable):
+    """
+    turn on/off the tracability.
+
+    tracable: boolean value. if set True, tracability is enabled.
+    """
+    global traceEnabled
+    traceEnabled = tracable
+    if tracable:
+        if not logger.handlers:
+            logger.addHandler(logging.StreamHandler())
+        logger.setLevel(logging.DEBUG)
+
+
+def setdefaulttimeout(timeout):
+    """
+    Set the global timeout setting to connect.
+
+    timeout: default socket timeout time. This value is second.
+    """
+    global default_timeout
+    default_timeout = timeout
+
+
+def getdefaulttimeout():
+    """
+    Return the global timeout setting(second) to connect.
+    """
+    return default_timeout
+
+
+def _parse_url(url):
+    """
+    parse url and the result is tuple of
+    (hostname, port, resource path and the flag of secure mode)
+
+    url: url string.
+    """
+    if ":" not in url:
+        raise ValueError("url is invalid")
+
+    scheme, url = url.split(":", 1)
+
+    parsed = urlparse(url, scheme="http")
+    if parsed.hostname:
+        hostname = parsed.hostname
+    else:
+        raise ValueError("hostname is invalid")
+    port = 0
+    if parsed.port:
+        port = parsed.port
+
+    is_secure = False
+    if scheme == "ws":
+        if not port:
+            port = 80
+    elif scheme == "wss":
+        is_secure = True
+        if not port:
+            port = 443
+    else:
+        raise ValueError("scheme %s is invalid" % scheme)
+
+    if parsed.path:
+        resource = parsed.path
+    else:
+        resource = "/"
+
+    if parsed.query:
+        resource += "?" + parsed.query
+
+    return (hostname, port, resource, is_secure)
+
+
+def create_connection(url, timeout=None, **options):
+    """
+    connect to url and return websocket object.
+
+    Connect to url and return the WebSocket object.
+    Passing optional timeout parameter will set the timeout on the socket.
+    If no timeout is supplied, the global default timeout setting returned by getdefauttimeout() is used.
+    You can customize using 'options'.
+    If you set "header" list object, you can set your own custom header.
+
+    >>> conn = create_connection("ws://echo.websocket.org/",
+         ...     header=["User-Agent: MyProgram",
+         ...             "x-custom: header"])
+
+
+    timeout: socket timeout time. This value is integer.
+             if you set None for this value, it means "use default_timeout value"
+
+    options: current support option is only "header".
+             if you set header as dict value, the custom HTTP headers are added.
+    """
+    sockopt = options.get("sockopt", [])
+    sslopt = options.get("sslopt", {})
+    websock = WebSocket(sockopt=sockopt, sslopt=sslopt)
+    websock.settimeout(timeout if timeout is not None else default_timeout)
+    websock.connect(url, **options)
+    return websock
+
+_MAX_INTEGER = (1 << 32) -1
+_AVAILABLE_KEY_CHARS = range(0x21, 0x2f + 1) + range(0x3a, 0x7e + 1)
+_MAX_CHAR_BYTE = (1<<8) -1
+
+# ref. Websocket gets an update, and it breaks stuff.
+# http://axod.blogspot.com/2010/06/websocket-gets-update-and-it-breaks.html
+
+
+def _create_sec_websocket_key():
+    uid = uuid.uuid4()
+    return base64.encodestring(uid.bytes).strip()
+
+
+_HEADERS_TO_CHECK = {
+    "upgrade": "websocket",
+    "connection": "upgrade",
+    }
+
+
+class ABNF(object):
+    """
+    ABNF frame class.
+    see http://tools.ietf.org/html/rfc5234
+    and http://tools.ietf.org/html/rfc6455#section-5.2
+    """
+
+    # operation code values.
+    OPCODE_CONT   = 0x0
+    OPCODE_TEXT   = 0x1
+    OPCODE_BINARY = 0x2
+    OPCODE_CLOSE  = 0x8
+    OPCODE_PING   = 0x9
+    OPCODE_PONG   = 0xa
+
+    # available operation code value tuple
+    OPCODES = (OPCODE_CONT, OPCODE_TEXT, OPCODE_BINARY, OPCODE_CLOSE,
+                OPCODE_PING, OPCODE_PONG)
+
+    # opcode human readable string
+    OPCODE_MAP = {
+        OPCODE_CONT: "cont",
+        OPCODE_TEXT: "text",
+        OPCODE_BINARY: "binary",
+        OPCODE_CLOSE: "close",
+        OPCODE_PING: "ping",
+        OPCODE_PONG: "pong"
+        }
+
+    # data length threashold.
+    LENGTH_7  = 0x7d
+    LENGTH_16 = 1 << 16
+    LENGTH_63 = 1 << 63
+
+    def __init__(self, fin=0, rsv1=0, rsv2=0, rsv3=0,
+                 opcode=OPCODE_TEXT, mask=1, data=""):
+        """
+        Constructor for ABNF.
+        please check RFC for arguments.
+        """
+        self.fin = fin
+        self.rsv1 = rsv1
+        self.rsv2 = rsv2
+        self.rsv3 = rsv3
+        self.opcode = opcode
+        self.mask = mask
+        self.data = data
+        self.get_mask_key = os.urandom
+
+    def __str__(self):
+        return "fin=" + str(self.fin) \
+                + " opcode=" + str(self.opcode) \
+                + " data=" + str(self.data)
+
+    @staticmethod
+    def create_frame(data, opcode):
+        """
+        create frame to send text, binary and other data.
+
+        data: data to send. This is string value(byte array).
+            if opcode is OPCODE_TEXT and this value is uniocde,
+            data value is conveted into unicode string, automatically.
+
+        opcode: operation code. please see OPCODE_XXX.
+        """
+        if opcode == ABNF.OPCODE_TEXT and isinstance(data, unicode):
+            data = data.encode("utf-8")
+        # mask must be set if send data from client
+        return ABNF(1, 0, 0, 0, opcode, 1, data)
+
+    def format(self):
+        """
+        format this object to string(byte array) to send data to server.
+        """
+        if any(x not in (0, 1) for x in [self.fin, self.rsv1, self.rsv2, self.rsv3]):
+            raise ValueError("not 0 or 1")
+        if self.opcode not in ABNF.OPCODES:
+            raise ValueError("Invalid OPCODE")
+        length = len(self.data)
+        if length >= ABNF.LENGTH_63:
+            raise ValueError("data is too long")
+
+        frame_header = chr(self.fin << 7
+                           | self.rsv1 << 6 | self.rsv2 << 5 | self.rsv3 << 4
+                           | self.opcode)
+        if length < ABNF.LENGTH_7:
+            frame_header += chr(self.mask << 7 | length)
+        elif length < ABNF.LENGTH_16:
+            frame_header += chr(self.mask << 7 | 0x7e)
+            frame_header += struct.pack("!H", length)
+        else:
+            frame_header += chr(self.mask << 7 | 0x7f)
+            frame_header += struct.pack("!Q", length)
+
+        if not self.mask:
+            return frame_header + self.data
+        else:
+            mask_key = self.get_mask_key(4)
+            return frame_header + self._get_masked(mask_key)
+
+    def _get_masked(self, mask_key):
+        s = ABNF.mask(mask_key, self.data)
+        return mask_key + "".join(s)
+
+    @staticmethod
+    def mask(mask_key, data):
+        """
+        mask or unmask data. Just do xor for each byte
+
+        mask_key: 4 byte string(byte).
+
+        data: data to mask/unmask.
+        """
+        _m = array.array("B", mask_key)
+        _d = array.array("B", data)
+        for i in xrange(len(_d)):
+            _d[i] ^= _m[i % 4]
+        return _d.tostring()
+
+
+class WebSocket(object):
+    """
+    Low level WebSocket interface.
+    This class is based on
+      The WebSocket protocol draft-hixie-thewebsocketprotocol-76
+      http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76
+
+    We can connect to the websocket server and send/recieve data.
+    The following example is a echo client.
+
+    >>> import websocket
+    >>> ws = websocket.WebSocket()
+    >>> ws.connect("ws://echo.websocket.org")
+    >>> ws.send("Hello, Server")
+    >>> ws.recv()
+    'Hello, Server'
+    >>> ws.close()
+
+    get_mask_key: a callable to produce new mask keys, see the set_mask_key
+      function's docstring for more details
+    sockopt: values for socket.setsockopt.
+        sockopt must be tuple and each element is argument of sock.setscokopt.
+    sslopt: dict object for ssl socket option.
+    """
+
+    def __init__(self, get_mask_key=None, sockopt=None, sslopt=None):
+        """
+        Initalize WebSocket object.
+        """
+        if sockopt is None:
+            sockopt = []
+        if sslopt is None:
+            sslopt = {}
+        self.connected = False
+        self.sock = socket.socket()
+        for opts in sockopt:
+            self.sock.setsockopt(*opts)
+        self.sslopt = sslopt
+        self.get_mask_key = get_mask_key
+        # Buffers over the packets from the layer beneath until desired amount
+        # bytes of bytes are received.
+        self._recv_buffer = []
+        # These buffer over the build-up of a single frame.
+        self._frame_header = None
+        self._frame_length = None
+        self._frame_mask = None
+        self._cont_data = None
+
+    def fileno(self):
+        return self.sock.fileno()
+
+    def set_mask_key(self, func):
+        """
+        set function to create musk key. You can custumize mask key generator.
+        Mainly, this is for testing purpose.
+
+        func: callable object. the fuct must 1 argument as integer.
+              The argument means length of mask key.
+              This func must be return string(byte array),
+              which length is argument specified.
+        """
+        self.get_mask_key = func
+
+    def gettimeout(self):
+        """
+        Get the websocket timeout(second).
+        """
+        return self.sock.gettimeout()
+
+    def settimeout(self, timeout):
+        """
+        Set the timeout to the websocket.
+
+        timeout: timeout time(second).
+        """
+        self.sock.settimeout(timeout)
+
+    timeout = property(gettimeout, settimeout)
+
+    def connect(self, url, **options):
+        """
+        Connect to url. url is websocket url scheme. ie. ws://host:port/resource
+        You can customize using 'options'.
+        If you set "header" dict object, you can set your own custom header.
+
+        >>> ws = WebSocket()
+        >>> ws.connect("ws://echo.websocket.org/",
+                ...     header={"User-Agent: MyProgram",
+                ...             "x-custom: header"})
+
+        timeout: socket timeout time. This value is integer.
+                 if you set None for this value,
+                 it means "use default_timeout value"
+
+        options: current support option is only "header".
+                 if you set header as dict value,
+                 the custom HTTP headers are added.
+
+        """
+        hostname, port, resource, is_secure = _parse_url(url)
+        # TODO: we need to support proxy
+        self.sock.connect((hostname, port))
+        if is_secure:
+            if HAVE_SSL:
+                if self.sslopt is None:
+                    sslopt = {}
+                else:
+                    sslopt = self.sslopt
+                self.sock = ssl.wrap_socket(self.sock, **sslopt)
+            else:
+                raise WebSocketException("SSL not available.")
+
+        self._handshake(hostname, port, resource, **options)
+
+    def _handshake(self, host, port, resource, **options):
+        headers = []
+        headers.append("GET %s HTTP/1.1" % resource)
+        headers.append("Upgrade: websocket")
+        headers.append("Connection: Upgrade")
+        if port == 80:
+            hostport = host
+        else:
+            hostport = "%s:%d" % (host, port)
+        headers.append("Host: %s" % hostport)
+
+        if "origin" in options:
+            headers.append("Origin: %s" % options["origin"])
+        else:
+            headers.append("Origin: http://%s" % hostport)
+
+        key = _create_sec_websocket_key()
+        headers.append("Sec-WebSocket-Key: %s" % key)
+        headers.append("Sec-WebSocket-Version: %s" % VERSION)
+        if "header" in options:
+            headers.extend(options["header"])
+
+        headers.append("")
+        headers.append("")
+
+        header_str = "\r\n".join(headers)
+        self._send(header_str)
+        if traceEnabled:
+            logger.debug("--- request header ---")
+            logger.debug(header_str)
+            logger.debug("-----------------------")
+
+        status, resp_headers = self._read_headers()
+        if status != 101:
+            self.close()
+            raise WebSocketException("Handshake Status %d" % status)
+
+        success = self._validate_header(resp_headers, key)
+        if not success:
+            self.close()
+            raise WebSocketException("Invalid WebSocket Header")
+
+        self.connected = True
+
+    def _validate_header(self, headers, key):
+        for k, v in _HEADERS_TO_CHECK.iteritems():
+            r = headers.get(k, None)
+            if not r:
+                return False
+            r = r.lower()
+            if v != r:
+                return False
+
+        result = headers.get("sec-websocket-accept", None)
+        if not result:
+            return False
+        result = result.lower()
+
+        value = key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
+        hashed = base64.encodestring(hashlib.sha1(value).digest()).strip().lower()
+        return hashed == result
+
+    def _read_headers(self):
+        status = None
+        headers = {}
+        if traceEnabled:
+            logger.debug("--- response header ---")
+
+        while True:
+            line = self._recv_line()
+            if line == "\r\n":
+                break
+            line = line.strip()
+            if traceEnabled:
+                logger.debug(line)
+            if not status:
+                status_info = line.split(" ", 2)
+                status = int(status_info[1])
+            else:
+                kv = line.split(":", 1)
+                if len(kv) == 2:
+                    key, value = kv
+                    headers[key.lower()] = value.strip().lower()
+                else:
+                    raise WebSocketException("Invalid header")
+
+        if traceEnabled:
+            logger.debug("-----------------------")
+
+        return status, headers
+
+    def send(self, payload, opcode=ABNF.OPCODE_TEXT):
+        """
+        Send the data as string.
+
+        payload: Payload must be utf-8 string or unicoce,
+                  if the opcode is OPCODE_TEXT.
+                  Otherwise, it must be string(byte array)
+
+        opcode: operation code to send. Please see OPCODE_XXX.
+        """
+        frame = ABNF.create_frame(payload, opcode)
+        if self.get_mask_key:
+            frame.get_mask_key = self.get_mask_key
+        data = frame.format()
+        length = len(data)
+        if traceEnabled:
+            logger.debug("send: " + repr(data))
+        while data:
+            l = self._send(data)
+            data = data[l:]
+        return length
+
+    def send_binary(self, payload):
+        return self.send(payload, ABNF.OPCODE_BINARY)
+
+    def ping(self, payload=""):
+        """
+        send ping data.
+
+        payload: data payload to send server.
+        """
+        self.send(payload, ABNF.OPCODE_PING)
+
+    def pong(self, payload):
+        """
+        send pong data.
+
+        payload: data payload to send server.
+        """
+        self.send(payload, ABNF.OPCODE_PONG)
+
+    def recv(self):
+        """
+        Receive string data(byte array) from the server.
+
+        return value: string(byte array) value.
+        """
+        opcode, data = self.recv_data()
+        return data
+
+    def recv_data(self):
+        """
+        Recieve data with operation code.
+
+        return  value: tuple of operation code and string(byte array) value.
+        """
+        while True:
+            frame = self.recv_frame()
+            if not frame:
+                # handle error:
+                # 'NoneType' object has no attribute 'opcode'
+                raise WebSocketException("Not a valid frame %s" % frame)
+            elif frame.opcode in (ABNF.OPCODE_TEXT, ABNF.OPCODE_BINARY, ABNF.OPCODE_CONT):
+                if frame.opcode == ABNF.OPCODE_CONT and not self._cont_data:
+                    raise WebSocketException("Illegal frame")
+                if self._cont_data:
+                    self._cont_data[1] += frame.data
+                else:
+                    self._cont_data = [frame.opcode, frame.data]
+                
+                if frame.fin:
+                    data = self._cont_data
+                    self._cont_data = None
+                    return data
+            elif frame.opcode == ABNF.OPCODE_CLOSE:
+                self.send_close()
+                return (frame.opcode, None)
+            elif frame.opcode == ABNF.OPCODE_PING:
+                self.pong(frame.data)
+
+    def recv_frame(self):
+        """
+        recieve data as frame from server.
+
+        return value: ABNF frame object.
+        """
+        # Header
+        if self._frame_header is None:
+            self._frame_header = self._recv_strict(2)
+        b1 = ord(self._frame_header[0])
+        fin = b1 >> 7 & 1
+        rsv1 = b1 >> 6 & 1
+        rsv2 = b1 >> 5 & 1
+        rsv3 = b1 >> 4 & 1
+        opcode = b1 & 0xf
+        b2 = ord(self._frame_header[1])
+        has_mask = b2 >> 7 & 1
+        # Frame length
+        if self._frame_length is None:
+            length_bits = b2 & 0x7f
+            if length_bits == 0x7e:
+                length_data = self._recv_strict(2)
+                self._frame_length = struct.unpack("!H", length_data)[0]
+            elif length_bits == 0x7f:
+                length_data = self._recv_strict(8)
+                self._frame_length = struct.unpack("!Q", length_data)[0]
+            else:
+                self._frame_length = length_bits
+        # Mask
+        if self._frame_mask is None:
+            self._frame_mask = self._recv_strict(4) if has_mask else ""
+        # Payload
+        payload = self._recv_strict(self._frame_length)
+        if has_mask:
+            payload = ABNF.mask(self._frame_mask, payload)
+        # Reset for next frame
+        self._frame_header = None
+        self._frame_length = None
+        self._frame_mask = None
+        return ABNF(fin, rsv1, rsv2, rsv3, opcode, has_mask, payload)
+
+
+    def send_close(self, status=STATUS_NORMAL, reason=""):
+        """
+        send close data to the server.
+
+        status: status code to send. see STATUS_XXX.
+
+        reason: the reason to close. This must be string.
+        """
+        if status < 0 or status >= ABNF.LENGTH_16:
+            raise ValueError("code is invalid range")
+        self.send(struct.pack('!H', status) + reason, ABNF.OPCODE_CLOSE)
+
+    def close(self, status=STATUS_NORMAL, reason=""):
+        """
+        Close Websocket object
+
+        status: status code to send. see STATUS_XXX.
+
+        reason: the reason to close. This must be string.
+        """
+        
+        try:
+            self.sock.shutdown(socket.SHUT_RDWR)
+        except:
+            pass
+            
+        '''
+        if self.connected:
+            if status < 0 or status >= ABNF.LENGTH_16:
+                raise ValueError("code is invalid range")
+
+            try:
+                self.send(struct.pack('!H', status) + reason, ABNF.OPCODE_CLOSE)
+                timeout = self.sock.gettimeout()
+                self.sock.settimeout(3)
+                try:
+                    frame = self.recv_frame()
+                    if logger.isEnabledFor(logging.ERROR):
+                        recv_status = struct.unpack("!H", frame.data)[0]
+                        if recv_status != STATUS_NORMAL:
+                            logger.error("close status: " + repr(recv_status))
+                except:
+                    pass
+                self.sock.settimeout(timeout)
+                self.sock.shutdown(socket.SHUT_RDWR)
+            except:
+                pass
+        '''
+        self._closeInternal()
+
+    def _closeInternal(self):
+        self.connected = False
+        self.sock.close()
+
+    def _send(self, data):
+        try:
+            return self.sock.send(data)
+        except socket.timeout as e:
+            raise WebSocketTimeoutException(e.args[0])
+        except Exception as e:
+            if "timed out" in e.args[0]:
+                raise WebSocketTimeoutException(e.args[0])
+            else:
+                raise e
+
+    def _recv(self, bufsize):
+        try:
+            bytes = self.sock.recv(bufsize)
+        except socket.timeout as e:
+            raise WebSocketTimeoutException(e.args[0])
+        except SSLError as e:
+            if e.args[0] == "The read operation timed out":
+                raise WebSocketTimeoutException(e.args[0])
+            else:
+                raise
+        if not bytes:
+            raise WebSocketConnectionClosedException()
+        return bytes
+
+
+    def _recv_strict(self, bufsize):
+        shortage = bufsize - sum(len(x) for x in self._recv_buffer)
+        while shortage > 0:
+            bytes = self._recv(shortage)
+            self._recv_buffer.append(bytes)
+            shortage -= len(bytes)
+        unified = "".join(self._recv_buffer)
+        if shortage == 0:
+            self._recv_buffer = []
+            return unified
+        else:
+            self._recv_buffer = [unified[bufsize:]]
+            return unified[:bufsize]
+
+
+    def _recv_line(self):
+        line = []
+        while True:
+            c = self._recv(1)
+            line.append(c)
+            if c == "\n":
+                break
+        return "".join(line)
+
+
+class WebSocketApp(object):
+    """
+    Higher level of APIs are provided.
+    The interface is like JavaScript WebSocket object.
+    """
+    def __init__(self, url, header=[],
+                 on_open=None, on_message=None, on_error=None,
+                 on_close=None, keep_running=True, get_mask_key=None):
+        """
+        url: websocket url.
+        header: custom header for websocket handshake.
+        on_open: callable object which is called at opening websocket.
+          this function has one argument. The arugment is this class object.
+        on_message: callbale object which is called when recieved data.
+         on_message has 2 arguments.
+         The 1st arugment is this class object.
+         The passing 2nd arugment is utf-8 string which we get from the server.
+       on_error: callable object which is called when we get error.
+         on_error has 2 arguments.
+         The 1st arugment is this class object.
+         The passing 2nd arugment is exception object.
+       on_close: callable object which is called when closed the connection.
+         this function has one argument. The arugment is this class object.
+       keep_running: a boolean flag indicating whether the app's main loop should
+         keep running, defaults to True
+       get_mask_key: a callable to produce new mask keys, see the WebSocket.set_mask_key's
+         docstring for more information
+        """
+        self.url = url
+        self.header = header
+        self.on_open = on_open
+        self.on_message = on_message
+        self.on_error = on_error
+        self.on_close = on_close
+        self.keep_running = keep_running
+        self.get_mask_key = get_mask_key
+        self.sock = None
+
+    def send(self, data, opcode=ABNF.OPCODE_TEXT):
+        """
+        send message.
+        data: message to send. If you set opcode to OPCODE_TEXT, data must be utf-8 string or unicode.
+        opcode: operation code of data. default is OPCODE_TEXT.
+        """
+        if self.sock.send(data, opcode) == 0:
+            raise WebSocketConnectionClosedException()
+
+    def close(self):
+        """
+        close websocket connection.
+        """
+        self.keep_running = False
+        if(self.sock != None):
+            self.sock.close()        
+
+    def _send_ping(self, interval):
+        while True:
+            for i in range(interval):
+                time.sleep(1)
+                if not self.keep_running:
+                    return
+            self.sock.ping()
+
+    def run_forever(self, sockopt=None, sslopt=None, ping_interval=0):
+        """
+        run event loop for WebSocket framework.
+        This loop is infinite loop and is alive during websocket is available.
+        sockopt: values for socket.setsockopt.
+            sockopt must be tuple and each element is argument of sock.setscokopt.
+        sslopt: ssl socket optional dict.
+        ping_interval: automatically send "ping" command every specified period(second)
+            if set to 0, not send automatically.
+        """
+        if sockopt is None:
+            sockopt = []
+        if sslopt is None:
+            sslopt = {}
+        if self.sock:
+            raise WebSocketException("socket is already opened")
+        thread = None
+        self.keep_running = True
+
+        try:
+            self.sock = WebSocket(self.get_mask_key, sockopt=sockopt, sslopt=sslopt)
+            self.sock.settimeout(default_timeout)
+            self.sock.connect(self.url, header=self.header)
+            self._callback(self.on_open)
+
+            if ping_interval:
+                thread = threading.Thread(target=self._send_ping, args=(ping_interval,))
+                thread.setDaemon(True)
+                thread.start()
+
+            while self.keep_running:
+            
+                try:
+                    data = self.sock.recv()
+                    
+                    if data is None or self.keep_running == False:
+                        break
+                    self._callback(self.on_message, data)                    
+                    
+                except Exception, e:
+                    #print str(e.args[0])
+                    if "timed out" not in e.args[0]:
+                        raise e
+
+        except Exception, e:
+            self._callback(self.on_error, e)
+        finally:
+            if thread:
+                self.keep_running = False
+            self.sock.close()
+            self._callback(self.on_close)
+            self.sock = None
+
+    def _callback(self, callback, *args):
+        if callback:
+            try:
+                callback(self, *args)
+            except Exception, e:
+                logger.error(e)
+                if True:#logger.isEnabledFor(logging.DEBUG):
+                    _, _, tb = sys.exc_info()
+                    traceback.print_tb(tb)
+
+
+if __name__ == "__main__":
+    enableTrace(True)
+    ws = create_connection("ws://echo.websocket.org/")
+    print("Sending 'Hello, World'...")
+    ws.send("Hello, World")
+    print("Sent")
+    print("Receiving...")
+    result = ws.recv()
+    print("Received '%s'" % result)
+    ws.close()
diff --git a/resources/lib/websocket_client.py b/resources/lib/websocket_client.py
index acf0df36..559cb152 100644
--- a/resources/lib/websocket_client.py
+++ b/resources/lib/websocket_client.py
@@ -1,327 +1,319 @@
-# -*- coding: utf-8 -*-
-
-#################################################################################################
-
-import json
-import threading
-import websocket
-
-import xbmc
-import xbmcgui
-
-import clientinfo
-import downloadutils
-import librarysync
-import playlist
-import userclient
-import utils
-
-import logging
-logging.basicConfig()
-
-#################################################################################################
-
-
-class WebSocket_Client(threading.Thread):
-
-    _shared_state = {}
-
-    client = None
-    stopWebsocket = False
-
-
-    def __init__(self):
-
-        self.__dict__ = self._shared_state
-        self.monitor = xbmc.Monitor()
-        
-        self.doUtils = downloadutils.DownloadUtils()
-        self.clientInfo = clientinfo.ClientInfo()
-        self.addonName = self.clientInfo.getAddonName()
-        self.deviceId = self.clientInfo.getDeviceId()
-        self.librarySync = librarysync.LibrarySync()
-        
-        threading.Thread.__init__(self)
-
-    def logMsg(self, msg, lvl=1):
-
-        self.className = self.__class__.__name__
-        utils.logMsg("%s %s" % (self.addonName, self.className), msg, lvl)
-
-
-    def sendProgressUpdate(self, data):
-        
-        log = self.logMsg
-
-        log("sendProgressUpdate", 2)
-        try:
-            messageData = {
-
-                'MessageType': "ReportPlaybackProgress",
-                'Data': data
-            }
-            messageString = json.dumps(messageData)
-            self.client.send(messageString)
-            log("Message data: %s" % messageString, 2)
-
-        except Exception as e:
-            log("Exception: %s" % e, 1)
-
-    def on_message(self, ws, message):
-        
-        log = self.logMsg
-        window = utils.window
-        lang = utils.language
-
-        result = json.loads(message)
-        messageType = result['MessageType']
-        data = result['Data']
-
-        if messageType not in ('SessionEnded'):
-            # Mute certain events
-            log("Message: %s" % message, 1)
-
-        if messageType == "Play":
-            # A remote control play command has been sent from the server.
-            itemIds = data['ItemIds']
-            command = data['PlayCommand']
-
-            pl = playlist.Playlist()
-            dialog = xbmcgui.Dialog()
-
-            if command == "PlayNow":
-                dialog.notification(
-                        heading="Emby for Kodi",
-                        message="%s %s" % (len(itemIds), lang(33004)),
-                        icon="special://home/addons/plugin.video.emby/icon.png",
-                        sound=False)
-                startat = data.get('StartPositionTicks', 0)
-                pl.playAll(itemIds, startat)
-
-            elif command == "PlayNext":
-                dialog.notification(
-                        heading="Emby for Kodi",
-                        message="%s %s" % (len(itemIds), lang(33005)),
-                        icon="special://home/addons/plugin.video.emby/icon.png",
-                        sound=False)
-                newplaylist = pl.modifyPlaylist(itemIds)
-                player = xbmc.Player()
-                if not player.isPlaying():
-                    # Only start the playlist if nothing is playing
-                    player.play(newplaylist)
-
-        elif messageType == "Playstate":
-            # A remote control update playstate command has been sent from the server.
-            command = data['Command']
-            player = xbmc.Player()
-
-            actions = {
-
-                'Stop': player.stop,
-                'Unpause': player.pause,
-                'Pause': player.pause,
-                'NextTrack': player.playnext,
-                'PreviousTrack': player.playprevious,
-                'Seek': player.seekTime
-            }
-            action = actions[command]
-            if command == "Seek":
-                seekto = data['SeekPositionTicks']
-                seektime = seekto / 10000000.0
-                action(seektime)
-                log("Seek to %s." % seektime, 1)
-            else:
-                action()
-                log("Command: %s completed." % command, 1)
-
-            window('emby_command', value="true")
-
-        elif messageType == "UserDataChanged":
-            # A user changed their personal rating for an item, or their playstate was updated
-            userdata_list = data['UserDataList']
-            self.librarySync.triage_items("userdata", userdata_list)
-
-        elif messageType == "LibraryChanged":
-            
-            librarySync = self.librarySync
-            processlist = {
-
-                'added': data['ItemsAdded'],
-                'update': data['ItemsUpdated'],
-                'remove': data['ItemsRemoved']
-            }
-            for action in processlist:
-                librarySync.triage_items(action, processlist[action])
-
-        elif messageType == "GeneralCommand":
-            
-            command = data['Name']
-            arguments = data['Arguments']
-
-            if command in ('Mute', 'Unmute', 'SetVolume',
-                            'SetSubtitleStreamIndex', 'SetAudioStreamIndex'):
-
-                player = xbmc.Player()
-                # These commands need to be reported back
-                if command == "Mute":
-                    xbmc.executebuiltin('Mute')
-                elif command == "Unmute":
-                    xbmc.executebuiltin('Mute')
-                elif command == "SetVolume":
-                    volume = arguments['Volume']
-                    xbmc.executebuiltin('SetVolume(%s[,showvolumebar])' % volume)
-                elif command == "SetAudioStreamIndex":
-                    index = int(arguments['Index'])
-                    player.setAudioStream(index - 1)
-                elif command == "SetSubtitleStreamIndex":
-                    embyindex = int(arguments['Index'])
-                    currentFile = player.getPlayingFile()
-
-                    mapping = window('emby_%s.indexMapping' % currentFile)
-                    if mapping:
-                        externalIndex = json.loads(mapping)
-                        # If there's external subtitles added via playbackutils
-                        for index in externalIndex:
-                            if externalIndex[index] == embyindex:
-                                player.setSubtitleStream(int(index))
-                                break
-                        else:
-                            # User selected internal subtitles
-                            external = len(externalIndex)
-                            audioTracks = len(player.getAvailableAudioStreams())
-                            player.setSubtitleStream(external + embyindex - audioTracks - 1)
-                    else:
-                        # Emby merges audio and subtitle index together
-                        audioTracks = len(player.getAvailableAudioStreams())
-                        player.setSubtitleStream(index - audioTracks - 1)
-
-                # Let service know
-                window('emby_command', value="true")
-
-            elif command == "DisplayMessage":
-                
-                header = arguments['Header']
-                text = arguments['Text']
-                xbmcgui.Dialog().notification(
-                                    heading=header,
-                                    message=text,
-                                    icon="special://home/addons/plugin.video.emby/icon.png",
-                                    time=4000)
-
-            elif command == "SendString":
-                
-                string = arguments['String']
-                text = {
-
-                    'jsonrpc': "2.0",
-                    'id': 0,
-                    'method': "Input.SendText",
-                    'params': {
-
-                        'text': "%s" % string,
-                        'done': False
-                    }
-                }
-                result = xbmc.executeJSONRPC(json.dumps(text))
-
-            else:
-                builtin = {
-
-                    'ToggleFullscreen': 'Action(FullScreen)',
-                    'ToggleOsdMenu': 'Action(OSD)',
-                    'ToggleContextMenu': 'Action(ContextMenu)',
-                    'MoveUp': 'Action(Up)',
-                    'MoveDown': 'Action(Down)',
-                    'MoveLeft': 'Action(Left)',
-                    'MoveRight': 'Action(Right)',
-                    'Select': 'Action(Select)',
-                    'Back': 'Action(back)',
-                    'GoHome': 'ActivateWindow(Home)',
-                    'PageUp': 'Action(PageUp)',
-                    'NextLetter': 'Action(NextLetter)',
-                    'GoToSearch': 'VideoLibrary.Search',
-                    'GoToSettings': 'ActivateWindow(Settings)',
-                    'PageDown': 'Action(PageDown)',
-                    'PreviousLetter': 'Action(PrevLetter)',
-                    'TakeScreenshot': 'TakeScreenshot',
-                    'ToggleMute': 'Mute',
-                    'VolumeUp': 'Action(VolumeUp)',
-                    'VolumeDown': 'Action(VolumeDown)',
-                }
-                action = builtin.get(command)
-                if action:
-                    xbmc.executebuiltin(action)
-
-        elif messageType == "ServerRestarting":
-            if utils.settings('supressRestartMsg') == "true":
-                xbmcgui.Dialog().notification(
-                                    heading="Emby for Kodi",
-                                    message=lang(33006),
-                                    icon="special://home/addons/plugin.video.emby/icon.png")
-
-        elif messageType == "UserConfigurationUpdated":
-            # Update user data set in userclient
-            userclient.UserClient().userSettings = data
-            self.librarySync.refresh_views = True
-
-    def on_close(self, ws):
-        self.logMsg("Closed.", 2)
-
-    def on_open(self, ws):
-        self.doUtils.postCapabilities(self.deviceId)
-
-    def on_error(self, ws, error):
-        if "10061" in str(error):
-            # Server is offline
-            pass
-        else:
-            self.logMsg("Error: %s" % error, 2)
-
-    def run(self):
-
-        log = self.logMsg
-        window = utils.window
-        monitor = self.monitor
-
-        loglevel = int(window('emby_logLevel'))
-        # websocket.enableTrace(True)
-
-        userId = window('emby_currUser')
-        server = window('emby_server%s' % userId)
-        token = window('emby_accessToken%s' % userId)
-        deviceId = self.deviceId
-
-        # Get the appropriate prefix for the websocket
-        if "https" in server:
-            server = server.replace('https', "wss")
-        else:
-            server = server.replace('http', "ws")
-
-        websocket_url = "%s?api_key=%s&deviceId=%s" % (server, token, deviceId)
-        log("websocket url: %s" % websocket_url, 1)
-
-        self.client = websocket.WebSocketApp(websocket_url,
-                                    on_message=self.on_message,
-                                    on_error=self.on_error,
-                                    on_close=self.on_close)
-        
-        self.client.on_open = self.on_open
-        log("----===## Starting WebSocketClient ##===----", 0)
-
-        while not monitor.abortRequested():
-
-            self.client.run_forever(ping_interval=10)
-            if self.stopWebsocket:
-                break
-
-            if monitor.waitForAbort(5):
-                # Abort was requested, exit
-                break
-
-        log("##===---- WebSocketClient Stopped ----===##", 0)
-
-    def stopClient(self):
-
-        self.stopWebsocket = True
-        self.client.close()
+# -*- coding: utf-8 -*-
+
+#################################################################################################
+
+import json
+import threading
+import websocket
+
+import xbmc
+import xbmcgui
+
+import clientinfo
+import downloadutils
+import librarysync
+import playlist
+import userclient
+import utils
+
+import logging
+logging.basicConfig()
+
+#################################################################################################
+
+
+class WebSocket_Client(threading.Thread):
+
+    _shared_state = {}
+
+    client = None
+    stopWebsocket = False
+
+
+    def __init__(self):
+
+        self.__dict__ = self._shared_state
+        self.monitor = xbmc.Monitor()
+        
+        self.doUtils = downloadutils.DownloadUtils()
+        self.clientInfo = clientinfo.ClientInfo()
+        self.addonName = self.clientInfo.getAddonName()
+        self.deviceId = self.clientInfo.getDeviceId()
+        self.librarySync = librarysync.LibrarySync()
+        
+        threading.Thread.__init__(self)
+
+    def logMsg(self, msg, lvl=1):
+
+        self.className = self.__class__.__name__
+        utils.logMsg("%s %s" % (self.addonName, self.className), msg, lvl)
+
+
+    def sendProgressUpdate(self, data):
+        
+        self.logMsg("sendProgressUpdate", 2)
+        try:
+            messageData = {
+
+                'MessageType': "ReportPlaybackProgress",
+                'Data': data
+            }
+            messageString = json.dumps(messageData)
+            self.client.send(messageString)
+            self.logMsg("Message data: %s" % messageString, 2)
+
+        except Exception as e:
+            self.logMsg("Exception: %s" % e, 1)
+
+    def on_message(self, ws, message):
+        
+        window = utils.window
+        lang = utils.language
+
+        result = json.loads(message)
+        messageType = result['MessageType']
+        data = result['Data']
+
+        if messageType not in ('SessionEnded'):
+            # Mute certain events
+            self.logMsg("Message: %s" % message, 1)
+
+        if messageType == "Play":
+            # A remote control play command has been sent from the server.
+            itemIds = data['ItemIds']
+            command = data['PlayCommand']
+
+            pl = playlist.Playlist()
+            dialog = xbmcgui.Dialog()
+
+            if command == "PlayNow":
+                dialog.notification(
+                        heading="Emby for Kodi",
+                        message="%s %s" % (len(itemIds), lang(33004)),
+                        icon="special://home/addons/plugin.video.emby/icon.png",
+                        sound=False)
+                startat = data.get('StartPositionTicks', 0)
+                pl.playAll(itemIds, startat)
+
+            elif command == "PlayNext":
+                dialog.notification(
+                        heading="Emby for Kodi",
+                        message="%s %s" % (len(itemIds), lang(33005)),
+                        icon="special://home/addons/plugin.video.emby/icon.png",
+                        sound=False)
+                newplaylist = pl.modifyPlaylist(itemIds)
+                player = xbmc.Player()
+                if not player.isPlaying():
+                    # Only start the playlist if nothing is playing
+                    player.play(newplaylist)
+
+        elif messageType == "Playstate":
+            # A remote control update playstate command has been sent from the server.
+            command = data['Command']
+            player = xbmc.Player()
+
+            actions = {
+
+                'Stop': player.stop,
+                'Unpause': player.pause,
+                'Pause': player.pause,
+                'NextTrack': player.playnext,
+                'PreviousTrack': player.playprevious,
+                'Seek': player.seekTime
+            }
+            action = actions[command]
+            if command == "Seek":
+                seekto = data['SeekPositionTicks']
+                seektime = seekto / 10000000.0
+                action(seektime)
+                self.logMsg("Seek to %s." % seektime, 1)
+            else:
+                action()
+                self.logMsg("Command: %s completed." % command, 1)
+
+            window('emby_command', value="true")
+
+        elif messageType == "UserDataChanged":
+            # A user changed their personal rating for an item, or their playstate was updated
+            userdata_list = data['UserDataList']
+            self.librarySync.triage_items("userdata", userdata_list)
+
+        elif messageType == "LibraryChanged":
+            
+            librarySync = self.librarySync
+            processlist = {
+
+                'added': data['ItemsAdded'],
+                'update': data['ItemsUpdated'],
+                'remove': data['ItemsRemoved']
+            }
+            for action in processlist:
+                librarySync.triage_items(action, processlist[action])
+
+        elif messageType == "GeneralCommand":
+            
+            command = data['Name']
+            arguments = data['Arguments']
+
+            if command in ('Mute', 'Unmute', 'SetVolume',
+                            'SetSubtitleStreamIndex', 'SetAudioStreamIndex'):
+
+                player = xbmc.Player()
+                # These commands need to be reported back
+                if command == "Mute":
+                    xbmc.executebuiltin('Mute')
+                elif command == "Unmute":
+                    xbmc.executebuiltin('Mute')
+                elif command == "SetVolume":
+                    volume = arguments['Volume']
+                    xbmc.executebuiltin('SetVolume(%s[,showvolumebar])' % volume)
+                elif command == "SetAudioStreamIndex":
+                    index = int(arguments['Index'])
+                    player.setAudioStream(index - 1)
+                elif command == "SetSubtitleStreamIndex":
+                    embyindex = int(arguments['Index'])
+                    currentFile = player.getPlayingFile()
+
+                    mapping = window('emby_%s.indexMapping' % currentFile)
+                    if mapping:
+                        externalIndex = json.loads(mapping)
+                        # If there's external subtitles added via playbackutils
+                        for index in externalIndex:
+                            if externalIndex[index] == embyindex:
+                                player.setSubtitleStream(int(index))
+                                break
+                        else:
+                            # User selected internal subtitles
+                            external = len(externalIndex)
+                            audioTracks = len(player.getAvailableAudioStreams())
+                            player.setSubtitleStream(external + embyindex - audioTracks - 1)
+                    else:
+                        # Emby merges audio and subtitle index together
+                        audioTracks = len(player.getAvailableAudioStreams())
+                        player.setSubtitleStream(index - audioTracks - 1)
+
+                # Let service know
+                window('emby_command', value="true")
+
+            elif command == "DisplayMessage":
+                
+                header = arguments['Header']
+                text = arguments['Text']
+                xbmcgui.Dialog().notification(
+                                    heading=header,
+                                    message=text,
+                                    icon="special://home/addons/plugin.video.emby/icon.png",
+                                    time=4000)
+
+            elif command == "SendString":
+                
+                string = arguments['String']
+                text = {
+
+                    'jsonrpc': "2.0",
+                    'id': 0,
+                    'method': "Input.SendText",
+                    'params': {
+
+                        'text': "%s" % string,
+                        'done': False
+                    }
+                }
+                result = xbmc.executeJSONRPC(json.dumps(text))
+
+            else:
+                builtin = {
+
+                    'ToggleFullscreen': 'Action(FullScreen)',
+                    'ToggleOsdMenu': 'Action(OSD)',
+                    'ToggleContextMenu': 'Action(ContextMenu)',
+                    'MoveUp': 'Action(Up)',
+                    'MoveDown': 'Action(Down)',
+                    'MoveLeft': 'Action(Left)',
+                    'MoveRight': 'Action(Right)',
+                    'Select': 'Action(Select)',
+                    'Back': 'Action(back)',
+                    'GoHome': 'ActivateWindow(Home)',
+                    'PageUp': 'Action(PageUp)',
+                    'NextLetter': 'Action(NextLetter)',
+                    'GoToSearch': 'VideoLibrary.Search',
+                    'GoToSettings': 'ActivateWindow(Settings)',
+                    'PageDown': 'Action(PageDown)',
+                    'PreviousLetter': 'Action(PrevLetter)',
+                    'TakeScreenshot': 'TakeScreenshot',
+                    'ToggleMute': 'Mute',
+                    'VolumeUp': 'Action(VolumeUp)',
+                    'VolumeDown': 'Action(VolumeDown)',
+                }
+                action = builtin.get(command)
+                if action:
+                    xbmc.executebuiltin(action)
+
+        elif messageType == "ServerRestarting":
+            if utils.settings('supressRestartMsg') == "true":
+                xbmcgui.Dialog().notification(
+                                    heading="Emby for Kodi",
+                                    message=lang(33006),
+                                    icon="special://home/addons/plugin.video.emby/icon.png")
+
+        elif messageType == "UserConfigurationUpdated":
+            # Update user data set in userclient
+            userclient.UserClient().userSettings = data
+            self.librarySync.refresh_views = True
+
+    def on_close(self, ws):
+        self.logMsg("Closed.", 2)
+
+    def on_open(self, ws):
+        self.doUtils.postCapabilities(self.deviceId)
+
+    def on_error(self, ws, error):
+        if "10061" in str(error):
+            # Server is offline
+            pass
+        else:
+            self.logMsg("Error: %s" % error, 2)
+
+    def run(self):
+
+        window = utils.window
+        loglevel = int(window('emby_logLevel'))
+        # websocket.enableTrace(True)
+
+        userId = window('emby_currUser')
+        server = window('emby_server%s' % userId)
+        token = window('emby_accessToken%s' % userId)
+        # Get the appropriate prefix for the websocket
+        if "https" in server:
+            server = server.replace('https', "wss")
+        else:
+            server = server.replace('http', "ws")
+
+        websocket_url = "%s?api_key=%s&deviceId=%s" % (server, token, self.deviceId)
+        self.logMsg("websocket url: %s" % websocket_url, 1)
+
+        self.client = websocket.WebSocketApp(websocket_url,
+                                    on_message=self.on_message,
+                                    on_error=self.on_error,
+                                    on_close=self.on_close)
+        
+        self.client.on_open = self.on_open
+        self.logMsg("----===## Starting WebSocketClient ##===----", 0)
+
+        while not self.monitor.abortRequested():
+
+            self.client.run_forever(ping_interval=10)
+            if self.stopWebsocket:
+                break
+
+            if self.monitor.waitForAbort(5):
+                # Abort was requested, exit
+                break
+
+        self.logMsg("##===---- WebSocketClient Stopped ----===##", 0)
+
+    def stopClient(self):
+
+        self.stopWebsocket = True
+        self.client.close()
         self.logMsg("Stopping thread.", 1)
\ No newline at end of file