From 7a0f69e0141550eef061040aa2bf8fc3fa55fbec Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Thu, 16 Jun 2016 00:43:36 -0500 Subject: [PATCH 001/103] Centralized logging --- resources/lib/api.py | 13 +- resources/lib/artwork.py | 236 ++++++++++--------- resources/lib/playbackutils.py | 61 +++-- resources/lib/read_embyserver.py | 141 ++++++----- resources/lib/userclient.py | 110 ++++----- resources/lib/utils.py | 377 +++++++++++++++++------------- resources/lib/videonodes.py | 23 +- resources/lib/websocket_client.py | 63 +++-- service.py | 34 +-- 9 files changed, 542 insertions(+), 516 deletions(-) diff --git a/resources/lib/api.py b/resources/lib/api.py index 97ad4178..e5641e52 100644 --- a/resources/lib/api.py +++ b/resources/lib/api.py @@ -5,7 +5,7 @@ ################################################################################################## import clientinfo -import utils +from utils import Logging, settings ################################################################################################## @@ -13,17 +13,16 @@ import utils class API(): def __init__(self, item): + + global log + log = Logging(self.__class__.__name__).log + # item is the api response self.item = item 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 getUserData(self): # Default @@ -223,7 +222,7 @@ class API(): resume = 0 if resume_seconds: resume = round(float(resume_seconds), 6) - jumpback = int(utils.settings('resumeJumpBack')) + jumpback = int(settings('resumeJumpBack')) if resume > jumpback: # To avoid negative bookmark resume = resume - jumpback diff --git a/resources/lib/artwork.py b/resources/lib/artwork.py index 9885829c..2c8afea9 100644 --- a/resources/lib/artwork.py +++ b/resources/lib/artwork.py @@ -12,9 +12,9 @@ import xbmc import xbmcgui import xbmcvfs -import utils import clientinfo import image_cache_thread +from utils import Logging, window, settings, kodiSQL ################################################################################################# @@ -29,24 +29,25 @@ class Artwork(): imageCacheThreads = [] imageCacheLimitThreads = 0 + def __init__(self): + + global log + log = Logging(self.__class__.__name__).log + self.clientinfo = clientinfo.ClientInfo() self.addonName = self.clientinfo.getAddonName() - self.enableTextureCache = utils.settings('enableTextureCache') == "true" - self.imageCacheLimitThreads = int(utils.settings("imageCacheLimit")) + self.enableTextureCache = settings('enableTextureCache') == "true" + self.imageCacheLimitThreads = int(settings('imageCacheLimit')) self.imageCacheLimitThreads = int(self.imageCacheLimitThreads * 5) - utils.logMsg("Using Image Cache Thread Count: " + str(self.imageCacheLimitThreads), 1) + log("Using Image Cache Thread Count: %s" % 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) + self.userId = window('emby_currUser') + self.server = window('emby_server%s' % self.userId) def double_urlencode(self, text): @@ -56,8 +57,8 @@ class Artwork(): return text def single_urlencode(self, text): - - text = urllib.urlencode({'blahblahblah':text.encode("utf-8")}) #urlencode needs a utf- string + # urlencode needs a utf- string + text = urllib.urlencode({'blahblahblah':text.encode("utf-8")}) text = text[13:] return text.decode("utf-8") #return the result again as unicode @@ -167,102 +168,116 @@ class Artwork(): def FullTextureCacheSync(self): # This method will sync all Kodi artwork to textures13.db # and cache them locally. This takes diskspace! + dialog = xbmcgui.Dialog() - if not xbmcgui.Dialog().yesno("Image Texture Cache", "Running the image cache process can take some time.", "Are you sure you want continue?"): + if not dialog.yesno( + heading="Image Texture Cache", + line1=( + "Running the image cache process can take some time. " + "Are you sure you want continue?")): return - self.logMsg("Doing Image Cache Sync", 1) + log("Doing Image Cache Sync", 1) - dialog = xbmcgui.DialogProgress() - dialog.create("Emby for Kodi", "Image Cache Sync") + pdialog = xbmcgui.DialogProgress() + pdialog.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) + if dialog.yesno("Image Texture Cache", "Reset all existing cache data first?"): + log("Resetting all cache data first.", 1) + # Remove all existing textures first - path = xbmc.translatePath("special://thumbnails/").decode('utf-8') + 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'))) + path = os.path.join(path+dir.decode('utf-8'),file.decode('utf-8')) + xbmcvfs.delete(path) 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() + 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"): - texturecursor.execute("DELETE FROM " + tableName) - textureconnection.commit() - texturecursor.close() + if tableName != "version": + cursor.execute("DELETE FROM " + tableName) + connection.commit() + cursor.close() # Cache all entries in video DB - connection = utils.kodiSQL('video') + connection = 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 + log("Image cache sync about to process %s images" % total, 1) cursor.close() + count = 0 + for url in result: + + if pdialog.iscanceled(): + break + + percentage = int((float(count) / float(total))*100) + message = "%s of %s (%s)" % (count, total, self.imageCacheThreads) + pdialog.update(percentage, "Updating Image Cache: %s" % message) + self.cacheTexture(url[0]) + count += 1 + + # Cache all entries in music DB - connection = utils.kodiSQL('music') + connection = 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 + log("Image cache sync about to process %s images" % total, 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: + count = 0 + for url in result: + + if pdialog.iscanceled(): + break + + percentage = int((float(count) / float(total))*100) + message = "%s of %s" % (count, total) + pdialog.update(percentage, "Updating Image Cache: %s" % message) + self.cacheTexture(url[0]) + count += 1 + + + pdialog.update(100, "Waiting for all threads to exit: %s" % len(self.imageCacheThreads)) + log("Waiting for all threads to exit", 1) + + while len(self.imageCacheThreads): 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) + pdialog.update(100, "Waiting for all threads to exit: %s" % len(self.imageCacheThreads)) + log("Waiting for all threads to exit: %s" % len(self.imageCacheThreads), 1) xbmc.sleep(500) - dialog.close() + pdialog.close() - def addWorkerImageCacheThread(self, urlToAdd): + def addWorkerImageCacheThread(self, url): - while(True): + 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): + 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) @@ -271,23 +286,21 @@ class Artwork(): self.imageCacheThreads.append(newThread) return else: - self.logMsg("Waiting for empty queue spot: " + str(len(self.imageCacheThreads)), 2) + log("Waiting for empty queue spot: %s" % len(self.imageCacheThreads), 2) xbmc.sleep(50) - - def CacheTexture(self, url): + def cacheTexture(self, url): # Cache a single image url to the texture cache if url and self.enableTextureCache: - self.logMsg("Processing: %s" % url, 2) + log("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 + if not self.imageCacheLimitThreads: + # 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" + 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)) @@ -298,7 +311,7 @@ class Artwork(): self.addWorkerImageCacheThread(url) - def addArtwork(self, artwork, kodiId, mediaType, cursor): + def addArtwork(self, artwork, kodi_id, media_type, cursor): # Kodi conversion table kodiart = { @@ -329,7 +342,7 @@ class Artwork(): "AND media_type = ?", "AND type LIKE ?" )) - cursor.execute(query, (kodiId, mediaType, "fanart%",)) + cursor.execute(query, (kodi_id, media_type, "fanart%",)) rows = cursor.fetchall() if len(rows) > backdropsNumber: @@ -341,16 +354,16 @@ class Artwork(): "AND media_type = ?", "AND type LIKE ?" )) - cursor.execute(query, (kodiId, mediaType, "fanart_",)) + cursor.execute(query, (kodi_id, media_type, "fanart_",)) # Process backdrops and extra fanart index = "" for backdrop in backdrops: self.addOrUpdateArt( - imageUrl=backdrop, - kodiId=kodiId, - mediaType=mediaType, - imageType="%s%s" % ("fanart", index), + image_url=backdrop, + kodi_id=kodi_id, + media_type=media_type, + image_type="%s%s" % ("fanart", index), cursor=cursor) if backdropsNumber > 1: @@ -363,24 +376,24 @@ class Artwork(): # 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, + image_url=artwork[art], + kodi_id=kodi_id, + media_type=media_type, + image_type=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], + image_url=artwork[art], + kodi_id=kodi_id, + media_type=media_type, + image_type=kodiart[art], cursor=cursor) - def addOrUpdateArt(self, imageUrl, kodiId, mediaType, imageType, cursor): + def addOrUpdateArt(self, image_url, kodi_id, media_type, image_type, cursor): # Possible that the imageurl is an empty string - if imageUrl: + if image_url: cacheimage = False query = ' '.join(( @@ -391,13 +404,13 @@ class Artwork(): "AND media_type = ?", "AND type = ?" )) - cursor.execute(query, (kodiId, mediaType, imageType,)) + cursor.execute(query, (kodi_id, media_type, image_type,)) 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) + log("Adding Art Link for kodiId: %s (%s)" % (kodi_id, image_url), 2) query = ( ''' @@ -406,21 +419,20 @@ class Artwork(): VALUES (?, ?, ?, ?) ''' ) - cursor.execute(query, (kodiId, mediaType, imageType, imageUrl)) + cursor.execute(query, (kodi_id, media_type, image_type, image_url)) else: # Only cache artwork if it changed - if url != imageUrl: + if url != image_url: cacheimage = True # Only for the main backdrop, poster - if (utils.window('emby_initialScan') != "true" and + if (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) + log("Updating Art url for %s kodiId: %s (%s) -> (%s)" + % (image_type, kodi_id, url, image_url), 1) query = ' '.join(( @@ -430,13 +442,13 @@ class Artwork(): "AND media_type = ?", "AND type = ?" )) - cursor.execute(query, (imageUrl, kodiId, mediaType, imageType)) + cursor.execute(query, (image_url, kodi_id, media_type, image_type)) # Cache fanart and poster in Kodi texture cache - if cacheimage and imageType in ("fanart", "poster"): - self.CacheTexture(imageUrl) + if cacheimage and image_type in ("fanart", "poster"): + self.cacheTexture(image_url) - def deleteArtwork(self, kodiid, mediatype, cursor): + def deleteArtwork(self, kodi_id, media_type, cursor): query = ' '.join(( @@ -445,7 +457,7 @@ class Artwork(): "WHERE media_id = ?", "AND media_type = ?" )) - cursor.execute(query, (kodiid, mediatype,)) + cursor.execute(query, (kodi_id, media_type,)) rows = cursor.fetchall() for row in rows: @@ -456,7 +468,7 @@ class Artwork(): def deleteCachedArtwork(self, url): # Only necessary to remove and apply a new backdrop or poster - connection = utils.kodiSQL('texture') + connection = kodiSQL('texture') cursor = connection.cursor() try: @@ -464,21 +476,21 @@ class Artwork(): cachedurl = cursor.fetchone()[0] except TypeError: - self.logMsg("Could not find cached url.", 1) + log("Could not find cached url.", 1) except OperationalError: - self.logMsg("Database is locked. Skip deletion process.", 1) + log("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) + log("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) + log("Issue deleting url from cache. Skipping.", 2) finally: cursor.close() @@ -501,26 +513,26 @@ class Artwork(): return people - def getUserArtwork(self, itemid, itemtype): + def getUserArtwork(self, item_id, item_type): # Load user information set by UserClient image = ("%s/emby/Users/%s/Images/%s?Format=original" - % (self.server, itemid, itemtype)) + % (self.server, item_id, item_type)) return image - def getAllArtwork(self, item, parentInfo=False): + def getAllArtwork(self, item, parent_artwork=False): itemid = item['Id'] artworks = item['ImageTags'] - backdrops = item.get('BackdropImageTags',[]) + backdrops = item.get('BackdropImageTags', []) maxHeight = 10000 maxWidth = 10000 customquery = "" - if utils.settings('compressArt') == "true": + if settings('compressArt') == "true": customquery = "&Quality=90" - if utils.settings('enableCoverArt') == "false": + if settings('enableCoverArt') == "false": customquery += "&EnableImageEnhancers=false" allartworks = { @@ -554,7 +566,7 @@ class Artwork(): allartworks[art] = artwork # Process parent items if the main item is missing artwork - if parentInfo: + if parent_artwork: # Process parent backdrops if not allartworks['Backdrop']: @@ -601,4 +613,4 @@ class Artwork(): % (self.server, parentId, maxWidth, maxHeight, parentTag, customquery)) allartworks['Primary'] = artwork - return allartworks + return allartworks \ No newline at end of file diff --git a/resources/lib/playbackutils.py b/resources/lib/playbackutils.py index 4fbbc636..0afb0c8b 100644 --- a/resources/lib/playbackutils.py +++ b/resources/lib/playbackutils.py @@ -16,7 +16,7 @@ import downloadutils import playutils as putils import playlist import read_embyserver as embyserver -import utils +from utils import Logging, window, settings, language as lang ################################################################################################# @@ -26,6 +26,9 @@ class PlaybackUtils(): def __init__(self, item): + global log + log = Logging(self.__class__.__name__).log + self.item = item self.API = api.API(self.item) @@ -33,28 +36,20 @@ class PlaybackUtils(): self.addonName = self.clientInfo.getAddonName() self.doUtils = downloadutils.DownloadUtils().downloadUrl - self.userid = utils.window('emby_currUser') - self.server = utils.window('emby_server%s' % self.userid) + self.userid = window('emby_currUser') + self.server = window('emby_server%s' % self.userid) 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) + log("Play called.", 1) playurl = playutils.getPlayUrl() if not playurl: return xbmcplugin.setResolvedUrl(int(sys.argv[1]), False, listitem) @@ -77,9 +72,9 @@ class PlaybackUtils(): 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) + log("Playlist start position: %s" % startPos, 2) + log("Playlist plugin position: %s" % currentPosition, 2) + log("Playlist size: %s" % sizePlaylist, 2) ############### RESUME POINT ################ @@ -91,12 +86,11 @@ class PlaybackUtils(): if not propertiesPlayback: window('emby_playbackProps', value="true") - self.logMsg("Setting up properties in playlist.", 1) + log("Setting up properties in playlist.", 1) - if (not homeScreen and not seektime and - window('emby_customPlaylist') != "true"): + if not homeScreen and not seektime and window('emby_customPlaylist') != "true": - self.logMsg("Adding dummy file to playlist.", 2) + log("Adding dummy file to playlist.", 2) dummyPlaylist = True playlist.add(playurl, listitem, index=startPos) # Remove the original item from playlist @@ -116,18 +110,18 @@ class PlaybackUtils(): getTrailers = True if settings('askCinema') == "true": - resp = xbmcgui.Dialog().yesno("Emby for Kodi", utils.language(33016)) + resp = xbmcgui.Dialog().yesno("Emby for Kodi", lang(33016)) if not resp: # User selected to not play trailers getTrailers = False - self.logMsg("Skip trailers.", 1) + log("Skip trailers.", 1) if getTrailers: for intro in intros['Items']: # The server randomly returns intros, process them. introListItem = xbmcgui.ListItem() introPlayurl = putils.PlayUtils(intro).getPlayUrl() - self.logMsg("Adding Intro: %s" % introPlayurl, 1) + log("Adding Intro: %s" % introPlayurl, 1) # Set listitem and properties for intros pbutils = PlaybackUtils(intro) @@ -143,7 +137,7 @@ class PlaybackUtils(): if homeScreen and not seektime and not sizePlaylist: # Extend our current playlist with the actual item to play # only if there's no playlist first - self.logMsg("Adding main item to playlist.", 1) + log("Adding main item to playlist.", 1) self.pl.addtoPlaylist(dbid, self.item['Type'].lower()) # Ensure that additional parts are played after the main item @@ -160,7 +154,7 @@ class PlaybackUtils(): additionalListItem = xbmcgui.ListItem() additionalPlayurl = putils.PlayUtils(part).getPlayUrl() - self.logMsg("Adding additional part: %s" % partcount, 1) + log("Adding additional part: %s" % partcount, 1) # Set listitem and properties for each additional parts pbutils = PlaybackUtils(part) @@ -174,13 +168,13 @@ class PlaybackUtils(): if dummyPlaylist: # Added a dummy file to the playlist, # because the first item is going to fail automatically. - self.logMsg("Processed as a playlist. First item is skipped.", 1) + log("Processed as a playlist. First item is skipped.", 1) return xbmcplugin.setResolvedUrl(int(sys.argv[1]), False, listitem) # We just skipped adding properties. Reset flag for next time. elif propertiesPlayback: - self.logMsg("Resetting properties playback flag.", 2) + log("Resetting properties playback flag.", 2) window('emby_playbackProps', clear=True) #self.pl.verifyPlaylist() @@ -190,7 +184,7 @@ class PlaybackUtils(): if window('emby_%s.playmethod' % playurl) == "Transcode": # Filter ISO since Emby does not probe anymore if self.item.get('VideoType') == "Iso": - self.logMsg("Skipping audio/subs prompt, ISO detected.", 1) + log("Skipping audio/subs prompt, ISO detected.", 1) else: playurl = playutils.audioSubsPref(playurl, listitem) window('emby_%s.playmethod' % playurl, value="Transcode") @@ -201,23 +195,22 @@ class PlaybackUtils(): ############### PLAYBACK ################ if homeScreen and seektime and window('emby_customPlaylist') != "true": - self.logMsg("Play as a widget item.", 1) + 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. - self.logMsg("Play playlist.", 1) + log("Play playlist.", 1) xbmc.Player().play(playlist, startpos=startPos) else: - self.logMsg("Play as a regular item.", 1) + log("Play as a regular item.", 1) xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, listitem) def setProperties(self, playurl, listitem): - window = utils.window # Set all properties necessary for plugin path playback itemid = self.item['Id'] itemtype = self.item['Type'] @@ -233,7 +226,7 @@ class PlaybackUtils(): window('%s.refreshid' % embyitem, value=itemid) # Append external subtitles to stream - playmethod = utils.window('%s.playmethod' % embyitem) + playmethod = window('%s.playmethod' % embyitem) # Only for direct stream if playmethod in ("DirectStream"): # Direct play automatically appends external @@ -272,13 +265,13 @@ class PlaybackUtils(): kodiindex += 1 mapping = json.dumps(mapping) - utils.window('emby_%s.indexMapping' % playurl, value=mapping) + 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) + allartwork = self.artwork.getAllArtwork(self.item, parent_artwork=True) # Set artwork for listitem arttypes = { diff --git a/resources/lib/read_embyserver.py b/resources/lib/read_embyserver.py index 3121a07a..cc0c4b4a 100644 --- a/resources/lib/read_embyserver.py +++ b/resources/lib/read_embyserver.py @@ -4,21 +4,22 @@ import xbmc -import utils import clientinfo import downloadutils +from utils import Logging, window, settings, kodiSQL ################################################################################################# class Read_EmbyServer(): - limitIndex = int(utils.settings('limitindex')) + limitIndex = int(settings('limitindex')) def __init__(self): - window = utils.window + global log + log = Logging(self.__class__.__name__).log self.clientInfo = clientinfo.ClientInfo() self.addonName = self.clientInfo.getAddonName() @@ -27,17 +28,11 @@ class Read_EmbyServer(): 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 = {} @@ -60,7 +55,8 @@ class Read_EmbyServer(): 'Ids': ",".join(itemlist), 'Fields': "Etag" } - result = self.doUtils("{server}/emby/Users/{UserId}/Items?&format=json", parameters=params) + url = "{server}/emby/Users/{UserId}/Items?&format=json" + result = self.doUtils(url, parameters=params) if result: items.extend(result['Items']) @@ -86,7 +82,8 @@ class Read_EmbyServer(): "MediaSources,VoteCount" ) } - result = self.doUtils("{server}/emby/Users/{UserId}/Items?format=json", parameters=params) + url = "{server}/emby/Users/{UserId}/Items?format=json" + result = self.doUtils(url, parameters=params) if result: items.extend(result['Items']) @@ -96,14 +93,15 @@ class Read_EmbyServer(): # Returns ancestors using embyId viewId = None - for view in self.doUtils("{server}/emby/Items/%s/Ancestors?UserId={UserId}&format=json" % itemid): + url = "{server}/emby/Items/%s/Ancestors?UserId={UserId}&format=json" % itemid + for view in self.doUtils(url): if view['Type'] == "CollectionFolder": # Found view viewId = view['Id'] # Compare to view table in emby database - emby = utils.kodiSQL('emby') + emby = kodiSQL('emby') cursor_emby = emby.cursor() query = ' '.join(( @@ -124,7 +122,8 @@ class Read_EmbyServer(): return [viewName, viewId, mediatype] - def getFilteredSection(self, parentid, itemtype=None, sortby="SortName", recursive=True, limit=None, sortorder="Ascending", filter=""): + def getFilteredSection(self, parentid, itemtype=None, sortby="SortName", recursive=True, + limit=None, sortorder="Ascending", filter=""): params = { 'ParentId': parentid, @@ -137,39 +136,54 @@ class Read_EmbyServer(): '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") + '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") + '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) + url = "{server}/emby/LiveTv/Channels/?userid={UserId}&format=json" + return self.doUtils(url, parameters=params) def getTvRecordings(self, groupid): - if groupid == "root": 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") + '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) + url = "{server}/emby/LiveTv/Recordings/?userid={UserId}&format=json" + return self.doUtils(url, parameters=params) def getSection(self, parentid, itemtype=None, sortby="SortName", basic=False, dialog=None): @@ -197,7 +211,7 @@ class Read_EmbyServer(): items['TotalRecordCount'] = total except TypeError: # Failed to retrieve - self.logMsg("%s:%s Failed to retrieve the server response." % (url, params), 2) + log("%s:%s Failed to retrieve the server response." % (url, params), 2) else: index = 0 @@ -239,27 +253,27 @@ class Read_EmbyServer(): # Something happened to the connection if not throttled: throttled = True - self.logMsg("Throttle activated.", 1) + log("Throttle activated.", 1) if jump == highestjump: # We already tried with the highestjump, but it failed. Reset value. - self.logMsg("Reset highest value.", 1) + log("Reset highest value.", 1) highestjump = 0 # Lower the number by half if highestjump: throttled = False jump = highestjump - self.logMsg("Throttle deactivated.", 1) + log("Throttle deactivated.", 1) else: jump = int(jump/4) - self.logMsg("Set jump limit to recover: %s" % jump, 2) + log("Set jump limit to recover: %s" % jump, 2) retry = 0 - while utils.window('emby_online') != "true": + while window('emby_online') != "true": # Wait server to come back online if retry == 5: - self.logMsg("Unable to reconnect to server. Abort process.", 1) + log("Unable to reconnect to server. Abort process.", 1) return items retry += 1 @@ -287,7 +301,7 @@ class Read_EmbyServer(): increment = 10 jump += increment - self.logMsg("Increase jump limit to: %s" % jump, 1) + log("Increase jump limit to: %s" % jump, 1) return items def getViews(self, mediatype="", root=False, sortedlist=False): @@ -304,7 +318,7 @@ class Read_EmbyServer(): try: items = result['Items'] except TypeError: - self.logMsg("Error retrieving views for type: %s" % mediatype, 2) + log("Error retrieving views for type: %s" % mediatype, 2) else: for item in items: @@ -373,15 +387,18 @@ class Read_EmbyServer(): 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): @@ -389,6 +406,7 @@ class Read_EmbyServer(): 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): @@ -404,7 +422,8 @@ class Read_EmbyServer(): 'IsVirtualUnaired': False, 'Fields': "Etag" } - result = self.doUtils("{server}/emby/Shows/%s/Seasons?UserId={UserId}&format=json" % showId, parameters=params) + url = "{server}/emby/Shows/%s/Seasons?UserId={UserId}&format=json" % showId + result = self.doUtils(url, parameters=params) if result: items = result @@ -422,7 +441,6 @@ class Read_EmbyServer(): return self.getSection(seasonId, "Episode") - def getArtists(self, dialog=None): items = { @@ -444,7 +462,7 @@ class Read_EmbyServer(): items['TotalRecordCount'] = total except TypeError: # Failed to retrieve - self.logMsg("%s:%s Failed to retrieve the server response." % (url, params), 2) + log("%s:%s Failed to retrieve the server response." % (url, params), 2) else: index = 1 @@ -478,17 +496,20 @@ class Read_EmbyServer(): 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") + return self.getSection(albumId, "Audio") def getAdditionalParts(self, itemId): @@ -497,8 +518,8 @@ class Read_EmbyServer(): 'Items': [], 'TotalRecordCount': 0 } - - result = self.doUtils("{server}/emby/Videos/%s/AdditionalParts?UserId={UserId}&format=json" % itemId) + url = "{server}/emby/Videos/%s/AdditionalParts?UserId={UserId}&format=json" % itemId + result = self.doUtils(url) if result: items = result @@ -520,21 +541,27 @@ class Read_EmbyServer(): def updateUserRating(self, itemid, like=None, favourite=None, deletelike=False): # Updates the user rating to Emby - + doUtils = self.doUtils + if favourite: - self.doUtils("{server}/emby/Users/{UserId}/FavoriteItems/%s?format=json" % itemid, action_type="POST") + url = "{server}/emby/Users/{UserId}/FavoriteItems/%s?format=json" % itemid + doUtils(url, action_type="POST") elif favourite == False: - self.doUtils("{server}/emby/Users/{UserId}/FavoriteItems/%s?format=json" % itemid, action_type="DELETE") + url = "{server}/emby/Users/{UserId}/FavoriteItems/%s?format=json" % itemid + doUtils(url, 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") + url = "{server}/emby/Users/{UserId}/Items/%s/Rating?Likes=true&format=json" % itemid + doUtils(url, 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") + url = "{server}/emby/Users/{UserId}/Items/%s/Rating?Likes=false&format=json" % itemid + doUtils(url, action_type="POST") elif deletelike: - self.doUtils("{server}/emby/Users/{UserId}/Items/%s/Rating?format=json" % itemid, action_type="DELETE") + url = "{server}/emby/Users/{UserId}/Items/%s/Rating?format=json" % itemid + doUtils(url, action_type="DELETE") else: - self.logMsg("Error processing user rating.", 1) + log("Error processing user rating.", 1) - self.logMsg("Update user rating to emby for itemid: %s " - "| like: %s | favourite: %s | deletelike: %s" - % (itemid, like, favourite, deletelike), 1) + log("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 f068b772..ce985647 100644 --- a/resources/lib/userclient.py +++ b/resources/lib/userclient.py @@ -11,9 +11,9 @@ import xbmcaddon import xbmcvfs import artwork -import utils import clientinfo import downloadutils +from utils import Logging, window, settings, language as lang ################################################################################################## @@ -39,6 +39,9 @@ class UserClient(threading.Thread): def __init__(self): + global log + log = Logging(self.__class__.__name__).log + self.__dict__ = self._shared_state self.addon = xbmcaddon.Addon() @@ -47,25 +50,20 @@ class UserClient(threading.Thread): 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') + additionalUsers = settings('additionalUsers') if additionalUsers: self.AdditionalUser = additionalUsers.split(',') def getUsername(self): - username = utils.settings('username') + username = settings('username') if not username: - self.logMsg("No username saved.", 2) + log("No username saved.", 2) return "" return username @@ -73,7 +71,7 @@ class UserClient(threading.Thread): def getLogLevel(self): try: - logLevel = int(utils.settings('logLevel')) + logLevel = int(settings('logLevel')) except ValueError: logLevel = 0 @@ -81,9 +79,6 @@ class UserClient(threading.Thread): def getUserId(self): - window = utils.window - settings = utils.settings - username = self.getUsername() w_userId = window('emby_currUser') s_userId = settings('userId%s' % username) @@ -93,22 +88,20 @@ class UserClient(threading.Thread): 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" + log("Returning userId from WINDOW for username: %s UserId: %s" % (username, w_userId), 2) return w_userId # Verify the settings elif s_userId: - self.logMsg("Returning userId from SETTINGS for username: %s userId: %s" + log("Returning userId from SETTINGS for username: %s userId: %s" % (username, s_userId), 2) return s_userId # No userId found else: - self.logMsg("No userId saved for username: %s." % username, 1) + log("No userId saved for username: %s." % username, 1) def getServer(self, prefix=True): - settings = utils.settings - alternate = settings('altip') == "true" if alternate: # Alternate host @@ -124,7 +117,7 @@ class UserClient(threading.Thread): server = host + ":" + port if not host: - self.logMsg("No server information saved.", 2) + log("No server information saved.", 2) return False # If https is true @@ -141,9 +134,6 @@ class UserClient(threading.Thread): def getToken(self): - window = utils.window - settings = utils.settings - username = self.getUsername() userId = self.getUserId() w_token = window('emby_accessToken%s' % userId) @@ -154,23 +144,21 @@ class UserClient(threading.Thread): 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" + log("Returning accessToken from WINDOW for username: %s accessToken: %s" % (username, w_token), 2) return w_token # Verify the settings elif s_token: - self.logMsg("Returning accessToken from SETTINGS for username: %s accessToken: %s" + log("Returning accessToken from SETTINGS for username: %s accessToken: %s" % (username, s_token), 2) window('emby_accessToken%s' % username, value=s_token) return s_token else: - self.logMsg("No token found.", 1) + log("No token found.", 1) return "" def getSSLverify(self): # Verify host certificate - settings = utils.settings - s_sslverify = settings('sslverify') if settings('altip') == "true": s_sslverify = settings('secondsslverify') @@ -182,8 +170,6 @@ class UserClient(threading.Thread): def getSSL(self): # Client side certificate - settings = utils.settings - s_cert = settings('sslcert') if settings('altip') == "true": s_cert = settings('secondsslcert') @@ -201,16 +187,16 @@ class UserClient(threading.Thread): self.userSettings = result # Set user image for skin display if result.get('PrimaryImageTag'): - utils.window('EmbyUserImage', value=artwork.Artwork().getUserArtwork(result['Id'], 'Primary')) + 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'])) + 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) + url = "%s/emby/Users/Public?format=json" % self.getServer() + result = self.doUtils.downloadUrl(url, authenticate=False) if result != "": return result else: @@ -220,13 +206,11 @@ class UserClient(threading.Thread): 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) + log("Access is restricted.", 1) self.HasAccess = False elif window('emby_online') != "true": @@ -234,15 +218,13 @@ class UserClient(threading.Thread): pass elif window('emby_serverStatus') == "restricted": - self.logMsg("Access is granted.", 1) + log("Access is granted.", 1) self.HasAccess = True window('emby_serverStatus', clear=True) - xbmcgui.Dialog().notification("Emby for Kodi", utils.language(33007)) + xbmcgui.Dialog().notification("Emby for Kodi", lang(33007)) def loadCurrUser(self, authenticated=False): - window = utils.window - doUtils = self.doUtils username = self.getUsername() userId = self.getUserId() @@ -290,9 +272,6 @@ class UserClient(threading.Thread): def authenticate(self): - lang = utils.language - window = utils.window - settings = utils.settings dialog = xbmcgui.Dialog() # Get /profile/addon_data @@ -304,12 +283,12 @@ class UserClient(threading.Thread): # If there's no settings.xml if not hasSettings: - self.logMsg("No settings.xml found.", 1) + log("No settings.xml found.", 1) self.auth = False return # If no user information elif not server or not username: - self.logMsg("Missing server information.", 1) + log("Missing server information.", 1) self.auth = False return # If there's a token, load the user @@ -319,9 +298,9 @@ class UserClient(threading.Thread): 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) + log("Current user: %s" % self.currUser, 1) + log("Current userId: %s" % self.currUserId, 1) + log("Current accessToken: %s" % self.currToken, 2) return ##### AUTHENTICATE USER ##### @@ -341,7 +320,7 @@ class UserClient(threading.Thread): option=xbmcgui.ALPHANUM_HIDE_INPUT) # If password dialog is cancelled if not password: - self.logMsg("No password entered.", 0) + log("No password entered.", 0) window('emby_serverStatus', value="Stop") self.auth = False return @@ -356,16 +335,17 @@ class UserClient(threading.Thread): # Authenticate username and password data = {'username': username, 'password': sha1} - self.logMsg(data, 2) + log(data, 2) - result = self.doUtils.downloadUrl("%s/emby/Users/AuthenticateByName?format=json" % server, postBody=data, action_type="POST", authenticate=False) + url = "%s/emby/Users/AuthenticateByName?format=json" % server + result = self.doUtils.downloadUrl(url, postBody=data, action_type="POST", authenticate=False) try: - self.logMsg("Auth response: %s" % result, 1) + log("Auth response: %s" % result, 1) accessToken = result['AccessToken'] except (KeyError, TypeError): - self.logMsg("Failed to retrieve the api key.", 1) + log("Failed to retrieve the api key.", 1) accessToken = None if accessToken is not None: @@ -374,19 +354,19 @@ class UserClient(threading.Thread): "%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) + log("User Authenticated: %s" % accessToken, 1) self.loadCurrUser(authenticated=True) window('emby_serverStatus', clear=True) self.retry = 0 else: - self.logMsg("User authentication failed.", 1) + log("User authentication failed.", 1) settings('accessToken', value="") settings('userId%s' % username, value="") dialog.ok(lang(33001), lang(33009)) # Give two attempts at entering password if self.retry == 2: - self.logMsg("Too many retries. " + 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)) @@ -396,23 +376,21 @@ class UserClient(threading.Thread): def resetClient(self): - self.logMsg("Reset UserClient authentication.", 1) + log("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) + settings('accessToken', value="") + window('emby_accessToken%s' % self.getUserId(), clear=True) self.currToken = None - self.logMsg("User token has been removed.", 1) + log("User token has been removed.", 1) self.auth = True self.currUser = None def run(self): - window = utils.window - monitor = xbmc.Monitor() - self.logMsg("----===## Starting UserClient ##===----", 0) + log("----===## Starting UserClient ##===----", 0) while not monitor.abortRequested(): @@ -447,8 +425,8 @@ class UserClient(threading.Thread): # The status Stop is for when user cancelled password dialog. if server and username and status != "Stop": # Only if there's information found to login - self.logMsg("Server found: %s" % server, 2) - self.logMsg("Username found: %s" % username, 2) + log("Server found: %s" % server, 2) + log("Username found: %s" % username, 2) self.auth = True @@ -461,7 +439,7 @@ class UserClient(threading.Thread): break self.doUtils.stopSession() - self.logMsg("##===---- UserClient Stopped ----===##", 0) + log("##===---- UserClient Stopped ----===##", 0) def stopClient(self): # When emby for kodi terminates diff --git a/resources/lib/utils.py b/resources/lib/utils.py index 40b711c3..dd2d04d4 100644 --- a/resources/lib/utils.py +++ b/resources/lib/utils.py @@ -20,7 +20,7 @@ import xbmcgui import xbmcvfs ################################################################################################# - +# Main methods def logMsg(title, msg, level=1): @@ -43,43 +43,80 @@ def logMsg(title, msg, level=1): 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) +class Logging(): - #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")''' + LOGGINGCLASS = None + + + def __init__(self, classname=""): + + self.LOGGINGCLASS = classname + + def log(self, msg, level=1): + + self.logMsg("EMBY %s" % self.LOGGINGCLASS, msg, level) + + def logMsg(self, 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'))) + +# Initiate class for utils.py document logging +log = Logging('Utils').log + + +def window(property, value=None, clear=False, window_id=10000): + # Get or set window property + WINDOW = xbmcgui.Window(window_id) 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") + else: + return WINDOW.getProperty(property) def settings(setting, value=None): # Get or add addon setting + addon = xbmcaddon.Addon(id='plugin.video.emby') + if value is not None: - xbmcaddon.Addon(id='plugin.video.emby').setSetting(setting, value) - else: - return xbmcaddon.Addon(id='plugin.video.emby').getSetting(setting) #returns unicode object + addon.setSetting(setting, value) + else: # returns unicode object + return addon.getSetting(setting) -def language(stringid): - # Central string retrieval - string = xbmcaddon.Addon(id='plugin.video.emby').getLocalizedString(stringid) #returns unicode object +def language(string_id): + # Central string retrieval - unicode + string = xbmcaddon.Addon(id='plugin.video.emby').getLocalizedString(string_id) return string +################################################################################################# +# Database related methods + 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') + elif media_type == "music": + dbPath = getKodiMusicDBPath() else: dbPath = getKodiVideoDBPath() @@ -98,8 +135,8 @@ def getKodiVideoDBPath(): } dbPath = xbmc.translatePath( - "special://database/MyVideos%s.db" - % dbVersion.get(xbmc.getInfoLabel('System.BuildVersion')[:2], "")).decode('utf-8') + "special://database/MyVideos%s.db" + % dbVersion.get(xbmc.getInfoLabel('System.BuildVersion')[:2], "")).decode('utf-8') return dbPath def getKodiMusicDBPath(): @@ -114,10 +151,13 @@ def getKodiMusicDBPath(): } dbPath = xbmc.translatePath( - "special://database/MyMusic%s.db" - % dbVersion.get(xbmc.getInfoLabel('System.BuildVersion')[:2], "")).decode('utf-8') + "special://database/MyMusic%s.db" + % dbVersion.get(xbmc.getInfoLabel('System.BuildVersion')[:2], "")).decode('utf-8') return dbPath +################################################################################################# +# Utility methods + def getScreensaver(): # Get the current screensaver value query = { @@ -145,139 +185,8 @@ def setScreensaver(value): '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 + result = xbmc.executeJSONRPC(json.dumps(query)) + log("Toggling screensaver: %s %s" % (value, result), 1) def convertdate(date): try: @@ -344,6 +253,141 @@ def indent(elem, level=0): if level and (not elem.tail or not elem.tail.strip()): elem.tail = i +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() + log(s.getvalue(), 1) + + return result + + return wrapper + return decorator + +################################################################################################# +# Addon utilities + +def reset(): + + dialog = xbmcgui.Dialog() + + if not dialog.yesno("Warning", "Are you sure you want to reset your local Kodi database?"): + 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 + log("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": + log("Resetting the Kodi music database.", 0) + 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 + log("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: + log("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) + log("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 sourcesXML(): # To make Master lock compatible path = xbmc.translatePath("special://profile/").decode('utf-8') @@ -413,12 +457,11 @@ def passwordsXML(): for path in paths: if path.find('.//from').text == "smb://%s/" % credentials: paths.remove(path) - logMsg("EMBY", "Successfully removed credentials for: %s" - % credentials, 1) + log("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) + log("Failed to find saved server: %s in passwords.xml" % credentials, 1) settings('networkCreds', value="") xbmcgui.Dialog().notification( @@ -473,7 +516,7 @@ def passwordsXML(): # Add credentials settings('networkCreds', value="%s" % server) - logMsg("EMBY", "Added server: %s to passwords.xml" % server, 1) + log("Added server: %s to passwords.xml" % server, 1) # Prettify and write to file try: indent(root) @@ -501,7 +544,7 @@ def playlistXSP(mediatype, tagname, viewid, viewtype="", delete=False): # Create the playlist directory if not xbmcvfs.exists(path): - logMsg("EMBY", "Creating directory: %s" % path, 1) + log("Creating directory: %s" % path, 1) xbmcvfs.mkdirs(path) # Only add the playlist if it doesn't already exists @@ -509,7 +552,7 @@ def playlistXSP(mediatype, tagname, viewid, viewtype="", delete=False): if delete: xbmcvfs.delete(xsppath) - logMsg("EMBY", "Successfully removed playlist: %s." % tagname, 1) + log("Successfully removed playlist: %s." % tagname, 1) return @@ -517,11 +560,11 @@ def playlistXSP(mediatype, tagname, viewid, viewtype="", delete=False): itemtypes = { 'homevideos': "movies" } - logMsg("EMBY", "Writing playlist file to: %s" % xsppath, 1) + log("Writing playlist file to: %s" % xsppath, 1) try: f = xbmcvfs.File(xsppath, 'w') except: - logMsg("EMBY", "Failed to create playlist: %s" % xsppath, 1) + log("Failed to create playlist: %s" % xsppath, 1) return else: f.write( @@ -535,7 +578,7 @@ def playlistXSP(mediatype, tagname, viewid, viewtype="", delete=False): '' % (itemtypes.get(mediatype, mediatype), plname, tagname)) f.close() - logMsg("EMBY", "Successfully added playlist: %s" % tagname) + log("Successfully added playlist: %s" % tagname, 1) def deletePlaylists(): @@ -557,10 +600,10 @@ def deleteNodes(): try: shutil.rmtree("%s%s" % (path, dir.decode('utf-8'))) except: - logMsg("EMBY", "Failed to delete directory: %s" % dir.decode('utf-8')) + log("Failed to delete directory: %s" % dir.decode('utf-8'), 0) 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')) + log("Failed to file: %s" % file.decode('utf-8'), 0) \ No newline at end of file diff --git a/resources/lib/videonodes.py b/resources/lib/videonodes.py index f7f63c3c..bf1d20f4 100644 --- a/resources/lib/videonodes.py +++ b/resources/lib/videonodes.py @@ -11,6 +11,7 @@ import xbmcvfs import clientinfo import utils +from utils import Logging, window, language as lang ################################################################################################# @@ -20,16 +21,14 @@ class VideoNodes(object): def __init__(self): + global log + log = Logging(self.__class__.__name__).log + 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): @@ -54,8 +53,6 @@ class VideoNodes(object): def viewNode(self, indexnumber, tagname, mediatype, viewtype, viewid, delete=False): - window = utils.window - if viewtype == "mixed": dirname = "%s - %s" % (viewid, mediatype) else: @@ -82,7 +79,7 @@ class VideoNodes(object): for file in files: xbmcvfs.delete(nodepath + file) - self.logMsg("Sucessfully removed videonode: %s." % tagname, 1) + log("Sucessfully removed videonode: %s." % tagname, 1) return # Create index entry @@ -184,7 +181,7 @@ class VideoNodes(object): # Get label stringid = nodes[node] if node != "1": - label = utils.language(stringid) + label = lang(stringid) if not label: label = xbmc.getLocalizedString(stringid) else: @@ -319,8 +316,6 @@ class VideoNodes(object): 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') @@ -342,7 +337,7 @@ class VideoNodes(object): 'Favorite tvshows': 30181, 'channels': 30173 } - label = utils.language(labels[tagname]) + label = lang(labels[tagname]) embynode = "Emby.nodes.%s" % indexnumber window('%s.title' % embynode, value=label) window('%s.path' % embynode, value=windowpath) @@ -369,9 +364,7 @@ class VideoNodes(object): def clearProperties(self): - window = utils.window - - self.logMsg("Clearing nodes properties.", 1) + log("Clearing nodes properties.", 1) embyprops = window('Emby.nodes.total') propnames = [ diff --git a/resources/lib/websocket_client.py b/resources/lib/websocket_client.py index 559cb152..90e9499e 100644 --- a/resources/lib/websocket_client.py +++ b/resources/lib/websocket_client.py @@ -14,10 +14,7 @@ import downloadutils import librarysync import playlist import userclient -import utils - -import logging -logging.basicConfig() +from utils import Logging, window, settings, language as lang ################################################################################################# @@ -32,6 +29,9 @@ class WebSocket_Client(threading.Thread): def __init__(self): + global log + log = Logging(self.__class__.__name__).log + self.__dict__ = self._shared_state self.monitor = xbmc.Monitor() @@ -43,15 +43,10 @@ class WebSocket_Client(threading.Thread): 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) + log("sendProgressUpdate", 2) try: messageData = { @@ -60,23 +55,21 @@ class WebSocket_Client(threading.Thread): } messageString = json.dumps(messageData) self.client.send(messageString) - self.logMsg("Message data: %s" % messageString, 2) + log("Message data: %s" % messageString, 2) except Exception as e: - self.logMsg("Exception: %s" % e, 1) + log("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'] + dialog = xbmcgui.Dialog() if messageType not in ('SessionEnded'): # Mute certain events - self.logMsg("Message: %s" % message, 1) + log("Message: %s" % message, 1) if messageType == "Play": # A remote control play command has been sent from the server. @@ -84,7 +77,6 @@ class WebSocket_Client(threading.Thread): command = data['PlayCommand'] pl = playlist.Playlist() - dialog = xbmcgui.Dialog() if command == "PlayNow": dialog.notification( @@ -126,10 +118,10 @@ class WebSocket_Client(threading.Thread): seekto = data['SeekPositionTicks'] seektime = seekto / 10000000.0 action(seektime) - self.logMsg("Seek to %s." % seektime, 1) + log("Seek to %s." % seektime, 1) else: action() - self.logMsg("Command: %s completed." % command, 1) + log("Command: %s completed." % command, 1) window('emby_command', value="true") @@ -199,11 +191,11 @@ class WebSocket_Client(threading.Thread): header = arguments['Header'] text = arguments['Text'] - xbmcgui.Dialog().notification( - heading=header, - message=text, - icon="special://home/addons/plugin.video.emby/icon.png", - time=4000) + dialog.notification( + heading=header, + message=text, + icon="special://home/addons/plugin.video.emby/icon.png", + time=4000) elif command == "SendString": @@ -250,11 +242,11 @@ class WebSocket_Client(threading.Thread): 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") + if settings('supressRestartMsg') == "true": + 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 @@ -262,7 +254,7 @@ class WebSocket_Client(threading.Thread): self.librarySync.refresh_views = True def on_close(self, ws): - self.logMsg("Closed.", 2) + log("Closed.", 2) def on_open(self, ws): self.doUtils.postCapabilities(self.deviceId) @@ -272,11 +264,10 @@ class WebSocket_Client(threading.Thread): # Server is offline pass else: - self.logMsg("Error: %s" % error, 2) + log("Error: %s" % error, 2) def run(self): - window = utils.window loglevel = int(window('emby_logLevel')) # websocket.enableTrace(True) @@ -290,7 +281,7 @@ class WebSocket_Client(threading.Thread): 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) + log("websocket url: %s" % websocket_url, 1) self.client = websocket.WebSocketApp(websocket_url, on_message=self.on_message, @@ -298,7 +289,7 @@ class WebSocket_Client(threading.Thread): on_close=self.on_close) self.client.on_open = self.on_open - self.logMsg("----===## Starting WebSocketClient ##===----", 0) + log("----===## Starting WebSocketClient ##===----", 0) while not self.monitor.abortRequested(): @@ -310,10 +301,10 @@ class WebSocket_Client(threading.Thread): # Abort was requested, exit break - self.logMsg("##===---- WebSocketClient Stopped ----===##", 0) + log("##===---- WebSocketClient Stopped ----===##", 0) def stopClient(self): self.stopWebsocket = True self.client.close() - self.logMsg("Stopping thread.", 1) \ No newline at end of file + log("Stopping thread.", 1) \ No newline at end of file diff --git a/service.py b/service.py index b54a4a7c..f47be05c 100644 --- a/service.py +++ b/service.py @@ -16,9 +16,9 @@ import xbmcvfs ################################################################################################# _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) +_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) ################################################################################################# @@ -28,9 +28,9 @@ import initialsetup import kodimonitor import librarysync import player -import utils import videonodes import websocket_client as wsc +from utils import Logging, window, settings, language as lang ################################################################################################# @@ -49,8 +49,8 @@ class Service(): def __init__(self): - log = self.logMsg - window = utils.window + global log + log = Logging(self.__class__.__name__).log self.clientInfo = clientinfo.ClientInfo() self.addonName = self.clientInfo.getAddonName() @@ -58,15 +58,14 @@ class Service(): self.monitor = xbmc.Monitor() window('emby_logLevel', value=str(logLevel)) - window('emby_kodiProfile', value=xbmc.translatePath("special://profile")) - window('emby_pluginpath', value=utils.settings('useDirectPaths')) + window('emby_kodiProfile', value=xbmc.translatePath('special://profile')) # Initial logging log("======== START %s ========" % self.addonName, 0) log("Platform: %s" % (self.clientInfo.getPlatform()), 0) log("KODI Version: %s" % xbmc.getInfoLabel('System.BuildVersion'), 0) log("%s Version: %s" % (self.addonName, self.clientInfo.getVersion()), 0) - log("Using plugin paths: %s" % (utils.settings('useDirectPaths') != "true"), 0) + log("Using plugin paths: %s" % (settings('useDirectPaths') == "0"), 0) log("Log Level: %s" % logLevel, 0) # Reset window props for profile switch @@ -86,22 +85,13 @@ class Service(): # Set the minimum database version window('emby_minDBVersion', value="1.1.63") - def logMsg(self, msg, lvl=1): - - className = self.__class__.__name__ - utils.logMsg("%s %s" % (self.addonName, className), msg, lvl) - def ServiceEntryPoint(self): - log = self.logMsg - window = utils.window - lang = utils.language - # Important: Threads depending on abortRequest will not trigger # if profile switch happens more than once. monitor = self.monitor - kodiProfile = xbmc.translatePath("special://profile") + kodiProfile = xbmc.translatePath('special://profile') # Server auto-detect initialsetup.InitialSetup().setup() @@ -119,7 +109,7 @@ class Service(): if window('emby_kodiProfile') != kodiProfile: # Profile change happened, terminate this thread and others log("Kodi profile was: %s and changed to: %s. Terminating old Emby thread." - % (kodiProfile, utils.window('emby_kodiProfile')), 1) + % (kodiProfile, window('emby_kodiProfile')), 1) break @@ -167,7 +157,7 @@ class Service(): else: # Start up events self.warn_auth = True - if utils.settings('connectMsg') == "true" and self.welcome_msg: + if settings('connectMsg') == "true" and self.welcome_msg: # Reset authentication warnings self.welcome_msg = False # Get additional users @@ -291,7 +281,7 @@ class Service(): log("======== STOP %s ========" % self.addonName, 0) # Delay option -delay = int(utils.settings('startupDelay')) +delay = int(settings('startupDelay')) xbmc.log("Delaying emby startup by: %s sec..." % delay) if delay and xbmc.Monitor().waitForAbort(delay): From b9d40d91a630348935462af18483ae7863829b86 Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Thu, 16 Jun 2016 00:49:54 -0500 Subject: [PATCH 002/103] Fix SeriesName missing Prevent crash from happening. --- resources/lib/itemtypes.py | 3 +-- resources/lib/librarysync.py | 3 ++- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/resources/lib/itemtypes.py b/resources/lib/itemtypes.py index 18c90517..8151ee93 100644 --- a/resources/lib/itemtypes.py +++ b/resources/lib/itemtypes.py @@ -192,7 +192,7 @@ class Items(object): title = item['Name'] if itemtype == "Episode": - title = "%s - %s" % (item['SeriesName'], title) + title = "%s - %s" % (item.get('SeriesName', "Unknown"), title) if pdialog: percentage = int((float(count) / float(total))*100) @@ -1284,7 +1284,6 @@ class TVShows(Items): self.logMsg("Skipping: %s. SeriesId is missing." % itemid, 1) return False - seriesName = item['SeriesName'] season = item.get('ParentIndexNumber') episode = item.get('IndexNumber', -1) diff --git a/resources/lib/librarysync.py b/resources/lib/librarysync.py index d3a441dd..1335f585 100644 --- a/resources/lib/librarysync.py +++ b/resources/lib/librarysync.py @@ -1363,7 +1363,8 @@ class ManualSync(LibrarySync): if pdialog: percentage = int((float(count) / float(total))*100) - pdialog.update(percentage, message="%s - %s" % (episode['SeriesName'], episode['Name'])) + title = "%s - %s" % (episode.get('SeriesName', "Unknown"), episode['Name']) + pdialog.update(percentage, message=title) count += 1 tvshows.add_updateEpisode(episode) From e7bdfacd4750b4a7a837d6ecc50325f3ba244897 Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Thu, 16 Jun 2016 14:13:38 -0500 Subject: [PATCH 003/103] Empty show Prevent crash in the event RecursiveItemCount is missing. Sounds like a server bug if it's missing from the api --- resources/lib/itemtypes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/lib/itemtypes.py b/resources/lib/itemtypes.py index 8151ee93..25f0db93 100644 --- a/resources/lib/itemtypes.py +++ b/resources/lib/itemtypes.py @@ -1004,7 +1004,7 @@ class TVShows(Items): artwork = self.artwork API = api.API(item) - if utils.settings('syncEmptyShows') == "false" and not item['RecursiveItemCount']: + if utils.settings('syncEmptyShows') == "false" and not item.get('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 From 9314c4a363ddac26fcdb84e9bb75b79215a89def Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Thu, 16 Jun 2016 16:24:07 -0500 Subject: [PATCH 004/103] Centralized Logging --- resources/lib/artwork.py | 6 ++-- resources/lib/clientinfo.py | 30 ++++++++-------- resources/lib/playbackutils.py | 2 +- resources/lib/playlist.py | 46 +++++++++++------------- resources/lib/playutils.py | 64 ++++++++++++++-------------------- 5 files changed, 67 insertions(+), 81 deletions(-) diff --git a/resources/lib/artwork.py b/resources/lib/artwork.py index 2c8afea9..663cfb06 100644 --- a/resources/lib/artwork.py +++ b/resources/lib/artwork.py @@ -279,7 +279,7 @@ class Artwork(): # 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.setUrl(self.double_urlencode(url)) newThread.setHost(self.xbmc_host, self.xbmc_port) newThread.setAuth(self.xbmc_username, self.xbmc_password) newThread.start() @@ -519,7 +519,7 @@ class Artwork(): % (self.server, item_id, item_type)) return image - def getAllArtwork(self, item, parent_artwork=False): + def getAllArtwork(self, item, parentInfo=False): itemid = item['Id'] artworks = item['ImageTags'] @@ -566,7 +566,7 @@ class Artwork(): allartworks[art] = artwork # Process parent items if the main item is missing artwork - if parent_artwork: + if parentInfo: # Process parent backdrops if not allartworks['Backdrop']: diff --git a/resources/lib/clientinfo.py b/resources/lib/clientinfo.py index 8b3fe655..d5550388 100644 --- a/resources/lib/clientinfo.py +++ b/resources/lib/clientinfo.py @@ -9,7 +9,7 @@ import xbmc import xbmcaddon import xbmcvfs -import utils +from utils import Logging, window, settings ################################################################################################# @@ -19,14 +19,12 @@ class ClientInfo(): def __init__(self): + global log + log = Logging(self.__class__.__name__).log + self.addon = xbmcaddon.Addon() self.addonName = self.getAddonName() - def logMsg(self, msg, lvl=1): - - className = self.__class__.__name__ - utils.logMsg("%s %s" % (self.addonName, className), msg, lvl) - def getAddonName(self): # Used for logging @@ -42,11 +40,11 @@ class ClientInfo(): def getDeviceName(self): - if utils.settings('deviceNameOpt') == "false": + if settings('deviceNameOpt') == "false": # Use Kodi's deviceName deviceName = xbmc.getInfoLabel('System.FriendlyName').decode('utf-8') else: - deviceName = utils.settings('deviceName') + deviceName = settings('deviceName') deviceName = deviceName.replace("\"", "_") deviceName = deviceName.replace("/", "_") @@ -71,16 +69,18 @@ class ClientInfo(): def getDeviceId(self, reset=False): - clientId = utils.window('emby_deviceId') + clientId = window('emby_deviceId') if clientId: return clientId addon_path = self.addon.getAddonInfo('path').decode('utf-8') if os.path.supports_unicode_filenames: - GUID_file = xbmc.translatePath(os.path.join(addon_path, "machine_guid")).decode('utf-8') + path = os.path.join(addon_path, "machine_guid") else: - GUID_file = xbmc.translatePath(os.path.join(addon_path.encode("utf-8"), "machine_guid")).decode('utf-8') - + path = os.path.join(addon_path.encode('utf-8'), "machine_guid") + + GUID_file = xbmc.translatePath(path).decode('utf-8') + if reset and xbmcvfs.exists(GUID_file): # Reset the file xbmcvfs.delete(GUID_file) @@ -88,14 +88,14 @@ class ClientInfo(): GUID = xbmcvfs.File(GUID_file) clientId = GUID.read() if not clientId: - self.logMsg("Generating a new deviceid...", 1) + log("Generating a new deviceid...", 1) clientId = str("%012X" % uuid4()) GUID = xbmcvfs.File(GUID_file, 'w') GUID.write(clientId) GUID.close() - self.logMsg("DeviceId loaded: %s" % clientId, 1) - utils.window('emby_deviceId', value=clientId) + log("DeviceId loaded: %s" % clientId, 1) + window('emby_deviceId', value=clientId) return clientId \ No newline at end of file diff --git a/resources/lib/playbackutils.py b/resources/lib/playbackutils.py index 0afb0c8b..4da87f27 100644 --- a/resources/lib/playbackutils.py +++ b/resources/lib/playbackutils.py @@ -271,7 +271,7 @@ class PlaybackUtils(): def setArtwork(self, listItem): # Set up item and item info - allartwork = self.artwork.getAllArtwork(self.item, parent_artwork=True) + allartwork = self.artwork.getAllArtwork(self.item, parentInfo=True) # Set artwork for listitem arttypes = { diff --git a/resources/lib/playlist.py b/resources/lib/playlist.py index bcd34a46..1f0819b6 100644 --- a/resources/lib/playlist.py +++ b/resources/lib/playlist.py @@ -13,7 +13,7 @@ import playutils import playbackutils import embydb_functions as embydb import read_embyserver as embyserver -import utils +from utils import Logging, window, settings, language as lang, kodiSQL ################################################################################################# @@ -23,25 +23,21 @@ class Playlist(): def __init__(self): + global log + log = Logging(self.__class__.__name__).log + 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.userid = window('emby_currUser') + self.server = 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') + embyconn = kodiSQL('emby') embycursor = embyconn.cursor() emby_db = embydb.Embydb_Functions(embycursor) @@ -49,8 +45,8 @@ class Playlist(): playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO) playlist.clear() - self.logMsg("---*** PLAY ALL ***---", 1) - self.logMsg("Items: %s and start at: %s" % (itemids, startat), 1) + log("---*** PLAY ALL ***---", 1) + log("Items: %s and start at: %s" % (itemids, startat), 1) started = False window('emby_customplaylist', value="true") @@ -66,14 +62,14 @@ class Playlist(): mediatype = embydb_item[4] except TypeError: # Item is not found in our database, add item manually - self.logMsg("Item was not found in the database, manually adding item.", 1) + log("Item was not found in the database, manually adding item.", 1) item = self.emby.getItem(itemid) self.addtoPlaylist_xbmc(playlist, item) else: # Add to playlist self.addtoPlaylist(dbid, mediatype) - self.logMsg("Adding %s to playlist." % itemid, 1) + log("Adding %s to playlist." % itemid, 1) if not started: started = True @@ -84,12 +80,12 @@ class Playlist(): def modifyPlaylist(self, itemids): - embyconn = utils.kodiSQL('emby') + embyconn = kodiSQL('emby') embycursor = embyconn.cursor() emby_db = embydb.Embydb_Functions(embycursor) - self.logMsg("---*** ADD TO PLAYLIST ***---", 1) - self.logMsg("Items: %s" % itemids, 1) + log("---*** ADD TO PLAYLIST ***---", 1) + log("Items: %s" % itemids, 1) player = xbmc.Player() playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO) @@ -107,7 +103,7 @@ class Playlist(): # Add to playlist self.addtoPlaylist(dbid, mediatype) - self.logMsg("Adding %s to playlist." % itemid, 1) + log("Adding %s to playlist." % itemid, 1) self.verifyPlaylist() embycursor.close() @@ -130,17 +126,17 @@ class Playlist(): else: pl['params']['item'] = {'file': url} - self.logMsg(xbmc.executeJSONRPC(json.dumps(pl)), 2) + log(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) + log("Failed to retrieve playurl.", 1) return - self.logMsg("Playurl: %s" % playurl) + log("Playurl: %s" % playurl) listitem = xbmcgui.ListItem() playbackutils.PlaybackUtils(item).setProperties(playurl, listitem) @@ -164,7 +160,7 @@ class Playlist(): else: pl['params']['item'] = {'file': url} - self.logMsg(xbmc.executeJSONRPC(json.dumps(pl)), 2) + log(xbmc.executeJSONRPC(json.dumps(pl)), 2) def verifyPlaylist(self): @@ -178,7 +174,7 @@ class Playlist(): 'playlistid': 1 } } - self.logMsg(xbmc.executeJSONRPC(json.dumps(pl)), 2) + log(xbmc.executeJSONRPC(json.dumps(pl)), 2) def removefromPlaylist(self, position): @@ -193,4 +189,4 @@ class Playlist(): 'position': position } } - self.logMsg(xbmc.executeJSONRPC(json.dumps(pl)), 2) + log(xbmc.executeJSONRPC(json.dumps(pl)), 2) \ No newline at end of file diff --git a/resources/lib/playutils.py b/resources/lib/playutils.py index a1de2948..ed9b57b0 100644 --- a/resources/lib/playutils.py +++ b/resources/lib/playutils.py @@ -7,7 +7,7 @@ import xbmcgui import xbmcvfs import clientinfo -import utils +from utils import Logging, window, settings, language as lang ################################################################################################# @@ -17,41 +17,37 @@ class PlayUtils(): def __init__(self, item): + global log + log = Logging(self.__class__.__name__).log + 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) + self.userid = window('emby_currUser') + self.server = window('emby_server%s' % self.userid) 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"): + 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) + log("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) + log("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) + log("File is direct playing.", 1) playurl = self.directPlay() playurl = playurl.encode('utf-8') # Set playmethod property @@ -59,14 +55,14 @@ class PlayUtils(): elif self.isDirectStream(): - self.logMsg("File is direct streaming.", 1) + log("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) + log("File is transcoding.", 1) playurl = self.transcoding() # Set playmethod property window('emby_%s.playmethod' % playurl, value="Transcode") @@ -88,21 +84,18 @@ class PlayUtils(): 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) + log("Can't direct play, play from HTTP enabled.", 1) return False videotrack = self.item['MediaSources'][0]['Name'] transcodeH265 = settings('transcodeH265') videoprofiles = [x['Profile'] for x in self.item['MediaSources'][0]['MediaStreams'] if 'Profile' in x] - transcodeHi10P = utils.settings('transcodeHi10P') + transcodeHi10P = settings('transcodeHi10P') if transcodeHi10P == "true" and "H264" in videotrack and "High 10" in videoprofiles: return False @@ -116,7 +109,7 @@ class PlayUtils(): '2': 720, '3': 1080 } - self.logMsg("Resolution is: %sP, transcode for resolution: %sP+" + log("Resolution is: %sP, transcode for resolution: %sP+" % (resolution, res[transcodeH265]), 1) if res[transcodeH265] <= resolution: return False @@ -124,19 +117,19 @@ class PlayUtils(): 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) + log("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.") + log("Unable to direct play.", 1) try: count = int(settings('failCount')) except ValueError: count = 0 - self.logMsg("Direct play failed: %s times." % count, 1) + log("Direct play failed: %s times." % count, 1) if count < 2: # Let the user know that direct play failed @@ -192,23 +185,22 @@ class PlayUtils(): # Convert path to direct play path = self.directPlay() - self.logMsg("Verifying path: %s" % path, 1) + log("Verifying path: %s" % path, 1) if xbmcvfs.exists(path): - self.logMsg("Path exists.", 1) + log("Path exists.", 1) return True elif ":" not in path: - self.logMsg("Can't verify path, assumed linux. Still try to direct play.", 1) + log("Can't verify path, assumed linux. Still try to direct play.", 1) return True else: - self.logMsg("Failed to find file.", 1) + log("Failed to find file.", 1) return False def isDirectStream(self): - videotrack = self.item['MediaSources'][0]['Name'] transcodeH265 = utils.settings('transcodeH265') videoprofiles = [x['Profile'] for x in self.item['MediaSources'][0]['MediaStreams'] if 'Profile' in x] @@ -226,7 +218,7 @@ class PlayUtils(): '2': 720, '3': 1080 } - self.logMsg("Resolution is: %sP, transcode for resolution: %sP+" + log("Resolution is: %sP, transcode for resolution: %sP+" % (resolution, res[transcodeH265]), 1) if res[transcodeH265] <= resolution: return False @@ -239,7 +231,7 @@ class PlayUtils(): # Verify the bitrate if not self.isNetworkSufficient(): - self.logMsg("The network speed is insufficient to direct stream file.", 1) + log("The network speed is insufficient to direct stream file.", 1) return False return True @@ -258,15 +250,14 @@ class PlayUtils(): 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) + log("Bitrate value is missing.", 1) else: - self.logMsg("The add-on settings bitrate is: %s, the video bitrate required is: %s" + log("The add-on settings bitrate is: %s, the video bitrate required is: %s" % (settings, sourceBitrate), 1) if settings < sourceBitrate: return False @@ -329,7 +320,6 @@ class PlayUtils(): def audioSubsPref(self, url, listitem): - lang = utils.language dialog = xbmcgui.Dialog() # For transcoding only # Present the list of audio to select from From 1dac1c4f4b8bfc43c199a73d042eb3dfb8fb4df2 Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Fri, 17 Jun 2016 14:52:53 -0500 Subject: [PATCH 005/103] Fix typo --- resources/lib/artwork.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/lib/artwork.py b/resources/lib/artwork.py index 663cfb06..4ed0b8d9 100644 --- a/resources/lib/artwork.py +++ b/resources/lib/artwork.py @@ -427,7 +427,7 @@ class Artwork(): # Only for the main backdrop, poster if (window('emby_initialScan') != "true" and - imageType in ("fanart", "poster")): + image_type in ("fanart", "poster")): # Delete current entry before updating with the new one self.deleteCachedArtwork(url) From 02e7c2946bf70c3ede08e95583e1ef2240dd2298 Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Fri, 17 Jun 2016 16:42:48 -0500 Subject: [PATCH 006/103] Centralize path validation and logging --- resources/lib/itemtypes.py | 275 ++++++++++++++++--------------------- 1 file changed, 117 insertions(+), 158 deletions(-) diff --git a/resources/lib/itemtypes.py b/resources/lib/itemtypes.py index 25f0db93..0bf87c69 100644 --- a/resources/lib/itemtypes.py +++ b/resources/lib/itemtypes.py @@ -14,11 +14,11 @@ 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 +from utils import Logging, window, settings, language as lang ################################################################################################## @@ -28,6 +28,9 @@ class Items(object): def __init__(self, embycursor, kodicursor): + global log + log = Logging(self.__class__.__name__).log + self.embycursor = embycursor self.kodicursor = kodicursor @@ -35,23 +38,18 @@ class Items(object): 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.kodiversion = int(xbmc.getInfoLabel('System.BuildVersion')[:2]) + self.directpath = settings('useDirectPaths') == "1" + self.music_enabled = settings('enableMusic') == "true" + self.contentmsg = settings('newContent') == "true" + self.newvideo_time = int(settings('newvideotime'))*1000 + self.newmusic_time = int(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 @@ -81,7 +79,7 @@ class Items(object): if total == 0: return False - self.logMsg("Processing %s: %s" % (process, items), 1) + log("Processing %s: %s" % (process, items), 1) if pdialog: pdialog.update(heading="Processing %s: %s items" % (process, total)) @@ -173,7 +171,7 @@ class Items(object): 'remove': items_process.remove } else: - self.logMsg("Unsupported itemtype: %s." % itemtype, 1) + log("Unsupported itemtype: %s." % itemtype, 1) actions = {} if actions.get(process): @@ -204,12 +202,29 @@ class Items(object): if musicconn is not None: # close connection for special types - self.logMsg("Updating music database.", 1) + log("Updating music database.", 1) musicconn.commit() musiccursor.close() return (True, update_videolibrary) + def pathValidation(self, path): + # Verify if direct path is accessible or not + if window('emby_pathverified') != "true" and not xbmcvfs.exists(path): + resp = xbmcgui.Dialog().yesno( + heading="Can't validate path", + line1=( + "Kodi can't locate file: %s. " + "You may need to verify your network credentials in the " + "add-on settings or use the Emby path substitution " + "to format your path correctly (Emby dashboard > library). " + "Stop syncing?" % playurl)) + if resp: + window('emby_shouldStop', value="true") + return False + + return True + def contentPop(self, name, time=5000): if time: @@ -272,11 +287,11 @@ class Movies(Items): movieid = emby_dbitem[0] fileid = emby_dbitem[1] pathid = emby_dbitem[2] - self.logMsg("movieid: %s fileid: %s pathid: %s" % (movieid, fileid, pathid), 1) + log("movieid: %s fileid: %s pathid: %s" % (movieid, fileid, pathid), 1) except TypeError: update_item = False - self.logMsg("movieid: %s not found." % itemid, 2) + log("movieid: %s not found." % itemid, 2) # movieid kodicursor.execute("select coalesce(max(idMovie),0) from movie") movieid = kodicursor.fetchone()[0] + 1 @@ -290,12 +305,12 @@ class Movies(Items): 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) + log("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) + log("View tag found: %s" % viewtag, 2) # fileId information checksum = API.getChecksum() @@ -338,7 +353,7 @@ class Movies(Items): try: trailer = "plugin://plugin.video.emby/trailer/?id=%s&mode=play" % result[0]['Id'] except IndexError: - self.logMsg("Failed to process local trailer.", 1) + log("Failed to process local trailer.", 1) trailer = None else: # Try to get the youtube trailer @@ -350,7 +365,7 @@ class Movies(Items): try: trailerId = trailer.rsplit('=', 1)[1] except IndexError: - self.logMsg("Failed to process trailer: %s" % trailer, 1) + log("Failed to process trailer: %s" % trailer, 1) trailer = None else: trailer = "plugin://plugin.video.youtube/play/?video_id=%s" % trailerId @@ -367,22 +382,11 @@ class Movies(Items): 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 + if not self.pathValidation(playurl): + return False path = playurl.replace(filename, "") - utils.window('emby_pathverified', value="true") + window('emby_pathverified', value="true") else: # Set plugin path and media flags using real filename path = "plugin://plugin.video.emby.movies/" @@ -398,7 +402,7 @@ class Movies(Items): ##### UPDATE THE MOVIE ##### if update_item: - self.logMsg("UPDATE movie itemid: %s - Title: %s" % (itemid, title), 1) + log("UPDATE movie itemid: %s - Title: %s" % (itemid, title), 1) # Update the movie entry query = ' '.join(( @@ -418,7 +422,7 @@ class Movies(Items): ##### OR ADD THE MOVIE ##### else: - self.logMsg("ADD movie itemid: %s - Title: %s" % (itemid, title), 1) + log("ADD movie itemid: %s - Title: %s" % (itemid, title), 1) # Add path pathid = self.kodi_db.addPath(path) @@ -528,10 +532,10 @@ class Movies(Items): try: movieid = emby_dbitem[0] except TypeError: - self.logMsg("Failed to add: %s to boxset." % movie['Name'], 1) + log("Failed to add: %s to boxset." % movie['Name'], 1) continue - self.logMsg("New addition to boxset %s: %s" % (title, movie['Name']), 1) + log("New addition to boxset %s: %s" % (title, movie['Name']), 1) self.kodi_db.assignBoxset(setid, movieid) # Update emby reference emby_db.updateParentId(itemid, setid) @@ -542,7 +546,7 @@ class Movies(Items): # Process removals from boxset for movie in process: movieid = current[movie] - self.logMsg("Remove from boxset %s: %s" % (title, movieid)) + log("Remove from boxset %s: %s" % (title, movieid)) self.kodi_db.removefromBoxset(movieid) # Update emby reference emby_db.updateParentId(movie, None) @@ -567,7 +571,7 @@ class Movies(Items): try: movieid = emby_dbitem[0] fileid = emby_dbitem[1] - self.logMsg( + log( "Update playstate for movie: %s fileid: %s" % (item['Name'], fileid), 1) except TypeError: @@ -585,7 +589,7 @@ class Movies(Items): resume = API.adjustResume(userdata['Resume']) total = round(float(runtime), 6) - self.logMsg("%s New resume point: %s" % (itemid, resume)) + log("%s New resume point: %s" % (itemid, resume)) self.kodi_db.addPlaystate(fileid, resume, total, playcount, dateplayed) emby_db.updateReference(itemid, checksum) @@ -601,7 +605,7 @@ class Movies(Items): kodiid = emby_dbitem[0] fileid = emby_dbitem[1] mediatype = emby_dbitem[4] - self.logMsg("Removing %sid: %s fileid: %s" % (mediatype, kodiid, fileid), 1) + log("Removing %sid: %s fileid: %s" % (mediatype, kodiid, fileid), 1) except TypeError: return @@ -627,7 +631,7 @@ class Movies(Items): kodicursor.execute("DELETE FROM sets WHERE idSet = ?", (kodiid,)) - self.logMsg("Deleted %s %s from kodi database" % (mediatype, itemid), 1) + log("Deleted %s %s from kodi database" % (mediatype, itemid), 1) class MusicVideos(Items): @@ -667,11 +671,11 @@ class MusicVideos(Items): mvideoid = emby_dbitem[0] fileid = emby_dbitem[1] pathid = emby_dbitem[2] - self.logMsg("mvideoid: %s fileid: %s pathid: %s" % (mvideoid, fileid, pathid), 1) + log("mvideoid: %s fileid: %s pathid: %s" % (mvideoid, fileid, pathid), 1) except TypeError: update_item = False - self.logMsg("mvideoid: %s not found." % itemid, 2) + log("mvideoid: %s not found." % itemid, 2) # mvideoid kodicursor.execute("select coalesce(max(idMVideo),0) from musicvideo") mvideoid = kodicursor.fetchone()[0] + 1 @@ -685,12 +689,12 @@ class MusicVideos(Items): 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) + log("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) + log("View tag found: %s" % viewtag, 2) # fileId information checksum = API.getChecksum() @@ -726,22 +730,11 @@ class MusicVideos(Items): 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 + if not self.pathValidation(playurl): + return False path = playurl.replace(filename, "") - utils.window('emby_pathverified', value="true") + window('emby_pathverified', value="true") else: # Set plugin path and media flags using real filename path = "plugin://plugin.video.emby.musicvideos/" @@ -757,7 +750,7 @@ class MusicVideos(Items): ##### UPDATE THE MUSIC VIDEO ##### if update_item: - self.logMsg("UPDATE mvideo itemid: %s - Title: %s" % (itemid, title), 1) + log("UPDATE mvideo itemid: %s - Title: %s" % (itemid, title), 1) # Update path query = "UPDATE path SET strPath = ? WHERE idPath = ?" @@ -783,7 +776,7 @@ class MusicVideos(Items): ##### OR ADD THE MUSIC VIDEO ##### else: - self.logMsg("ADD mvideo itemid: %s - Title: %s" % (itemid, title), 1) + log("ADD mvideo itemid: %s - Title: %s" % (itemid, title), 1) # Add path query = ' '.join(( @@ -883,7 +876,7 @@ class MusicVideos(Items): try: mvideoid = emby_dbitem[0] fileid = emby_dbitem[1] - self.logMsg( + log( "Update playstate for musicvideo: %s fileid: %s" % (item['Name'], fileid), 1) except TypeError: @@ -915,7 +908,7 @@ class MusicVideos(Items): mvideoid = emby_dbitem[0] fileid = emby_dbitem[1] pathid = emby_dbitem[2] - self.logMsg("Removing mvideoid: %s fileid: %s" % (mvideoid, fileid, pathid), 1) + log("Removing mvideoid: %s fileid: %s" % (mvideoid, fileid, pathid), 1) except TypeError: return @@ -941,7 +934,7 @@ class MusicVideos(Items): 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) + log("Deleted musicvideo %s from kodi database" % itemid, 1) class TVShows(Items): @@ -1004,8 +997,8 @@ class TVShows(Items): artwork = self.artwork API = api.API(item) - if utils.settings('syncEmptyShows') == "false" and not item.get('RecursiveItemCount'): - self.logMsg("Skipping empty show: %s" % item['Name'], 1) + if settings('syncEmptyShows') == "false" and not item.get('RecursiveItemCount'): + log("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 @@ -1016,11 +1009,11 @@ class TVShows(Items): try: showid = emby_dbitem[0] pathid = emby_dbitem[2] - self.logMsg("showid: %s pathid: %s" % (showid, pathid), 1) + log("showid: %s pathid: %s" % (showid, pathid), 1) except TypeError: update_item = False - self.logMsg("showid: %s not found." % itemid, 2) + log("showid: %s not found." % itemid, 2) kodicursor.execute("select coalesce(max(idShow),0) from tvshow") showid = kodicursor.fetchone()[0] + 1 @@ -1033,7 +1026,7 @@ class TVShows(Items): 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) + log("showid: %s missing from Kodi, repairing the entry." % showid, 1) # Force re-add episodes after the show is re-created. force_episodes = True @@ -1041,7 +1034,7 @@ class TVShows(Items): 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) + log("View tag found: %s" % viewtag, 2) # fileId information checksum = API.getChecksum() @@ -1078,21 +1071,10 @@ class TVShows(Items): 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 + if not self.pathValidation(playurl): + return False - utils.window('emby_pathverified', value="true") + window('emby_pathverified', value="true") else: # Set plugin path toplevelpath = "plugin://plugin.video.emby.tvshows/" @@ -1101,7 +1083,7 @@ class TVShows(Items): ##### UPDATE THE TVSHOW ##### if update_item: - self.logMsg("UPDATE tvshow itemid: %s - Title: %s" % (itemid, title), 1) + log("UPDATE tvshow itemid: %s - Title: %s" % (itemid, title), 1) # Update the tvshow entry query = ' '.join(( @@ -1119,7 +1101,7 @@ class TVShows(Items): ##### OR ADD THE TVSHOW ##### else: - self.logMsg("ADD tvshow itemid: %s - Title: %s" % (itemid, title), 1) + log("ADD tvshow itemid: %s - Title: %s" % (itemid, title), 1) # Add top path toppathid = self.kodi_db.addPath(toplevelpath) @@ -1190,7 +1172,7 @@ class TVShows(Items): 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) + log("Repairing episodes for showid: %s %s" % (showid, title), 1) all_episodes = emby.getEpisodesbyShow(itemid) self.added_episode(all_episodes['Items'], None) @@ -1239,11 +1221,11 @@ class TVShows(Items): episodeid = emby_dbitem[0] fileid = emby_dbitem[1] pathid = emby_dbitem[2] - self.logMsg("episodeid: %s fileid: %s pathid: %s" % (episodeid, fileid, pathid), 1) + log("episodeid: %s fileid: %s pathid: %s" % (episodeid, fileid, pathid), 1) except TypeError: update_item = False - self.logMsg("episodeid: %s not found." % itemid, 2) + log("episodeid: %s not found." % itemid, 2) # episodeid kodicursor.execute("select coalesce(max(idEpisode),0) from episode") episodeid = kodicursor.fetchone()[0] + 1 @@ -1257,7 +1239,7 @@ class TVShows(Items): 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) + log("episodeid: %s missing from Kodi, repairing the entry." % episodeid, 1) # fileId information checksum = API.getChecksum() @@ -1281,7 +1263,7 @@ class TVShows(Items): seriesId = item['SeriesId'] except KeyError: # Missing seriesId, skip - self.logMsg("Skipping: %s. SeriesId is missing." % itemid, 1) + log("Skipping: %s. SeriesId is missing." % itemid, 1) return False season = item.get('ParentIndexNumber') @@ -1319,7 +1301,7 @@ class TVShows(Items): try: showid = show[0] except TypeError: - self.logMsg("Skipping: %s. Unable to add series: %s." % (itemid, seriesId)) + log("Skipping: %s. Unable to add series: %s." % (itemid, seriesId)) return False seasonid = self.kodi_db.addSeason(showid, season) @@ -1336,22 +1318,11 @@ class TVShows(Items): 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 + if not self.pathValidation(playurl): + return False path = playurl.replace(filename, "") - utils.window('emby_pathverified', value="true") + window('emby_pathverified', value="true") else: # Set plugin path and media flags using real filename path = "plugin://plugin.video.emby.tvshows/%s/" % seriesId @@ -1367,7 +1338,7 @@ class TVShows(Items): ##### UPDATE THE EPISODE ##### if update_item: - self.logMsg("UPDATE episode itemid: %s - Title: %s" % (itemid, title), 1) + log("UPDATE episode itemid: %s - Title: %s" % (itemid, title), 1) # Update the movie entry if self.kodiversion in (16, 17): @@ -1401,7 +1372,7 @@ class TVShows(Items): ##### OR ADD THE EPISODE ##### else: - self.logMsg("ADD episode itemid: %s - Title: %s" % (itemid, title), 1) + log("ADD episode itemid: %s - Title: %s" % (itemid, title), 1) # Add path pathid = self.kodi_db.addPath(path) @@ -1504,7 +1475,7 @@ class TVShows(Items): kodiid = emby_dbitem[0] fileid = emby_dbitem[1] mediatype = emby_dbitem[4] - self.logMsg( + log( "Update playstate for %s: %s fileid: %s" % (mediatype, item['Name'], fileid), 1) except TypeError: @@ -1523,7 +1494,7 @@ class TVShows(Items): resume = API.adjustResume(userdata['Resume']) total = round(float(runtime), 6) - self.logMsg("%s New resume point: %s" % (itemid, resume)) + log("%s New resume point: %s" % (itemid, resume)) self.kodi_db.addPlaystate(fileid, resume, total, playcount, dateplayed) if not self.directpath and not resume: @@ -1561,7 +1532,7 @@ class TVShows(Items): pathid = emby_dbitem[2] parentid = emby_dbitem[3] mediatype = emby_dbitem[4] - self.logMsg("Removing %s kodiid: %s fileid: %s" % (mediatype, kodiid, fileid), 1) + log("Removing %s kodiid: %s fileid: %s" % (mediatype, kodiid, fileid), 1) except TypeError: return @@ -1651,14 +1622,14 @@ class TVShows(Items): self.removeShow(parentid) emby_db.removeItem_byKodiId(parentid, "tvshow") - self.logMsg("Deleted %s: %s from kodi database" % (mediatype, itemid), 1) + log("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) + log("Removed tvshow: %s." % kodiid, 2) def removeSeason(self, kodiid): @@ -1666,7 +1637,7 @@ class TVShows(Items): self.artwork.deleteArtwork(kodiid, "season", kodicursor) kodicursor.execute("DELETE FROM seasons WHERE idSeason = ?", (kodiid,)) - self.logMsg("Removed season: %s." % kodiid, 2) + log("Removed season: %s." % kodiid, 2) def removeEpisode(self, kodiid, fileid): @@ -1675,7 +1646,7 @@ class TVShows(Items): 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) + log("Removed episode: %s." % kodiid, 2) class Music(Items): @@ -1684,12 +1655,12 @@ class Music(Items): Items.__init__(self, embycursor, musiccursor) - self.directstream = utils.settings('streamMusic') == "true" - self.enableimportsongrating = utils.settings('enableImportSongRating') == "true" - self.enableexportsongrating = utils.settings('enableExportSongRating') == "true" - self.enableupdatesongrating = utils.settings('enableUpdateSongRating') == "true" - self.userid = utils.window('emby_currUser') - self.server = utils.window('emby_server%s' % self.userid) + self.directstream = settings('streamMusic') == "true" + self.enableimportsongrating = settings('enableImportSongRating') == "true" + self.enableexportsongrating = settings('enableExportSongRating') == "true" + self.enableupdatesongrating = settings('enableUpdateSongRating') == "true" + self.userid = window('emby_currUser') + self.server = window('emby_server%s' % self.userid) def added(self, items, pdialog): @@ -1749,7 +1720,7 @@ class Music(Items): artistid = emby_dbitem[0] except TypeError: update_item = False - self.logMsg("artistid: %s not found." % itemid, 2) + log("artistid: %s not found." % itemid, 2) ##### The artist details ##### lastScraped = datetime.now().strftime('%Y-%m-%d %H:%M:%S') @@ -1776,13 +1747,13 @@ class Music(Items): ##### UPDATE THE ARTIST ##### if update_item: - self.logMsg("UPDATE artist itemid: %s - Name: %s" % (itemid, name), 1) + log("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) + log("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) @@ -1830,7 +1801,7 @@ class Music(Items): albumid = emby_dbitem[0] except TypeError: update_item = False - self.logMsg("albumid: %s not found." % itemid, 2) + log("albumid: %s not found." % itemid, 2) ##### The album details ##### lastScraped = datetime.now().strftime('%Y-%m-%d %H:%M:%S') @@ -1861,13 +1832,13 @@ class Music(Items): ##### UPDATE THE ALBUM ##### if update_item: - self.logMsg("UPDATE album itemid: %s - Name: %s" % (itemid, name), 1) + log("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) + log("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) @@ -2000,7 +1971,7 @@ class Music(Items): albumid = emby_dbitem[3] except TypeError: update_item = False - self.logMsg("songid: %s not found." % itemid, 2) + log("songid: %s not found." % itemid, 2) ##### The song details ##### checksum = API.getChecksum() @@ -2047,27 +2018,15 @@ class Music(Items): 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 + if not self.pathValidation(playurl): + return False path = playurl.replace(filename, "") - utils.window('emby_pathverified', value="true") + window('emby_pathverified', value="true") ##### UPDATE THE SONG ##### if update_item: - self.logMsg("UPDATE song itemid: %s - Title: %s" % (itemid, title), 1) + log("UPDATE song itemid: %s - Title: %s" % (itemid, title), 1) # Update path query = "UPDATE path SET strPath = ? WHERE idPath = ?" @@ -2090,7 +2049,7 @@ class Music(Items): ##### OR ADD THE SONG ##### else: - self.logMsg("ADD song itemid: %s - Title: %s" % (itemid, title), 1) + log("ADD song itemid: %s - Title: %s" % (itemid, title), 1) # Add path pathid = self.kodi_db.addPath(path) @@ -2103,27 +2062,27 @@ class Music(Items): # 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) + log("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) + log("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) + log("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) + log("Found albumid: %s" % albumid, 1) except TypeError: # No album found, create a single's album - self.logMsg("Failed to add album. Creating singles.", 1) + log("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: @@ -2305,7 +2264,7 @@ class Music(Items): try: kodiid = emby_dbitem[0] mediatype = emby_dbitem[4] - self.logMsg("Update playstate for %s: %s" % (mediatype, item['Name']), 1) + log("Update playstate for %s: %s" % (mediatype, item['Name']), 1) except TypeError: return @@ -2313,8 +2272,8 @@ class Music(Items): #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) + if window("ignore-update-%s" %itemid): + window("ignore-update-%s" %itemid,clear=True) return # Process playstates @@ -2344,7 +2303,7 @@ class Music(Items): try: kodiid = emby_dbitem[0] mediatype = emby_dbitem[4] - self.logMsg("Removing %s kodiid: %s" % (mediatype, kodiid), 1) + log("Removing %s kodiid: %s" % (mediatype, kodiid), 1) except TypeError: return @@ -2412,7 +2371,7 @@ class Music(Items): # Remove artist self.removeArtist(kodiid) - self.logMsg("Deleted %s: %s from kodi database" % (mediatype, itemid), 1) + log("Deleted %s: %s from kodi database" % (mediatype, itemid), 1) def removeSong(self, kodiid): From 5658801f7280e51ccea021653e1d9155bd4ed190 Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Fri, 17 Jun 2016 22:03:28 -0500 Subject: [PATCH 007/103] Centralized logging --- resources/lib/connect.py | 58 ++++---- resources/lib/downloadutils.py | 113 ++++++++-------- resources/lib/image_cache_thread.py | 32 +++-- resources/lib/initialsetup.py | 77 +++++------ resources/lib/kodidb_functions.py | 44 +++--- resources/lib/kodimonitor.py | 80 ++++++----- resources/lib/librarysync.py | 202 +++++++++++++--------------- resources/lib/musicutils.py | 26 ++-- resources/lib/player.py | 75 +++++------ resources/lib/utils.py | 2 +- 10 files changed, 336 insertions(+), 373 deletions(-) diff --git a/resources/lib/connect.py b/resources/lib/connect.py index 2bd5c05d..87dbc2e1 100644 --- a/resources/lib/connect.py +++ b/resources/lib/connect.py @@ -6,8 +6,8 @@ import json import requests import logging -import utils import clientinfo +from utils import Logging, window ################################################################################################## @@ -34,28 +34,26 @@ class ConnectUtils(): def __init__(self): + global log + log = Logging(self.__class__.__name__).log + self.__dict__ = self._shared_state - def logMsg(self, msg, lvl=1): - - className = self.__class__.__name__ - utils.logMsg("%s %s" % (self.addonName, className), msg, lvl) - def setUserId(self, userId): # Reserved for userclient only self.userId = userId - self.logMsg("Set connect userId: %s" % userId, 2) + log("Set connect userId: %s" % userId, 2) def setServer(self, server): # Reserved for userclient only self.server = server - self.logMsg("Set connect server: %s" % server, 2) + log("Set connect server: %s" % server, 2) def setToken(self, token): # Reserved for userclient only self.token = token - self.logMsg("Set connect token: %s" % token, 2) + log("Set connect token: %s" % token, 2) def startSession(self): @@ -73,7 +71,7 @@ class ConnectUtils(): if self.sslclient is not None: verify = self.sslclient except: - self.logMsg("Could not load SSL settings.", 1) + log("Could not load SSL settings.", 1) # Start session self.c = requests.Session() @@ -83,13 +81,13 @@ class ConnectUtils(): self.c.mount("http://", requests.adapters.HTTPAdapter(max_retries=1)) self.c.mount("https://", requests.adapters.HTTPAdapter(max_retries=1)) - self.logMsg("Requests session started on: %s" % self.server, 1) + log("Requests session started on: %s" % self.server, 1) def stopSession(self): try: self.c.close() except Exception as e: - self.logMsg("Requests session could not be terminated: %s" % e, 1) + log("Requests session could not be terminated: %s" % e, 1) def getHeader(self, authenticate=True): @@ -103,7 +101,7 @@ class ConnectUtils(): 'Content-type': 'application/x-www-form-urlencoded; charset=UTF-8', 'Accept': "application/json" } - self.logMsg("Header: %s" % header, 1) + log("Header: %s" % header, 1) else: token = self.token @@ -115,17 +113,17 @@ class ConnectUtils(): 'X-Application': "Kodi/%s" % version, 'X-Connect-UserToken': token } - self.logMsg("Header: %s" % header, 1) + log("Header: %s" % header, 1) return header def doUrl(self, url, data=None, postBody=None, rtype="GET", parameters=None, authenticate=True, timeout=None): - window = utils.window - - self.logMsg("=== ENTER connectUrl ===", 2) + log("=== ENTER connectUrl ===", 2) + default_link = "" + if timeout is None: timeout = self.timeout @@ -209,25 +207,25 @@ class ConnectUtils(): verify=verifyssl) ##### THE RESPONSE ##### - self.logMsg(r.url, 1) - self.logMsg(r, 1) + log(r.url, 1) + log(r, 1) if r.status_code == 204: # No body in the response - self.logMsg("====== 204 Success ======", 1) + log("====== 204 Success ======", 1) elif r.status_code == requests.codes.ok: try: # UNICODE - JSON object r = r.json() - self.logMsg("====== 200 Success ======", 1) - self.logMsg("Response: %s" % r, 1) + log("====== 200 Success ======", 1) + log("Response: %s" % r, 1) return r except: if r.headers.get('content-type') != "text/html": - self.logMsg("Unable to convert the response for: %s" % url, 1) + log("Unable to convert the response for: %s" % url, 1) else: r.raise_for_status() @@ -238,8 +236,8 @@ class ConnectUtils(): pass except requests.exceptions.ConnectTimeout as e: - self.logMsg("Server timeout at: %s" % url, 0) - self.logMsg(e, 1) + log("Server timeout at: %s" % url, 0) + log(e, 1) except requests.exceptions.HTTPError as e: @@ -255,11 +253,11 @@ class ConnectUtils(): pass except requests.exceptions.SSLError as e: - self.logMsg("Invalid SSL certificate for: %s" % url, 0) - self.logMsg(e, 1) + log("Invalid SSL certificate for: %s" % url, 0) + log(e, 1) except requests.exceptions.RequestException as e: - self.logMsg("Unknown error connecting to: %s" % url, 0) - self.logMsg(e, 1) + log("Unknown error connecting to: %s" % url, 0) + log(e, 1) - return default_link + return default_link \ No newline at end of file diff --git a/resources/lib/downloadutils.py b/resources/lib/downloadutils.py index a74ee6f2..ea55e7d1 100644 --- a/resources/lib/downloadutils.py +++ b/resources/lib/downloadutils.py @@ -9,14 +9,15 @@ import logging import xbmc import xbmcgui -import utils import clientinfo +from utils import Logging, window, settings ################################################################################################## # Disable requests logging -from requests.packages.urllib3.exceptions import InsecureRequestWarning +from requests.packages.urllib3.exceptions import InsecureRequestWarning, InsecurePlatformWarning requests.packages.urllib3.disable_warnings(InsecureRequestWarning) +requests.packages.urllib3.disable_warnings(InsecurePlatformWarning) #logging.getLogger('requests').setLevel(logging.WARNING) ################################################################################################## @@ -36,40 +37,38 @@ class DownloadUtils(): def __init__(self): + global log + log = Logging(self.__class__.__name__).log + self.__dict__ = self._shared_state - def logMsg(self, msg, lvl=1): - - className = self.__class__.__name__ - utils.logMsg("%s %s" % (self.addonName, className), msg, lvl) - def setUsername(self, username): # Reserved for userclient only self.username = username - self.logMsg("Set username: %s" % username, 2) + log("Set username: %s" % username, 2) def setUserId(self, userId): # Reserved for userclient only self.userId = userId - self.logMsg("Set userId: %s" % userId, 2) + log("Set userId: %s" % userId, 2) def setServer(self, server): # Reserved for userclient only self.server = server - self.logMsg("Set server: %s" % server, 2) + log("Set server: %s" % server, 2) def setToken(self, token): # Reserved for userclient only self.token = token - self.logMsg("Set token: %s" % token, 2) + log("Set token: %s" % token, 2) def setSSL(self, ssl, sslclient): # Reserved for userclient only self.sslverify = ssl self.sslclient = sslclient - self.logMsg("Verify SSL host certificate: %s" % ssl, 2) - self.logMsg("SSL client side certificate: %s" % sslclient, 2) + log("Verify SSL host certificate: %s" % ssl, 2) + log("SSL client side certificate: %s" % sslclient, 2) def postCapabilities(self, deviceId): @@ -94,11 +93,11 @@ class DownloadUtils(): ) } - self.logMsg("Capabilities URL: %s" % url, 2) - self.logMsg("Postdata: %s" % data, 2) + log("Capabilities URL: %s" % url, 2) + log("Postdata: %s" % data, 2) self.downloadUrl(url, postBody=data, action_type="POST") - self.logMsg("Posted capabilities to %s" % self.server, 2) + log("Posted capabilities to %s" % self.server, 2) # Attempt at getting sessionId url = "{server}/emby/Sessions?DeviceId=%s&format=json" % deviceId @@ -107,20 +106,19 @@ class DownloadUtils(): sessionId = result[0]['Id'] except (KeyError, TypeError): - self.logMsg("Failed to retrieve sessionId.", 1) + log("Failed to retrieve sessionId.", 1) else: - self.logMsg("Session: %s" % result, 2) - self.logMsg("SessionId: %s" % sessionId, 1) - utils.window('emby_sessionId', value=sessionId) + log("Session: %s" % result, 2) + log("SessionId: %s" % sessionId, 1) + window('emby_sessionId', value=sessionId) # Post any permanent additional users - additionalUsers = utils.settings('additionalUsers') + additionalUsers = settings('additionalUsers') if additionalUsers: additionalUsers = additionalUsers.split(',') - self.logMsg( - "List of permanent users added to the session: %s" + log("List of permanent users added to the session: %s" % additionalUsers, 1) # Get the user list from server to get the userId @@ -158,7 +156,7 @@ class DownloadUtils(): if self.sslclient is not None: verify = self.sslclient except: - self.logMsg("Could not load SSL settings.", 1) + log("Could not load SSL settings.", 1) # Start session self.s = requests.Session() @@ -168,18 +166,18 @@ class DownloadUtils(): self.s.mount("http://", requests.adapters.HTTPAdapter(max_retries=1)) self.s.mount("https://", requests.adapters.HTTPAdapter(max_retries=1)) - self.logMsg("Requests session started on: %s" % self.server, 1) + log("Requests session started on: %s" % self.server, 1) def stopSession(self): try: self.s.close() except: - self.logMsg("Requests session could not be terminated.", 1) + log("Requests session could not be terminated.", 1) def getHeader(self, authenticate=True): deviceName = self.clientInfo.getDeviceName() - deviceName = utils.normalize_string(deviceName.encode('utf-8')) + deviceName = deviceName.encode('utf-8') deviceId = self.clientInfo.getDeviceId() version = self.clientInfo.getVersion() @@ -195,7 +193,7 @@ class DownloadUtils(): 'Accept-Charset': 'UTF-8,*', 'Authorization': auth } - self.logMsg("Header: %s" % header, 2) + log("Header: %s" % header, 2) else: userId = self.userId @@ -212,19 +210,20 @@ class DownloadUtils(): 'Authorization': auth, 'X-MediaBrowser-Token': token } - self.logMsg("Header: %s" % header, 2) + log("Header: %s" % header, 2) return header - def downloadUrl(self, url, postBody=None, action_type="GET", parameters=None, authenticate=True): + def downloadUrl(self, url, postBody=None, action_type="GET", parameters=None, + authenticate=True): - self.logMsg("=== ENTER downloadUrl ===", 2) + log("=== ENTER downloadUrl ===", 2) default_link = "" try: # If user is authenticated - if (authenticate): + if authenticate: # Get requests session try: s = self.s @@ -243,18 +242,18 @@ class DownloadUtils(): except AttributeError: # request session does not exists # Get user information - self.userId = utils.window('emby_currUser') - self.server = utils.window('emby_server%s' % self.userId) - self.token = utils.window('emby_accessToken%s' % self.userId) + self.userId = window('emby_currUser') + self.server = window('emby_server%s' % self.userId) + self.token = window('emby_accessToken%s' % self.userId) header = self.getHeader() verifyssl = False cert = None # IF user enables ssl verification - if utils.settings('sslverify') == "true": + if settings('sslverify') == "true": verifyssl = True - if utils.settings('sslcert') != "None": - verifyssl = utils.settings('sslcert') + if settings('sslcert') != "None": + verifyssl = settings('sslcert') # Replace for the real values url = url.replace("{server}", self.server) @@ -314,23 +313,23 @@ class DownloadUtils(): verify=verifyssl) ##### THE RESPONSE ##### - self.logMsg(r.url, 2) + log(r.url, 2) if r.status_code == 204: # No body in the response - self.logMsg("====== 204 Success ======", 2) + log("====== 204 Success ======", 2) elif r.status_code == requests.codes.ok: try: # UNICODE - JSON object r = r.json() - self.logMsg("====== 200 Success ======", 2) - self.logMsg("Response: %s" % r, 2) + log("====== 200 Success ======", 2) + log("Response: %s" % r, 2) return r except: if r.headers.get('content-type') != "text/html": - self.logMsg("Unable to convert the response for: %s" % url, 1) + log("Unable to convert the response for: %s" % url, 1) else: r.raise_for_status() @@ -338,26 +337,26 @@ class DownloadUtils(): except requests.exceptions.ConnectionError as e: # Make the addon aware of status - if utils.window('emby_online') != "false": - self.logMsg("Server unreachable at: %s" % url, 0) - self.logMsg(e, 2) - utils.window('emby_online', value="false") + if window('emby_online') != "false": + log("Server unreachable at: %s" % url, 0) + log(e, 2) + window('emby_online', value="false") except requests.exceptions.ConnectTimeout as e: - self.logMsg("Server timeout at: %s" % url, 0) - self.logMsg(e, 1) + log("Server timeout at: %s" % url, 0) + log(e, 1) except requests.exceptions.HTTPError as e: if r.status_code == 401: # Unauthorized - status = utils.window('emby_serverStatus') + status = window('emby_serverStatus') if 'X-Application-Error-Code' in r.headers: # Emby server errors if r.headers['X-Application-Error-Code'] == "ParentalControl": # Parental control - access restricted - utils.window('emby_serverStatus', value="restricted") + window('emby_serverStatus', value="restricted") xbmcgui.Dialog().notification( heading="Emby server", message="Access restricted.", @@ -371,8 +370,8 @@ class DownloadUtils(): elif status not in ("401", "Auth"): # Tell userclient token has been revoked. - utils.window('emby_serverStatus', value="401") - self.logMsg("HTTP Error: %s" % e, 0) + window('emby_serverStatus', value="401") + log("HTTP Error: %s" % e, 0) xbmcgui.Dialog().notification( heading="Error connecting", message="Unauthorized.", @@ -387,11 +386,11 @@ class DownloadUtils(): pass except requests.exceptions.SSLError as e: - self.logMsg("Invalid SSL certificate for: %s" % url, 0) - self.logMsg(e, 1) + log("Invalid SSL certificate for: %s" % url, 0) + log(e, 1) except requests.exceptions.RequestException as e: - self.logMsg("Unknown error connecting to: %s" % url, 0) - self.logMsg(e, 1) + log("Unknown error connecting to: %s" % url, 0) + log(e, 1) return default_link \ No newline at end of file diff --git a/resources/lib/image_cache_thread.py b/resources/lib/image_cache_thread.py index 626be481..fdf63d63 100644 --- a/resources/lib/image_cache_thread.py +++ b/resources/lib/image_cache_thread.py @@ -1,8 +1,14 @@ +# -*- coding: utf-8 -*- + +################################################################################################# + import threading -import utils -import xbmc import requests +from utils import Logging + +################################################################################################# + class image_cache_thread(threading.Thread): urlToProcess = None @@ -13,28 +19,32 @@ class image_cache_thread(threading.Thread): xbmc_username = "" xbmc_password = "" + def __init__(self): - self.monitor = xbmc.Monitor() + + global log + log = Logging(self.__class__.__name__).log + threading.Thread.__init__(self) - - def logMsg(self, msg, lvl=1): - className = self.__class__.__name__ - utils.logMsg("%s" % className, msg, lvl) + def setUrl(self, url): + self.urlToProcess = url def setHost(self, host, port): + self.xbmc_host = host self.xbmc_port = port def setAuth(self, user, pwd): + self.xbmc_username = user self.xbmc_password = pwd def run(self): - self.logMsg("Image Caching Thread Processing : " + self.urlToProcess, 2) + log("Image Caching Thread Processing: %s" % self.urlToProcess, 2) try: response = requests.head( @@ -46,7 +56,5 @@ class image_cache_thread(threading.Thread): # We don't need the result except: pass - self.logMsg("Image Caching Thread Exited", 2) - - self.isFinished = True - \ No newline at end of file + log("Image Caching Thread Exited", 2) + self.isFinished = True \ No newline at end of file diff --git a/resources/lib/initialsetup.py b/resources/lib/initialsetup.py index 7bf0dbb9..4b1f63f3 100644 --- a/resources/lib/initialsetup.py +++ b/resources/lib/initialsetup.py @@ -9,10 +9,10 @@ import xbmc import xbmcgui import xbmcaddon -import utils import clientinfo import downloadutils import userclient +from utils import Logging, settings, language as lang, passwordsXML ################################################################################################# @@ -22,74 +22,67 @@ class InitialSetup(): def __init__(self): - self.addon = xbmcaddon.Addon() - self.__language__ = self.addon.getLocalizedString + global log + log = Logging(self.__class__.__name__).log self.clientInfo = clientinfo.ClientInfo() - self.addonName = self.clientInfo.getAddonName() self.addonId = self.clientInfo.getAddonId() self.doUtils = downloadutils.DownloadUtils() self.userClient = userclient.UserClient() - - def logMsg(self, msg, lvl=1): - - className = self.__class__.__name__ - utils.logMsg("%s %s" % (self.addonName, className), msg, lvl) def setup(self): # Check server, user, direct paths, music, direct stream if not direct path. - string = self.__language__ addonId = self.addonId ##### SERVER INFO ##### - self.logMsg("Initial setup called.", 2) + log("Initial setup called.", 2) server = self.userClient.getServer() if server: - self.logMsg("Server is already set.", 2) + log("Server is already set.", 2) return - self.logMsg("Looking for server...", 2) + log("Looking for server...", 2) server = self.getServerDetails() - self.logMsg("Found: %s" % server, 2) + log("Found: %s" % server, 2) try: prefix, ip, port = server.replace("/", "").split(":") except: # Failed to retrieve server information - self.logMsg("getServerDetails failed.", 1) + log("getServerDetails failed.", 1) xbmc.executebuiltin('Addon.OpenSettings(%s)' % addonId) return else: server_confirm = xbmcgui.Dialog().yesno( heading="Emby for Kodi", line1="Proceed with the following server?", - line2="%s %s" % (string(30169), server)) + line2="%s %s" % (lang(30169), server)) if server_confirm: # Correct server found - self.logMsg("Server is selected. Saving the information.", 1) - utils.settings('ipaddress', value=ip) - utils.settings('port', value=port) + log("Server is selected. Saving the information.", 1) + settings('ipaddress', value=ip) + settings('port', value=port) if prefix == "https": - utils.settings('https', value="true") + settings('https', value="true") else: # User selected no or cancelled the dialog - self.logMsg("No server selected.", 1) + log("No server selected.", 1) xbmc.executebuiltin('Addon.OpenSettings(%s)' % addonId) return ##### USER INFO ##### - self.logMsg("Getting user list.", 1) + log("Getting user list.", 1) url = "%s/emby/Users/Public?format=json" % server result = self.doUtils.downloadUrl(url, authenticate=False) if result == "": - self.logMsg("Unable to connect to %s" % server, 1) + log("Unable to connect to %s" % server, 1) return - self.logMsg("Response: %s" % result, 2) + log("Response: %s" % result, 2) # Process the list of users usernames = [] users_hasPassword = [] @@ -103,14 +96,14 @@ class InitialSetup(): name = "%s (secure)" % name users_hasPassword.append(name) - self.logMsg("Presenting user list: %s" % users_hasPassword, 1) - user_select = xbmcgui.Dialog().select(string(30200), users_hasPassword) + log("Presenting user list: %s" % users_hasPassword, 1) + user_select = xbmcgui.Dialog().select(lang(30200), users_hasPassword) if user_select > -1: selected_user = usernames[user_select] - self.logMsg("Selected user: %s" % selected_user, 1) - utils.settings('username', value=selected_user) + log("Selected user: %s" % selected_user, 1) + settings('username', value=selected_user) else: - self.logMsg("No user selected.", 1) + log("No user selected.", 1) xbmc.executebuiltin('Addon.OpenSettings(%s)' % addonId) ##### ADDITIONAL PROMPTS ##### @@ -126,8 +119,8 @@ class InitialSetup(): nolabel="Addon (Default)", yeslabel="Native (Direct Paths)") if directPaths: - self.logMsg("User opted to use direct paths.", 1) - utils.settings('useDirectPaths', value="1") + log("User opted to use direct paths.", 1) + settings('useDirectPaths', value="1") # ask for credentials credentials = dialog.yesno( @@ -138,15 +131,15 @@ class InitialSetup(): "during the initial scan of your content if Kodi can't " "locate your content.")) if credentials: - self.logMsg("Presenting network credentials dialog.", 1) - utils.passwordsXML() + log("Presenting network credentials dialog.", 1) + passwordsXML() musicDisabled = dialog.yesno( heading="Music Library", line1="Disable Emby music library?") if musicDisabled: - self.logMsg("User opted to disable Emby music library.", 1) - utils.settings('enableMusic', value="false") + log("User opted to disable Emby music library.", 1) + settings('enableMusic', value="false") else: # Only prompt if the user didn't select direct paths for videos if not directPaths: @@ -157,12 +150,12 @@ class InitialSetup(): "this option only if you plan on listening " "to music outside of your network.")) if musicAccess: - self.logMsg("User opted to direct stream music.", 1) - utils.settings('streamMusic', value="true") + log("User opted to direct stream music.", 1) + settings('streamMusic', value="true") def getServerDetails(self): - self.logMsg("Getting Server Details from Network", 1) + log("Getting Server Details from Network", 1) MULTI_GROUP = ("", 7359) MESSAGE = "who is EmbyServer?" @@ -176,15 +169,15 @@ 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) + log("MultiGroup : %s" % str(MULTI_GROUP), 2) + log("Sending UDP Data: %s" % MESSAGE, 2) sock.sendto(MESSAGE, MULTI_GROUP) try: data, addr = sock.recvfrom(1024) # buffer size is 1024 bytes - self.logMsg("Received Response: %s" % data) + log("Received Response: %s" % data) except: - self.logMsg("No UDP Response") + log("No UDP Response") return None else: # Get the address diff --git a/resources/lib/kodidb_functions.py b/resources/lib/kodidb_functions.py index 6c3dd8b1..86981e7b 100644 --- a/resources/lib/kodidb_functions.py +++ b/resources/lib/kodidb_functions.py @@ -7,7 +7,7 @@ import xbmc import api import artwork import clientinfo -import utils +from utils import Logging ################################################################################################## @@ -19,16 +19,14 @@ class Kodidb_Functions(): def __init__(self, cursor): + global log + log = Logging(self.__class__.__name__).log + 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): @@ -153,7 +151,7 @@ class Kodidb_Functions(): 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) + log("Add country to media, processing: %s" % country, 2) finally: # Assign country to content query = ( @@ -187,7 +185,7 @@ class Kodidb_Functions(): query = "INSERT INTO country(idCountry, strCountry) values(?, ?)" self.cursor.execute(query, (idCountry, country)) - self.logMsg("Add country to media, processing: %s" % country, 2) + log("Add country to media, processing: %s" % country, 2) finally: # Only movies have a country field @@ -232,7 +230,7 @@ class Kodidb_Functions(): query = "INSERT INTO actor(actor_id, name) values(?, ?)" self.cursor.execute(query, (actorid, name)) - self.logMsg("Add people to media, processing: %s" % name, 2) + log("Add people to media, processing: %s" % name, 2) finally: # Link person to content @@ -302,7 +300,7 @@ class Kodidb_Functions(): query = "INSERT INTO actors(idActor, strActor) values(?, ?)" self.cursor.execute(query, (actorid, name)) - self.logMsg("Add people to media, processing: %s" % name, 2) + log("Add people to media, processing: %s" % name, 2) finally: # Link person to content @@ -462,7 +460,7 @@ class Kodidb_Functions(): 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) + log("Add Genres to media, processing: %s" % genre, 2) finally: # Assign genre to item @@ -507,7 +505,7 @@ class Kodidb_Functions(): query = "INSERT INTO genre(idGenre, strGenre) values(?, ?)" self.cursor.execute(query, (idGenre, genre)) - self.logMsg("Add Genres to media, processing: %s" % genre, 2) + log("Add Genres to media, processing: %s" % genre, 2) finally: # Assign genre to item @@ -566,7 +564,7 @@ class Kodidb_Functions(): query = "INSERT INTO studio(studio_id, name) values(?, ?)" self.cursor.execute(query, (studioid, studio)) - self.logMsg("Add Studios to media, processing: %s" % studio, 2) + log("Add Studios to media, processing: %s" % studio, 2) finally: # Assign studio to item query = ( @@ -597,7 +595,7 @@ class Kodidb_Functions(): query = "INSERT INTO studio(idstudio, strstudio) values(?, ?)" self.cursor.execute(query, (studioid, studio)) - self.logMsg("Add Studios to media, processing: %s" % studio, 2) + log("Add Studios to media, processing: %s" % studio, 2) finally: # Assign studio to item if "movie" in mediatype: @@ -728,7 +726,7 @@ class Kodidb_Functions(): self.cursor.execute(query, (kodiid, mediatype)) # Add tags - self.logMsg("Adding Tags: %s" % tags, 2) + log("Adding Tags: %s" % tags, 2) for tag in tags: self.addTag(kodiid, tag, mediatype) @@ -750,7 +748,7 @@ class Kodidb_Functions(): except TypeError: # Create the tag, because it does not exist tag_id = self.createTag(tag) - self.logMsg("Adding tag: %s" % tag, 2) + log("Adding tag: %s" % tag, 2) finally: # Assign tag to item @@ -779,7 +777,7 @@ class Kodidb_Functions(): except TypeError: # Create the tag tag_id = self.createTag(tag) - self.logMsg("Adding tag: %s" % tag, 2) + log("Adding tag: %s" % tag, 2) finally: # Assign tag to item @@ -815,7 +813,7 @@ class Kodidb_Functions(): 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) + log("Create tag_id: %s name: %s" % (tag_id, name), 2) else: # Kodi Helix query = ' '.join(( @@ -835,13 +833,13 @@ class Kodidb_Functions(): 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) + log("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) + log("Updating: %s with %s for %s: %s" % (oldtag, newtag, mediatype, kodiid), 2) if self.kodiversion in (15, 16, 17): # Kodi Isengard, Jarvis, Krypton @@ -858,7 +856,7 @@ class Kodidb_Functions(): 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) + log("Exception: %s" % e, 1) query = ' '.join(( "DELETE FROM tag_link", @@ -882,7 +880,7 @@ class Kodidb_Functions(): 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) + log("Exception: %s" % e, 1) query = ' '.join(( "DELETE FROM taglinks", @@ -943,7 +941,7 @@ class Kodidb_Functions(): def createBoxset(self, boxsetname): - self.logMsg("Adding boxset: %s" % boxsetname, 2) + log("Adding boxset: %s" % boxsetname, 2) query = ' '.join(( "SELECT idSet", diff --git a/resources/lib/kodimonitor.py b/resources/lib/kodimonitor.py index ea1a4f17..cc217de9 100644 --- a/resources/lib/kodimonitor.py +++ b/resources/lib/kodimonitor.py @@ -11,7 +11,7 @@ import clientinfo import downloadutils import embydb_functions as embydb import playbackutils as pbutils -import utils +from utils import Logging, window, settings, kodiSQL ################################################################################################# @@ -21,27 +21,25 @@ class KodiMonitor(xbmc.Monitor): def __init__(self): + global log + log = Logging(self.__class__.__name__).log + 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) + log("Kodi monitor started.", 1) def onScanStarted(self, library): - self.logMsg("Kodi library scan %s running." % library, 2) + log("Kodi library scan %s running." % library, 2) if library == "video": - utils.window('emby_kodiScan', value="true") + window('emby_kodiScan', value="true") def onScanFinished(self, library): - self.logMsg("Kodi library scan %s finished." % library, 2) + log("Kodi library scan %s finished." % library, 2) if library == "video": - utils.window('emby_kodiScan', clear=True) + window('emby_kodiScan', clear=True) def onSettingsChanged(self): # Monitor emby settings @@ -50,7 +48,7 @@ class KodiMonitor(xbmc.Monitor): '''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) + log("Changed to playback mode detected", 1) utils.window('emby_pluginpath', value=currentPath) resp = xbmcgui.Dialog().yesno( heading="Playback mode change detected", @@ -61,17 +59,17 @@ class KodiMonitor(xbmc.Monitor): if resp: utils.reset()''' - currentLog = utils.settings('logLevel') - if utils.window('emby_logLevel') != currentLog: + currentLog = settings('logLevel') + if 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) + log("New log level: %s" % currentLog, 1) + 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) + log("Method: %s Data: %s" % (method, data), 1) if data: data = json.loads(data,'utf-8') @@ -84,23 +82,23 @@ class KodiMonitor(xbmc.Monitor): kodiid = item['id'] item_type = item['type'] except (KeyError, TypeError): - self.logMsg("Item is invalid for playstate update.", 1) + log("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")): + if ((settings('useDirectPaths') == "1" and not item_type == "song") or + (item_type == "song" and settings('enableMusic') == "true")): # Set up properties for player - embyconn = utils.kodiSQL('emby') + embyconn = 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) + log("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) + log("Item: %s" % result, 2) playurl = None count = 0 @@ -114,12 +112,10 @@ class KodiMonitor(xbmc.Monitor): listItem = xbmcgui.ListItem() playback = pbutils.PlaybackUtils(result) - if item_type == "song" and utils.settings('streamMusic') == "true": - utils.window('emby_%s.playmethod' % playurl, - value="DirectStream") + if item_type == "song" and settings('streamMusic') == "true": + window('emby_%s.playmethod' % playurl, value="DirectStream") else: - utils.window('emby_%s.playmethod' % playurl, - value="DirectPlay") + window('emby_%s.playmethod' % playurl, value="DirectPlay") # Set properties for player.py playback.setProperties(playurl, listItem) finally: @@ -134,31 +130,31 @@ class KodiMonitor(xbmc.Monitor): kodiid = item['id'] item_type = item['type'] except (KeyError, TypeError): - self.logMsg("Item is invalid for playstate update.", 1) + log("Item is invalid for playstate update.", 1) else: # Send notification to the server. - embyconn = utils.kodiSQL('emby') + embyconn = 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) + log("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": + if window('emby_skipWatched%s' % itemid) == "true": # property is set in player.py - utils.window('emby_skipWatched%s' % itemid, clear=True) + 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) + log("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) + log("Mark as unwatched for itemid: %s" % itemid, 1) finally: embycursor.close() @@ -172,7 +168,7 @@ class KodiMonitor(xbmc.Monitor): kodiid = data['id'] type = data['type'] except (KeyError, TypeError): - self.logMsg("Item is invalid for emby deletion.", 1) + log("Item is invalid for emby deletion.", 1) else: # Send the delete action to the server. embyconn = utils.kodiSQL('emby') @@ -182,19 +178,19 @@ class KodiMonitor(xbmc.Monitor): try: itemid = emby_dbitem[0] except TypeError: - self.logMsg("Could not find itemid in emby database.", 1) + log("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) + log("User skipped deletion.", 1) embycursor.close() return url = "{server}/emby/Items/%s?format=json" % itemid - self.logMsg("Deleting request: %s" % itemid) + log("Deleting request: %s" % itemid) doUtils.downloadUrl(url, action_type="DELETE") finally: embycursor.close()''' @@ -203,13 +199,13 @@ class KodiMonitor(xbmc.Monitor): elif method == "System.OnWake": # Allow network to wake up xbmc.sleep(10000) - utils.window('emby_onWake', value="true") + window('emby_onWake', value="true") elif method == "GUI.OnScreensaverDeactivated": - if utils.settings('dbSyncScreensaver') == "true": + if settings('dbSyncScreensaver') == "true": xbmc.sleep(5000); - utils.window('emby_onWake', value="true") + window('emby_onWake', value="true") elif method == "Playlist.OnClear": diff --git a/resources/lib/librarysync.py b/resources/lib/librarysync.py index 1335f585..6d047148 100644 --- a/resources/lib/librarysync.py +++ b/resources/lib/librarysync.py @@ -20,6 +20,7 @@ import kodidb_functions as kodidb import read_embyserver as embyserver import userclient import videonodes +from utils import Logging, window, settings, language as lang ################################################################################################## @@ -42,6 +43,9 @@ class LibrarySync(threading.Thread): def __init__(self): + global log + log = Logging(self.__class__.__name__).log + self.__dict__ = self._shared_state self.monitor = xbmc.Monitor() @@ -54,26 +58,20 @@ class LibrarySync(threading.Thread): 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: + if settings('dbSyncIndicator') == "true" or forced: dialog = xbmcgui.DialogProgressBG() dialog.create("Emby for Kodi", title) - self.logMsg("Show progress dialog: %s" % title, 2) + log("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": @@ -88,7 +86,7 @@ class LibrarySync(threading.Thread): for plugin in result: if plugin['Name'] == "Emby.Kodi Sync Queue": - self.logMsg("Found server plugin.", 2) + log("Found server plugin.", 2) completed = self.fastSync() break @@ -103,37 +101,31 @@ class LibrarySync(threading.Thread): def fastSync(self): - lastSync = utils.settings('LastIncrementalSync') + lastSync = settings('LastIncrementalSync') if not lastSync: lastSync = "2010-01-01T00:00:00Z" - lastSyncTime = utils.convertdate(lastSync) - self.logMsg("Last sync run: %s" % lastSyncTime, 1) + lastSyncTime = utils.convertDate(lastSync) + log("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) + 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: - self.logMsg("Fast sync server retention insufficient, fall back to full sync", 1) + log("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) + url = "{server}/emby/Emby.Kodi.SyncQueue/{UserId}/GetItems?format=json" + result = self.doUtils(url, parameters=params) try: processlist = { @@ -145,11 +137,11 @@ class LibrarySync(threading.Thread): } except (KeyError, TypeError): - self.logMsg("Failed to retrieve latest updates using fast sync.", 1) + log("Failed to retrieve latest updates using fast sync.", 1) return False else: - self.logMsg("Fast sync changes: %s" % result, 1) + log("Fast sync changes: %s" % result, 1) for action in processlist: self.triage_items(action, processlist[action]) @@ -163,60 +155,55 @@ class LibrarySync(threading.Thread): 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) + server_time = utils.convertDate(server_time) except Exception as e: # If the server plugin is not installed or an error happened. - self.logMsg("An exception occurred: %s" % e, 1) + log("An exception occurred: %s" % e, 1) time_now = datetime.utcnow()-timedelta(minutes=overlap) lastSync = time_now.strftime('%Y-%m-%dT%H:%M:%SZ') - self.logMsg("New sync time: client time -%s min: %s" % (overlap, lastSync), 1) + log("New sync time: client time -%s min: %s" % (overlap, lastSync), 1) else: lastSync = (server_time - timedelta(minutes=overlap)).strftime('%Y-%m-%dT%H:%M:%SZ') - self.logMsg("New sync time: server time -%s min: %s" % (overlap, lastSync), 1) + log("New sync time: server time -%s min: %s" % (overlap, lastSync), 1) finally: - utils.settings('LastIncrementalSync', value=lastSync) + settings('LastIncrementalSync', value=lastSync) def shouldStop(self): # Checkpoint during the syncing process if self.monitor.abortRequested(): return True - elif utils.window('emby_shouldStop') == "true": + elif 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) + log("Kodi scan is running. Waiting...", 1) kodidb_scan = window('emby_kodiScan') == "true" if self.shouldStop(): - self.logMsg("Commit unsuccessful. Sync terminated.", 1) + log("Commit unsuccessful. Sync terminated.", 1) break if self.monitor.waitForAbort(1): # Abort was requested while waiting. We should exit - self.logMsg("Commit unsuccessful.", 1) + log("Commit unsuccessful.", 1) break else: connection.commit() - self.logMsg("Commit successful.", 1) + log("Commit successful.", 1) def fullSync(self, manualrun=False, repair=False, 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" + music_enabled = settings('enableMusic') == "true" xbmc.executebuiltin('InhibitIdleShutdown(true)') screensaver = utils.getScreensaver() @@ -284,7 +271,7 @@ class LibrarySync(threading.Thread): self.dbCommit(kodiconn) embyconn.commit() elapsedTime = datetime.now() - startTime - self.logMsg("SyncDatabase (finished %s in: %s)" + log("SyncDatabase (finished %s in: %s)" % (itemtype, str(elapsedTime).split('.')[0]), 1) else: # Close the Kodi cursor @@ -312,7 +299,7 @@ class LibrarySync(threading.Thread): musicconn.commit() embyconn.commit() elapsedTime = datetime.now() - startTime - self.logMsg("SyncDatabase (finished music in: %s)" + log("SyncDatabase (finished music in: %s)" % (str(elapsedTime).split('.')[0]), 1) musiccursor.close() @@ -335,7 +322,7 @@ class LibrarySync(threading.Thread): xbmcgui.Dialog().notification( heading="Emby for Kodi", message="%s %s %s" % - (message, utils.language(33025), str(elapsedtotal).split('.')[0]), + (message, lang(33025), str(elapsedtotal).split('.')[0]), icon="special://home/addons/plugin.video.emby/icon.png", sound=False) return True @@ -378,7 +365,7 @@ class LibrarySync(threading.Thread): if view['type'] == "mixed": sorted_views.append(view['name']) sorted_views.append(view['name']) - self.logMsg("Sorted views: %s" % sorted_views, 1) + log("Sorted views: %s" % sorted_views, 1) # total nodes for window properties self.vnodes.clearProperties() @@ -415,7 +402,8 @@ class LibrarySync(threading.Thread): '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) + url = "{server}/emby/Users/{UserId}/Items?format=json" + result = self.doUtils(url, parameters=params) try: verifyitem = result['Items'][0]['Id'] except (TypeError, IndexError): @@ -430,14 +418,14 @@ class LibrarySync(threading.Thread): # 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" + 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) - self.logMsg("Couldn't find corresponding grouped view: %s" % sorted_views, 1) + log("Couldn't find corresponding grouped view: %s" % sorted_views, 1) # Failsafe try: @@ -453,7 +441,7 @@ class LibrarySync(threading.Thread): current_tagid = view[2] except TypeError: - self.logMsg("Creating viewid: %s in Emby database." % folderid, 1) + 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 @@ -472,7 +460,7 @@ class LibrarySync(threading.Thread): emby_db.addView(folderid, foldername, viewtype, tagid) else: - self.logMsg(' '.join(( + log(' '.join(( "Found viewid: %s" % folderid, "viewname: %s" % current_viewname, @@ -488,7 +476,7 @@ class LibrarySync(threading.Thread): # View was modified, update with latest info if current_viewname != foldername: - self.logMsg("viewid: %s new viewname: %s" % (folderid, foldername), 1) + log("viewid: %s new viewname: %s" % (folderid, foldername), 1) tagid = kodi_db.createTag(foldername) # Update view with new info @@ -556,20 +544,19 @@ class LibrarySync(threading.Thread): utils.window('Emby.nodes.total', str(totalnodes)) # Remove any old referenced views - self.logMsg("Removing views: %s" % current_views, 1) + log("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) + log("Media folders: %s" % views, 1) ##### PROCESS MOVIES ##### for view in views: @@ -604,7 +591,7 @@ class LibrarySync(threading.Thread): count += 1 movies.add_update(embymovie, view['name'], view['id']) else: - self.logMsg("Movies finished.", 2) + log("Movies finished.", 2) ##### PROCESS BOXSETS ##### @@ -631,7 +618,7 @@ class LibrarySync(threading.Thread): count += 1 movies.add_updateBoxset(boxset) else: - self.logMsg("Boxsets finished.", 2) + log("Boxsets finished.", 2) return True @@ -642,7 +629,7 @@ class LibrarySync(threading.Thread): mvideos = itemtypes.MusicVideos(embycursor, kodicursor) views = emby_db.getView_byType('musicvideos') - self.logMsg("Media folders: %s" % views, 1) + log("Media folders: %s" % views, 1) for view in views: @@ -656,7 +643,7 @@ class LibrarySync(threading.Thread): if pdialog: pdialog.update( heading="Emby for Kodi", - message="%s %s..." % (utils.language(33019), viewName)) + message="%s %s..." % (lang(33019), viewName)) # Initial or repair sync all_embymvideos = self.emby.getMusicVideos(viewId, dialog=pdialog) @@ -679,7 +666,7 @@ class LibrarySync(threading.Thread): count += 1 mvideos.add_update(embymvideo, viewName, viewId) else: - self.logMsg("MusicVideos finished.", 2) + log("MusicVideos finished.", 2) return True @@ -691,7 +678,7 @@ class LibrarySync(threading.Thread): views = emby_db.getView_byType('tvshows') views += emby_db.getView_byType('mixed') - self.logMsg("Media folders: %s" % views, 1) + log("Media folders: %s" % views, 1) for view in views: @@ -702,7 +689,7 @@ class LibrarySync(threading.Thread): if pdialog: pdialog.update( heading="Emby for Kodi", - message="%s %s..." % (utils.language(33020), view['name'])) + message="%s %s..." % (lang(33020), view['name'])) all_embytvshows = self.emby.getShows(view['id'], dialog=pdialog) total = all_embytvshows['TotalRecordCount'] @@ -737,7 +724,7 @@ class LibrarySync(threading.Thread): pdialog.update(percentage, message="%s - %s" % (title, episodetitle)) tvshows.add_updateEpisode(episode) else: - self.logMsg("TVShows finished.", 2) + log("TVShows finished.", 2) return True @@ -757,7 +744,7 @@ class LibrarySync(threading.Thread): if pdialog: pdialog.update( heading="Emby for Kodi", - message="%s %s..." % (utils.language(33021), itemtype)) + message="%s %s..." % (lang(33021), itemtype)) all_embyitems = process[itemtype][0](dialog=pdialog) total = all_embyitems['TotalRecordCount'] @@ -778,7 +765,7 @@ class LibrarySync(threading.Thread): process[itemtype][1](embyitem) else: - self.logMsg("%s finished." % itemtype, 2) + log("%s finished." % itemtype, 2) return True @@ -799,7 +786,7 @@ class LibrarySync(threading.Thread): itemids.append(item['ItemId']) items = itemids - self.logMsg("Queue %s: %s" % (process, items), 1) + log("Queue %s: %s" % (process, items), 1) processlist[process].extend(items) def incrementalSync(self): @@ -833,7 +820,7 @@ class LibrarySync(threading.Thread): } for process_type in ['added', 'update', 'userdata', 'remove']: - if process[process_type] and utils.window('emby_kodiScan') != "true": + if process[process_type] and window('emby_kodiScan') != "true": listItems = list(process[process_type]) del process[process_type][:] # Reset class list @@ -871,7 +858,7 @@ class LibrarySync(threading.Thread): if update_embydb: update_embydb = False - self.logMsg("Updating emby database.", 1) + log("Updating emby database.", 1) embyconn.commit() self.saveLastSync() @@ -880,8 +867,8 @@ class LibrarySync(threading.Thread): self.forceLibraryUpdate = False self.dbCommit(kodiconn) - self.logMsg("Updating video library.", 1) - utils.window('emby_kodiScan', value="true") + log("Updating video library.", 1) + window('emby_kodiScan', value="true") xbmc.executebuiltin('UpdateLibrary(video)') if pDialog: @@ -893,7 +880,7 @@ class LibrarySync(threading.Thread): 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) + log("current: %s minimum: %s" % (current, minimum), 1) currMajor, currMinor, currPatch = current.split(".") minMajor, minMinor, minPatch = minimum.split(".") @@ -911,7 +898,7 @@ class LibrarySync(threading.Thread): try: self.run_internal() except Exception as e: - utils.window('emby_dbScan', clear=True) + window('emby_dbScan', clear=True) xbmcgui.Dialog().ok( heading="Emby for Kodi", line1=( @@ -922,14 +909,11 @@ class LibrarySync(threading.Thread): def run_internal(self): - lang = utils.language - window = utils.window - settings = utils.settings dialog = xbmcgui.Dialog() startupComplete = False - self.logMsg("---===### Starting LibrarySync ###===---", 0) + log("---===### Starting LibrarySync ###===---", 0) while not self.monitor.abortRequested(): @@ -947,12 +931,12 @@ class LibrarySync(threading.Thread): uptoDate = self.compareDBVersion(currentVersion, minVersion) if not uptoDate: - self.logMsg("Database version out of date: %s minimum version required: %s" + 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: - self.logMsg("Database version is out of date! USER IGNORED!", 0) + log("Database version is out of date! USER IGNORED!", 0) dialog.ok("Emby for Kodi", lang(33023)) else: utils.reset() @@ -967,7 +951,7 @@ class LibrarySync(threading.Thread): videoDb = utils.getKodiVideoDBPath() if not xbmcvfs.exists(videoDb): # Database does not exists - self.logMsg( + log( "The current Kodi version is incompatible " "with the Emby for Kodi add-on. Please visit " "https://github.com/MediaBrowser/Emby.Kodi/wiki " @@ -979,12 +963,12 @@ class LibrarySync(threading.Thread): break # Run start up sync - self.logMsg("Database version: %s" % settings('dbCreatedWithVersion'), 0) - self.logMsg("SyncDatabase (started)", 1) + log("Database version: %s" % settings('dbCreatedWithVersion'), 0) + log("SyncDatabase (started)", 1) startTime = datetime.now() librarySync = self.startSync() elapsedTime = datetime.now() - startTime - self.logMsg("SyncDatabase (finished in: %s) %s" + log("SyncDatabase (finished in: %s) %s" % (str(elapsedTime).split('.')[0], librarySync), 1) # Only try the initial sync once per kodi session regardless # This will prevent an infinite loop in case something goes wrong. @@ -999,32 +983,32 @@ class LibrarySync(threading.Thread): # Set in kodimonitor.py window('emby_onWake', clear=True) if window('emby_syncRunning') != "true": - self.logMsg("SyncDatabase onWake (started)", 0) + log("SyncDatabase onWake (started)", 0) librarySync = self.startSync() - self.logMsg("SyncDatabase onWake (finished) %s" % librarySync, 0) + log("SyncDatabase onWake (finished) %s" % librarySync, 0) if self.stop_thread: # Set in service.py - self.logMsg("Service terminated thread.", 2) + log("Service terminated thread.", 2) break if self.monitor.waitForAbort(1): # Abort was requested while waiting. We should exit break - self.logMsg("###===--- LibrarySync Stopped ---===###", 0) + log("###===--- LibrarySync Stopped ---===###", 0) def stopThread(self): self.stop_thread = True - self.logMsg("Ending thread...", 2) + log("Ending thread...", 2) def suspendThread(self): self.suspend_thread = True - self.logMsg("Pausing thread...", 0) + log("Pausing thread...", 0) def resumeThread(self): self.suspend_thread = False - self.logMsg("Resuming thread...", 0) + log("Resuming thread...", 0) class ManualSync(LibrarySync): @@ -1041,14 +1025,13 @@ class ManualSync(LibrarySync): 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) + log("Media folders: %s" % views, 1) # Pull the list of movies and boxsets in Kodi try: @@ -1095,7 +1078,7 @@ class ManualSync(LibrarySync): # Only update if movie is not in Kodi or checksum is different updatelist.append(itemid) - self.logMsg("Movies to update for %s: %s" % (viewName, updatelist), 1) + log("Movies to update for %s: %s" % (viewName, updatelist), 1) embymovies = self.emby.getFullItems(updatelist) total = len(updatelist) del updatelist[:] @@ -1137,7 +1120,7 @@ class ManualSync(LibrarySync): updatelist.append(itemid) embyboxsets.append(boxset) - self.logMsg("Boxsets to update: %s" % updatelist, 1) + log("Boxsets to update: %s" % updatelist, 1) total = len(updatelist) if pdialog: @@ -1161,13 +1144,13 @@ class ManualSync(LibrarySync): if kodimovie not in all_embymoviesIds: movies.remove(kodimovie) else: - self.logMsg("Movies compare finished.", 1) + log("Movies compare finished.", 1) for boxset in all_kodisets: if boxset not in all_embyboxsetsIds: movies.remove(boxset) else: - self.logMsg("Boxsets compare finished.", 1) + log("Boxsets compare finished.", 1) return True @@ -1178,7 +1161,7 @@ class ManualSync(LibrarySync): mvideos = itemtypes.MusicVideos(embycursor, kodicursor) views = emby_db.getView_byType('musicvideos') - self.logMsg("Media folders: %s" % views, 1) + log("Media folders: %s" % views, 1) # Pull the list of musicvideos in Kodi try: @@ -1201,7 +1184,7 @@ class ManualSync(LibrarySync): if pdialog: pdialog.update( heading="Emby for Kodi", - message="%s %s..." % (utils.language(33028), viewName)) + message="%s %s..." % (lang(33028), viewName)) all_embymvideos = self.emby.getMusicVideos(viewId, basic=True, dialog=pdialog) for embymvideo in all_embymvideos['Items']: @@ -1218,7 +1201,7 @@ class ManualSync(LibrarySync): # Only update if musicvideo is not in Kodi or checksum is different updatelist.append(itemid) - self.logMsg("MusicVideos to update for %s: %s" % (viewName, updatelist), 1) + log("MusicVideos to update for %s: %s" % (viewName, updatelist), 1) embymvideos = self.emby.getFullItems(updatelist) total = len(updatelist) del updatelist[:] @@ -1245,20 +1228,19 @@ class ManualSync(LibrarySync): if kodimvideo not in all_embymvideosIds: mvideos.remove(kodimvideo) else: - self.logMsg("MusicVideos compare finished.", 1) + log("MusicVideos compare finished.", 1) return True def tvshows(self, embycursor, kodicursor, pdialog): - 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) + log("Media folders: %s" % views, 1) # Pull the list of tvshows and episodes in Kodi try: @@ -1305,7 +1287,7 @@ class ManualSync(LibrarySync): # Only update if movie is not in Kodi or checksum is different updatelist.append(itemid) - self.logMsg("TVShows to update for %s: %s" % (viewName, updatelist), 1) + log("TVShows to update for %s: %s" % (viewName, updatelist), 1) embytvshows = self.emby.getFullItems(updatelist) total = len(updatelist) del updatelist[:] @@ -1349,7 +1331,7 @@ class ManualSync(LibrarySync): # Only update if movie is not in Kodi or checksum is different updatelist.append(itemid) - self.logMsg("Episodes to update for %s: %s" % (viewName, updatelist), 1) + log("Episodes to update for %s: %s" % (viewName, updatelist), 1) embyepisodes = self.emby.getFullItems(updatelist) total = len(updatelist) del updatelist[:] @@ -1374,13 +1356,13 @@ class ManualSync(LibrarySync): if koditvshow not in all_embytvshowsIds: tvshows.remove(koditvshow) else: - self.logMsg("TVShows compare finished.", 1) + log("TVShows compare finished.", 1) for kodiepisode in all_kodiepisodes: if kodiepisode not in all_embyepisodesIds: tvshows.remove(kodiepisode) else: - self.logMsg("Episodes compare finished.", 1) + log("Episodes compare finished.", 1) return True @@ -1421,7 +1403,7 @@ class ManualSync(LibrarySync): if pdialog: pdialog.update( heading="Emby for Kodi", - message="%s %s..." % (utils.language(33031), data_type)) + message="%s %s..." % (lang(33031), data_type)) if data_type != "artists": all_embyitems = process[data_type][0](basic=True, dialog=pdialog) else: @@ -1446,7 +1428,7 @@ class ManualSync(LibrarySync): 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) + log("%s to update: %s" % (data_type, updatelist), 1) embyitems = self.emby.getFullItems(updatelist) total = len(updatelist) del updatelist[:] @@ -1467,15 +1449,15 @@ class ManualSync(LibrarySync): if kodiartist not in all_embyartistsIds and all_kodiartists[kodiartist] is not None: music.remove(kodiartist) else: - self.logMsg("Artist compare finished.", 1) + log("Artist compare finished.", 1) for kodialbum in all_kodialbums: if kodialbum not in all_embyalbumsIds: music.remove(kodialbum) else: - self.logMsg("Albums compare finished.", 1) + log("Albums compare finished.", 1) for kodisong in all_kodisongs: if kodisong not in all_embysongsIds: music.remove(kodisong) else: - self.logMsg("Songs compare finished.", 1) - return True + log("Songs compare finished.", 1) + return True \ No newline at end of file diff --git a/resources/lib/musicutils.py b/resources/lib/musicutils.py index b058c5c5..dd44e830 100644 --- a/resources/lib/musicutils.py +++ b/resources/lib/musicutils.py @@ -14,20 +14,18 @@ from mutagen import id3 import base64 import read_embyserver as embyserver -import utils +from utils import Logging, window ################################################################################################# # 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) +log = Logging('MusicTools').log 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) + log("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 @@ -104,7 +102,7 @@ def getAdditionalSongTags(embyid, emby_rating, API, kodicursor, emby_db, enablei 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)) + log("getAdditionalSongTags --> embyid: %s - emby_rating: %s - file_rating: %s - current rating in kodidb: %s" %(embyid, emby_rating, file_rating, currentvalue)) updateFileRating = False updateEmbyRating = False @@ -171,7 +169,7 @@ def getAdditionalSongTags(embyid, emby_rating, API, kodicursor, emby_db, enablei if updateEmbyRating and enableexportsongrating: # sync details to emby server. Translation needed between ID3 rating and emby likes/favourites: like, favourite, deletelike = getEmbyRatingFromKodiRating(rating) - utils.window("ignore-update-%s" %embyid, "true") #set temp windows prop to ignore the update from webclient update + 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) @@ -183,7 +181,7 @@ def getSongTags(file): hasEmbeddedCover = False isTemp,filename = getRealFileName(file) - logMsg( "getting song ID3 tags for " + filename) + log( "getting song ID3 tags for " + filename) try: ###### FLAC FILES ############# @@ -217,14 +215,14 @@ def getSongTags(file): #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)) + log( "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) + log("Exception in getSongTags", str(e),0) rating = None #remove tempfile if needed.... @@ -246,7 +244,7 @@ def updateRatingToFile(rating, file): 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)) + log( "setting song rating: %s for filename: %s - using tempfile: %s" %(rating,file,tempfile)) if not tempfile: return @@ -263,7 +261,7 @@ def updateRatingToFile(rating, file): audio.add(id3.POPM(email="Windows Media Player 9 Series", rating=calcrating, count=1)) audio.save() else: - logMsg( "Not supported fileformat: %s" %(tempfile)) + log( "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 @@ -274,14 +272,14 @@ def updateRatingToFile(rating, file): 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)) + log( "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) + log("Exception in updateRatingToFile %s" %e,0) \ No newline at end of file diff --git a/resources/lib/player.py b/resources/lib/player.py index 7f323460..1cc187bb 100644 --- a/resources/lib/player.py +++ b/resources/lib/player.py @@ -7,11 +7,11 @@ import json import xbmc import xbmcgui -import utils import clientinfo import downloadutils import kodidb_functions as kodidb import websocket_client as wsc +from utils import Logging, window, settings, language as lang ################################################################################################# @@ -28,6 +28,9 @@ class Player(xbmc.Player): def __init__(self): + global log + log = Logging(self.__class__.__name__).log + self.__dict__ = self._shared_state self.clientInfo = clientinfo.ClientInfo() @@ -36,20 +39,13 @@ class Player(xbmc.Player): 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) + log("Starting playback monitor.", 2) def GetPlayStats(self): return self.playStats def onPlayBackStarted(self): - - window = utils.window # Will be called when xbmc starts playing a file self.stopAll() @@ -67,7 +63,7 @@ class Player(xbmc.Player): except: pass if count == 5: # try 5 times - self.logMsg("Cancelling playback report...", 1) + log("Cancelling playback report...", 1) break else: count += 1 @@ -84,12 +80,12 @@ class Player(xbmc.Player): 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) + log("Could not find itemId, cancelling playback report...", 1) break else: tryCount += 1 else: - self.logMsg("ONPLAYBACK_STARTED: %s itemid: %s" % (currentFile, itemId), 0) + log("ONPLAYBACK_STARTED: %s itemid: %s" % (currentFile, itemId), 0) # Only proceed if an itemId was found. embyitem = "emby_%s" % currentFile @@ -102,7 +98,7 @@ class Player(xbmc.Player): 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) + log("Seeking to: %s" % customseek, 1) self.xbmcplayer.seekTime(int(customseek)/10000000.0) window('emby_customPlaylist.seektime', clear=True) @@ -189,7 +185,7 @@ class Player(xbmc.Player): if mapping: # Set in playbackutils.py - self.logMsg("Mapping for external subtitles index: %s" % mapping, 2) + log("Mapping for external subtitles index: %s" % mapping, 2) externalIndex = json.loads(mapping) if externalIndex.get(str(indexSubs)): @@ -207,7 +203,7 @@ class Player(xbmc.Player): # Post playback to server - self.logMsg("Sending POST play started: %s." % postdata, 2) + log("Sending POST play started: %s." % postdata, 2) self.doUtils(url, postBody=postdata, action_type="POST") # Ensure we do have a runtime @@ -215,7 +211,7 @@ class Player(xbmc.Player): runtime = int(runtime) except ValueError: runtime = self.xbmcplayer.getTotalTime() - self.logMsg("Runtime is missing, Kodi runtime: %s" % runtime, 1) + log("Runtime is missing, Kodi runtime: %s" % runtime, 1) # Save data map for updates and position calls data = { @@ -232,7 +228,7 @@ class Player(xbmc.Player): } self.played_info[currentFile] = data - self.logMsg("ADDING_FILE: %s" % self.played_info, 1) + log("ADDING_FILE: %s" % self.played_info, 1) # log some playback stats '''if(itemType != None): @@ -251,7 +247,7 @@ class Player(xbmc.Player): def reportPlayback(self): - self.logMsg("reportPlayback Called", 2) + log("reportPlayback Called", 2) # Get current file currentFile = self.currentFile @@ -345,11 +341,11 @@ class Player(xbmc.Player): # Number of audiotracks to help get Emby Index audioTracks = len(xbmc.Player().getAvailableAudioStreams()) - mapping = utils.window("emby_%s.indexMapping" % currentFile) + mapping = window("emby_%s.indexMapping" % currentFile) if mapping: # Set in PlaybackUtils.py - self.logMsg("Mapping for external subtitles index: %s" % mapping, 2) + log("Mapping for external subtitles index: %s" % mapping, 2) externalIndex = json.loads(mapping) if externalIndex.get(str(indexSubs)): @@ -369,13 +365,13 @@ class Player(xbmc.Player): # Report progress via websocketclient postdata = json.dumps(postdata) - self.logMsg("Report: %s" % postdata, 2) + log("Report: %s" % postdata, 2) self.ws.sendProgressUpdate(postdata) def onPlayBackPaused(self): currentFile = self.currentFile - self.logMsg("PLAYBACK_PAUSED: %s" % currentFile, 2) + log("PLAYBACK_PAUSED: %s" % currentFile, 2) if self.played_info.get(currentFile): self.played_info[currentFile]['paused'] = True @@ -385,7 +381,7 @@ class Player(xbmc.Player): def onPlayBackResumed(self): currentFile = self.currentFile - self.logMsg("PLAYBACK_RESUMED: %s" % currentFile, 2) + log("PLAYBACK_RESUMED: %s" % currentFile, 2) if self.played_info.get(currentFile): self.played_info[currentFile]['paused'] = False @@ -395,7 +391,7 @@ class Player(xbmc.Player): def onPlayBackSeek(self, time, seekOffset): # Make position when seeking a bit more accurate currentFile = self.currentFile - self.logMsg("PLAYBACK_SEEK: %s" % currentFile, 2) + log("PLAYBACK_SEEK: %s" % currentFile, 2) if self.played_info.get(currentFile): position = self.xbmcplayer.getTime() @@ -404,39 +400,34 @@ class Player(xbmc.Player): self.reportPlayback() def onPlayBackStopped(self): - - window = utils.window # Will be called when user stops xbmc playing a file - self.logMsg("ONPLAYBACK_STOPPED", 2) + log("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) + 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) + log("ONPLAYBACK_ENDED", 2) + 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) + log("Played_information: %s" % self.played_info, 1) # Process each items for item in self.played_info: data = self.played_info.get(item) if data: - self.logMsg("Item path: %s" % item, 2) - self.logMsg("Item data: %s" % data, 2) + log("Item path: %s" % item, 2) + log("Item data: %s" % data, 2) runtime = data['runtime'] currentPosition = data['currentPosition'] @@ -447,7 +438,7 @@ class Player(xbmc.Player): playMethod = data['playmethod'] # Prevent manually mark as watched in Kodi monitor - utils.window('emby_skipWatched%s' % itemid, value="true") + window('emby_skipWatched%s' % itemid, value="true") if currentPosition and runtime: try: @@ -457,7 +448,7 @@ class Player(xbmc.Player): percentComplete = 0 markPlayedAt = float(settings('markPlayed')) / 100 - self.logMsg("Percent complete: %s Mark played at: %s" + log("Percent complete: %s Mark played at: %s" % (percentComplete, markPlayedAt), 1) # Send the delete action to the server. @@ -475,18 +466,18 @@ class Player(xbmc.Player): if percentComplete >= markPlayedAt and offerDelete: resp = xbmcgui.Dialog().yesno(lang(30091), lang(33015), autoclose=120000) if not resp: - self.logMsg("User skipped deletion.", 1) + log("User skipped deletion.", 1) continue url = "{server}/emby/Items/%s?format=json" % itemid - self.logMsg("Deleting request: %s" % itemid, 1) + log("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) + log("Transcoding for %s terminated." % itemid, 1) deviceId = self.clientInfo.getDeviceId() url = "{server}/emby/Videos/ActiveEncodings?DeviceId=%s" % deviceId self.doUtils(url, action_type="DELETE") @@ -495,7 +486,7 @@ class Player(xbmc.Player): def stopPlayback(self, data): - self.logMsg("stopPlayback called", 2) + log("stopPlayback called", 2) itemId = data['item_id'] currentPosition = data['currentPosition'] diff --git a/resources/lib/utils.py b/resources/lib/utils.py index dd2d04d4..203a7dee 100644 --- a/resources/lib/utils.py +++ b/resources/lib/utils.py @@ -188,7 +188,7 @@ def setScreensaver(value): result = xbmc.executeJSONRPC(json.dumps(query)) log("Toggling screensaver: %s %s" % (value, result), 1) -def convertdate(date): +def convertDate(date): try: date = datetime.strptime(date, "%Y-%m-%dT%H:%M:%SZ") except TypeError: From 55998c796f03ffe14b7e79c75568d9896df796fc Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Fri, 17 Jun 2016 22:05:18 -0500 Subject: [PATCH 008/103] Remove old log method --- resources/lib/utils.py | 23 +---------------------- 1 file changed, 1 insertion(+), 22 deletions(-) diff --git a/resources/lib/utils.py b/resources/lib/utils.py index 203a7dee..b203ba24 100644 --- a/resources/lib/utils.py +++ b/resources/lib/utils.py @@ -9,10 +9,10 @@ import pstats import sqlite3 import StringIO import os -from datetime import datetime, time import time import unicodedata import xml.etree.ElementTree as etree +from datetime import datetime, time import xbmc import xbmcaddon @@ -22,27 +22,6 @@ import xbmcvfs ################################################################################################# # Main methods -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'))) - class Logging(): LOGGINGCLASS = None From 14b133917967c0e68389a3988889ac76072b7db7 Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Sat, 18 Jun 2016 13:56:56 -0500 Subject: [PATCH 009/103] Fix typos --- resources/lib/playutils.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/resources/lib/playutils.py b/resources/lib/playutils.py index ed9b57b0..8e3c9a08 100644 --- a/resources/lib/playutils.py +++ b/resources/lib/playutils.py @@ -202,9 +202,9 @@ class PlayUtils(): def isDirectStream(self): videotrack = self.item['MediaSources'][0]['Name'] - transcodeH265 = utils.settings('transcodeH265') + transcodeH265 = settings('transcodeH265') videoprofiles = [x['Profile'] for x in self.item['MediaSources'][0]['MediaStreams'] if 'Profile' in x] - transcodeHi10P = utils.settings('transcodeHi10P') + transcodeHi10P = settings('transcodeHi10P') if transcodeHi10P == "true" and "H264" in videotrack and "High 10" in videoprofiles: return False @@ -316,7 +316,7 @@ class PlayUtils(): } # max bit rate supported by server (max signed 32bit integer) - return bitrate.get(utils.settings('videoBitrate'), 2147483) + return bitrate.get(settings('videoBitrate'), 2147483) def audioSubsPref(self, url, listitem): From 9c585b682c7fac451d17471efe387233109f980c Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Sat, 18 Jun 2016 18:39:13 -0500 Subject: [PATCH 010/103] Fix logging --- default.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/default.py b/default.py index 2a9ff290..9a686753 100644 --- a/default.py +++ b/default.py @@ -21,9 +21,11 @@ sys.path.append(base_resource) import entrypoint import utils +from utils import Logging, window ################################################################################################# +log = Logging('Default').log enableProfiling = False class Main: @@ -103,15 +105,15 @@ class Main: if mode == "settings": xbmc.executebuiltin('Addon.OpenSettings(plugin.video.emby)') elif mode in ("manualsync", "fastsync", "repair"): - if utils.window('emby_online') != "true": + if window('emby_online') != "true": # Server is not online, do not run the sync xbmcgui.Dialog().ok(heading="Emby for Kodi", line1=("Unable to run the sync, the add-on is not " "connected to the Emby server.")) - utils.logMsg("EMBY", "Not connected to the emby server.", 1) + log("EMBY", "Not connected to the emby server.", 1) return - if utils.window('emby_dbScan') != "true": + if window('emby_dbScan') != "true": import librarysync lib = librarysync.LibrarySync() if mode == "manualsync": @@ -121,7 +123,7 @@ class Main: else: lib.fullSync(repair=True) else: - utils.logMsg("EMBY", "Database scan is already running.", 1) + log("EMBY", "Database scan is already running.", 1) elif mode == "texturecache": import artwork @@ -131,7 +133,7 @@ class Main: if ( __name__ == "__main__" ): - xbmc.log('plugin.video.emby started') + log('plugin.video.emby started', 1) if enableProfiling: import cProfile @@ -152,4 +154,4 @@ if ( __name__ == "__main__" ): else: Main() - xbmc.log('plugin.video.emby stopped') \ No newline at end of file + log('plugin.video.emby stopped', 1) \ No newline at end of file From 6ebabf780563a17523fce2b5fd2668646b336137 Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Sat, 18 Jun 2016 18:52:35 -0500 Subject: [PATCH 011/103] Fix log typo --- default.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/default.py b/default.py index 9a686753..72e3ebfa 100644 --- a/default.py +++ b/default.py @@ -110,7 +110,7 @@ class Main: xbmcgui.Dialog().ok(heading="Emby for Kodi", line1=("Unable to run the sync, the add-on is not " "connected to the Emby server.")) - log("EMBY", "Not connected to the emby server.", 1) + log("Not connected to the emby server.", 1) return if window('emby_dbScan') != "true": @@ -123,7 +123,7 @@ class Main: else: lib.fullSync(repair=True) else: - log("EMBY", "Database scan is already running.", 1) + log("Database scan is already running.", 1) elif mode == "texturecache": import artwork From 354877d31c1726426a38dc2810f28cf27a1010ee Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Sat, 18 Jun 2016 19:48:19 -0500 Subject: [PATCH 012/103] Centralized Logging --- resources/lib/embydb_functions.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/resources/lib/embydb_functions.py b/resources/lib/embydb_functions.py index cfebd5ff..73e808d8 100644 --- a/resources/lib/embydb_functions.py +++ b/resources/lib/embydb_functions.py @@ -2,8 +2,10 @@ ################################################################################################# -import utils +from sqlite3 import OperationalError + import clientinfo +from utils import Logging ################################################################################################# @@ -13,15 +15,14 @@ class Embydb_Functions(): def __init__(self, embycursor): + global log + log = Logging(self.__class__.__name__).log + 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): From aa3a6fa17f7510196b606099568fc7f36ba783c1 Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Sun, 19 Jun 2016 13:30:54 -0500 Subject: [PATCH 013/103] Centralize logging --- contextmenu.py | 71 +++++++++++++++++++----------------- resources/lib/entrypoint.py | 72 +++++++++++++++++++------------------ 2 files changed, 75 insertions(+), 68 deletions(-) diff --git a/contextmenu.py b/contextmenu.py index c91da6a1..fa21c38a 100644 --- a/contextmenu.py +++ b/contextmenu.py @@ -10,13 +10,16 @@ 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) +################################################################################################# + +_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 @@ -25,9 +28,11 @@ import embydb_functions as embydb import kodidb_functions as kodidb import musicutils as musicutils import api +from utils import Logging, settings language as lang, kodiSQL -def logMsg(msg, lvl=1): - utils.logMsg("%s %s" % ("EMBY", "Contextmenu"), msg, lvl) +################################################################################################# + +log = Logging('ContextMenu').log #Kodi contextmenu item to configure the emby settings @@ -47,14 +52,14 @@ if __name__ == '__main__': if (not itemid or itemid == "-1") and xbmc.getInfoLabel("ListItem.Property(embyid)"): embyid = xbmc.getInfoLabel("ListItem.Property(embyid)") else: - embyconn = utils.kodiSQL('emby') + embyconn = 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)) + log("Contextmenu opened for embyid: %s - itemtype: %s" %(embyid,itemtype)) if embyid: item = emby.getItem(embyid) @@ -66,45 +71,45 @@ if __name__ == '__main__': options=[] if likes == True: #clear like for the item - options.append(utils.language(30402)) + options.append(lang(30402)) if likes == False or likes == None: #Like the item - options.append(utils.language(30403)) + options.append(lang(30403)) if likes == True or likes == None: #Dislike the item - options.append(utils.language(30404)) + options.append(lang(30404)) if favourite == False: #Add to emby favourites - options.append(utils.language(30405)) + options.append(lang(30405)) if favourite == True: #Remove from emby favourites - options.append(utils.language(30406)) + options.append(lang(30406)) if itemtype == "song": #Set custom song rating - options.append(utils.language(30407)) + options.append(lang(30407)) #delete item - options.append(utils.language(30409)) + options.append(lang(30409)) #addon settings - options.append(utils.language(30408)) + options.append(lang(30408)) #display select dialog and process results - header = utils.language(30401) + header = lang(30401) ret = xbmcgui.Dialog().select(header, options) if ret != -1: - if options[ret] == utils.language(30402): + if options[ret] == lang(30402): emby.updateUserRating(embyid, deletelike=True) - if options[ret] == utils.language(30403): + if options[ret] == lang(30403): emby.updateUserRating(embyid, like=True) - if options[ret] == utils.language(30404): + if options[ret] == lang(30404): emby.updateUserRating(embyid, like=False) - if options[ret] == utils.language(30405): + if options[ret] == lang(30405): emby.updateUserRating(embyid, favourite=True) - if options[ret] == utils.language(30406): + if options[ret] == lang(30406): emby.updateUserRating(embyid, favourite=False) - if options[ret] == utils.language(30407): - kodiconn = utils.kodiSQL('music') + if options[ret] == lang(30407): + kodiconn = kodiSQL('music') kodicursor = kodiconn.cursor() query = ' '.join(("SELECT rating", "FROM song", "WHERE idSong = ?" )) kodicursor.execute(query, (itemid,)) @@ -113,39 +118,39 @@ if __name__ == '__main__': if newvalue: newvalue = int(newvalue) if newvalue > 5: newvalue = "5" - if utils.settings('enableUpdateSongRating') == "true": + if settings('enableUpdateSongRating') == "true": musicutils.updateRatingToFile(newvalue, API.getFilePath()) - if utils.settings('enableExportSongRating') == "true": + if 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): + if options[ret] == lang(30408): #Open addon settings xbmc.executebuiltin("Addon.OpenSettings(plugin.video.emby)") - if options[ret] == utils.language(30409): + if options[ret] == lang(30409): #delete item from the server delete = True - if utils.settings('skipContextMenu') != "true": + if 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) + log("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) + log("Deleting request: %s" % embyid, 0) doUtils.downloadUrl(url, action_type="DELETE") - '''if utils.settings('skipContextMenu') != "true": + '''if settings('skipContextMenu') != "true": if xbmcgui.Dialog().yesno( heading="Confirm delete", line1=("Delete file on Emby Server? This will " diff --git a/resources/lib/entrypoint.py b/resources/lib/entrypoint.py index bc81ad0a..581af349 100644 --- a/resources/lib/entrypoint.py +++ b/resources/lib/entrypoint.py @@ -24,10 +24,12 @@ import playlist import playbackutils as pbutils import playutils import api - +from utils import Logging, window, settings ################################################################################################# +log = Logging('Entrypoint').log + def doPlayback(itemid, dbid): @@ -44,8 +46,8 @@ def resetAuth(): "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") + log("EMBY", "Reset login attempts.", 1) + window('emby_serverStatus', value="Auth") else: xbmc.executebuiltin('Addon.OpenSettings(plugin.video.emby)') @@ -59,15 +61,15 @@ def addDirectoryItem(label, path, folder=True): def doMainListing(): xbmcplugin.setContent(int(sys.argv[1]), 'files') # Get emby nodes from the window props - embyprops = utils.window('Emby.nodes.total') + embyprops = window('Emby.nodes.total') if embyprops: totalnodes = int(embyprops) for i in range(totalnodes): - path = utils.window('Emby.nodes.%s.index' % i) + path = 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) + path = window('Emby.nodes.%s.content' % i) + label = window('Emby.nodes.%s.title' % i) + node_type = 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": @@ -101,17 +103,17 @@ def resetDeviceId(): dialog = xbmcgui.Dialog() language = utils.language - deviceId_old = utils.window('emby_deviceId') + deviceId_old = window('emby_deviceId') try: - utils.window('emby_deviceId', clear=True) + 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) + log("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" + log("EMBY", "Successfully removed old deviceId: %s New deviceId: %s" % (deviceId_old, deviceId), 1) dialog.ok( heading="Emby for Kodi", @@ -139,7 +141,7 @@ def deleteItem(): elif xbmc.getCondVisibility('Container.Content(pictures)'): itemtype = "picture" else: - utils.logMsg("EMBY delete", "Unknown type, unable to proceed.", 1) + log("EMBY delete", "Unknown type, unable to proceed.", 1) return embyconn = utils.kodiSQL('emby') @@ -151,21 +153,21 @@ def deleteItem(): try: embyid = item[0] except TypeError: - utils.logMsg("EMBY delete", "Unknown embyId, unable to proceed.", 1) + log("EMBY delete", "Unknown embyId, unable to proceed.", 1) return - if utils.settings('skipContextMenu') != "true": + if 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) + log("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) + log("EMBY delete", "Deleting request: %s" % embyid, 0) doUtils.downloadUrl(url, action_type="DELETE") ##### ADD ADDITIONAL USERS ##### @@ -176,7 +178,7 @@ def addUser(): clientInfo = clientinfo.ClientInfo() deviceId = clientInfo.getDeviceId() deviceName = clientInfo.getDeviceName() - userid = utils.window('emby_currUser') + userid = window('emby_currUser') dialog = xbmcgui.Dialog() # Get session @@ -229,8 +231,8 @@ def addUser(): time=1000) # clear picture - position = utils.window('EmbyAdditionalUserPosition.%s' % selected_userId) - utils.window('EmbyAdditionalUserImage.%s' % position, clear=True) + position = window('EmbyAdditionalUserPosition.%s' % selected_userId) + window('EmbyAdditionalUserImage.%s' % position, clear=True) return else: return @@ -247,7 +249,7 @@ def addUser(): return # Subtract any additional users - utils.logMsg("EMBY", "Displaying list of users: %s" % users) + log("EMBY", "Displaying list of users: %s" % users) resp = dialog.select("Add user to the session", users) # post additional user if resp > -1: @@ -262,7 +264,7 @@ def addUser(): time=1000) except: - utils.logMsg("EMBY", "Failed to add user to session.") + log("EMBY", "Failed to add user to session.") dialog.notification( heading="Error", message="Unable to add/remove user from the session.", @@ -272,9 +274,9 @@ def addUser(): # always clear the individual items first totalNodes = 10 for i in range(totalNodes): - if not utils.window('EmbyAdditionalUserImage.%s' % i): + if not window('EmbyAdditionalUserImage.%s' % i): break - utils.window('EmbyAdditionalUserImage.%s' % i, clear=True) + window('EmbyAdditionalUserImage.%s' % i, clear=True) url = "{server}/emby/Sessions?DeviceId=%s" % deviceId result = doUtils.downloadUrl(url) @@ -284,9 +286,9 @@ def addUser(): userid = additionaluser['UserId'] url = "{server}/emby/Users/%s?format=json" % userid result = doUtils.downloadUrl(url) - utils.window('EmbyAdditionalUserImage.%s' % count, + window('EmbyAdditionalUserImage.%s' % count, value=art.getUserArtwork(result['Id'], 'Primary')) - utils.window('EmbyAdditionalUserPosition.%s' % userid, value=str(count)) + window('EmbyAdditionalUserPosition.%s' % userid, value=str(count)) count +=1 ##### THEME MUSIC/VIDEOS ##### @@ -318,7 +320,7 @@ def getThemeMedia(): 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) + log("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. @@ -468,7 +470,7 @@ def refreshPlaylist(): sound=False) except Exception as e: - utils.logMsg("EMBY", "Refresh playlists/nodes failed: %s" % e, 1) + log("EMBY", "Refresh playlists/nodes failed: %s" % e, 1) dialog.notification( heading="Emby for Kodi", message="Emby playlists/nodes refresh failed", @@ -480,9 +482,9 @@ def refreshPlaylist(): 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)) + title = window('Emby.nodes.%s%s.title' %(nodeindex,node)) if title: - path = utils.window('Emby.nodes.%s%s.content' %(nodeindex,node)) + path = window('Emby.nodes.%s%s.content' %(nodeindex,node)) addDirectoryItem(title, path) xbmcplugin.endOfDirectory(int(sys.argv[1])) @@ -510,7 +512,7 @@ def BrowseContent(viewname, browse_type="", folderid=""): 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'))) + log("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: @@ -795,7 +797,7 @@ def getNextUpEpisodes(tagname, limit): pass else: for item in items: - if utils.settings('ignoreSpecialsNextEpisodes') == "true": + if settings('ignoreSpecialsNextEpisodes') == "true": query = { 'jsonrpc': "2.0", @@ -1043,7 +1045,7 @@ def getExtraFanArt(embyId,embyPath): if embyId: #only proceed if we actually have a emby id - utils.logMsg("EMBY", "Requesting extrafanart for Id: %s" % embyId, 0) + log("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 @@ -1072,7 +1074,7 @@ def getExtraFanArt(embyId,embyPath): xbmcvfs.copy(backdrop, fanartFile) count += 1 else: - utils.logMsg("EMBY", "Found cached backdrop.", 2) + log("EMBY", "Found cached backdrop.", 2) # Use existing cached images dirs, files = xbmcvfs.listdir(fanartDir) for file in files: @@ -1083,7 +1085,7 @@ def getExtraFanArt(embyId,embyPath): url=fanartFile, listitem=li) except Exception as e: - utils.logMsg("EMBY", "Error getting extrafanart: %s" % e, 0) + log("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 From a8fd73740cf9ad210371baaffd7528b453be69c5 Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Sun, 19 Jun 2016 16:24:34 -0500 Subject: [PATCH 014/103] Strings --- default.py | 59 ++++++++++---------------- resources/language/English/strings.xml | 39 +++++++++++++---- resources/lib/initialsetup.py | 54 +++++++++-------------- resources/settings.xml | 2 +- service.py | 4 +- 5 files changed, 76 insertions(+), 82 deletions(-) diff --git a/default.py b/default.py index 72e3ebfa..110ae76d 100644 --- a/default.py +++ b/default.py @@ -12,32 +12,32 @@ 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) +_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 entrypoint import utils -from utils import Logging, window +from utils import Logging, window, language as lang +log = Logging('Default').log ################################################################################################# -log = Logging('Default').log -enableProfiling = False -class Main: +class Main(): # MAIN ENTRY POINT + #@utils.profiling() def __init__(self): # Parse parameters base_url = sys.argv[0] params = urlparse.parse_qs(sys.argv[2][1:]) - xbmc.log("Parameter string: %s" % sys.argv[2]) + log("Parameter string: %s" % sys.argv[2], 0) try: mode = params['mode'][0] itemid = params.get('id') @@ -72,11 +72,13 @@ class Main: embypath = sys.argv[2][1:] embyid = params.get('id',[""])[0] entrypoint.getExtraFanArt(embyid,embypath) + return if "/Extras" in sys.argv[0] or "/VideoFiles" in sys.argv[0]: embypath = sys.argv[2][1:] embyid = params.get('id',[""])[0] - entrypoint.getVideoFiles(embyid,embypath) + entrypoint.getVideoFiles(embyid, embypath) + return if modes.get(mode): # Simple functions @@ -88,11 +90,11 @@ class Main: limit = int(params['limit'][0]) modes[mode](itemid, limit) - elif mode in ["channels","getsubfolders"]: + elif mode in ("channels","getsubfolders"): modes[mode](itemid) elif mode == "browsecontent": - modes[mode]( itemid, params.get('type',[""])[0], params.get('folderid',[""])[0] ) + modes[mode](itemid, params.get('type',[""])[0], params.get('folderid',[""])[0]) elif mode == "channelsfolder": folderid = params['folderid'][0] @@ -104,12 +106,13 @@ class Main: # Other functions if mode == "settings": xbmc.executebuiltin('Addon.OpenSettings(plugin.video.emby)') + elif mode in ("manualsync", "fastsync", "repair"): + if window('emby_online') != "true": # Server is not online, do not run the sync - xbmcgui.Dialog().ok(heading="Emby for Kodi", - line1=("Unable to run the sync, the add-on is not " - "connected to the Emby server.")) + xbmcgui.Dialog().ok(heading=lang(29999), + line1=lang(33034)) log("Not connected to the emby server.", 1) return @@ -128,30 +131,12 @@ class Main: elif mode == "texturecache": import artwork artwork.Artwork().FullTextureCacheSync() + else: entrypoint.doMainListing() - -if ( __name__ == "__main__" ): + +if __name__ == "__main__": log('plugin.video.emby started', 1) - - if enableProfiling: - import cProfile - import pstats - import random - from time import gmtime, strftime - addonid = addon_.getAddonInfo('id').decode( 'utf-8' ) - datapath = os.path.join( xbmc.translatePath( "special://profile/" ).decode( 'utf-8' ), "addon_data", addonid ) - - filename = os.path.join( datapath, strftime( "%Y%m%d%H%M%S",gmtime() ) + "-" + str( random.randrange(0,100000) ) + ".log" ) - cProfile.run( 'Main()', filename ) - - stream = open( filename + ".txt", 'w') - p = pstats.Stats( filename, stream = stream ) - p.sort_stats( "cumulative" ) - p.print_stats() - - else: - Main() - + Main() log('plugin.video.emby stopped', 1) \ No newline at end of file diff --git a/resources/language/English/strings.xml b/resources/language/English/strings.xml index 1a145adc..b15b7fe8 100644 --- a/resources/language/English/strings.xml +++ b/resources/language/English/strings.xml @@ -1,25 +1,30 @@  + + Emby for Kodi Primary Server Address Play from HTTP instead of SMB Log level - Username: - Password: + Username + Port Number + + + + + + + Network Username: Network Password: Transcode: - Enable Performance Profiling - Local caching system Emby Network Device Name Advanced - Username - Port Number Number of recent Movies to show: Number of recent TV episodes to show: Number of recent Music Albums to show: @@ -141,7 +146,7 @@ Transcoding Server Detection Succeeded Found server - Address : + Address: Recently Added TV Shows @@ -175,7 +180,8 @@ Search Set Views - Select User + Select User + Profiling enabled. Please remember to turn off when finished testing. Error in ArtworkRotationThread @@ -262,6 +268,12 @@ Delete item from the server + Primary Server Address + Play from HTTP instead of SMB + Log level + Username + Port Number + Verify Host SSL Certificate Client SSL certificate Use alternate address @@ -299,7 +311,8 @@ Server messages Generate a new device Id Sync when screensaver is deactivated - Force Transcode Hi10P + Force Transcode Hi10P + Disabled Welcome @@ -337,4 +350,12 @@ Failed to generate a new device Id. See your logs for more information. A new device Id has been generated. Kodi will now restart. + Proceed with the following server? + Caution! If you choose Native mode, certain Emby features will be missing, such as: Emby cinema mode, direct stream/transcode options and parental access schedule. + Addon (Default) + Native (Direct Paths) + "Add network credentials to allow Kodi access to your content? Important: Kodi will need to be restarted to see the credentials. They can also be added at a later time. + Disable Emby music library? + Direct stream the music library? Select this option if the music library will be remotely accessed. + diff --git a/resources/lib/initialsetup.py b/resources/lib/initialsetup.py index 4b1f63f3..da0a5108 100644 --- a/resources/lib/initialsetup.py +++ b/resources/lib/initialsetup.py @@ -25,15 +25,15 @@ class InitialSetup(): global log log = Logging(self.__class__.__name__).log - self.clientInfo = clientinfo.ClientInfo() - self.addonId = self.clientInfo.getAddonId() - self.doUtils = downloadutils.DownloadUtils() + self.addonId = clientinfo.ClientInfo().getAddonId() + self.doUtils = downloadutils.DownloadUtils().downloadUrl self.userClient = userclient.UserClient() def setup(self): # Check server, user, direct paths, music, direct stream if not direct path. addonId = self.addonId + dialog = xbmcgui.Dialog() ##### SERVER INFO ##### @@ -54,10 +54,10 @@ class InitialSetup(): xbmc.executebuiltin('Addon.OpenSettings(%s)' % addonId) return else: - server_confirm = xbmcgui.Dialog().yesno( - heading="Emby for Kodi", - line1="Proceed with the following server?", - line2="%s %s" % (lang(30169), server)) + server_confirm = dialog.yesno( + heading=lang(29999), + line1=lang(33034), + line2="%s %s" % (lang(30169), server)) if server_confirm: # Correct server found log("Server is selected. Saving the information.", 1) @@ -75,9 +75,8 @@ class InitialSetup(): ##### USER INFO ##### log("Getting user list.", 1) - - url = "%s/emby/Users/Public?format=json" % server - result = self.doUtils.downloadUrl(url, authenticate=False) + + result = self.doUtils("%s/emby/Users/Public?format=json" % server, authenticate=False) if result == "": log("Unable to connect to %s" % server, 1) return @@ -97,7 +96,7 @@ class InitialSetup(): users_hasPassword.append(name) log("Presenting user list: %s" % users_hasPassword, 1) - user_select = xbmcgui.Dialog().select(lang(30200), users_hasPassword) + user_select = dialog.select(lang(30200), users_hasPassword) if user_select > -1: selected_user = usernames[user_select] log("Selected user: %s" % selected_user, 1) @@ -105,38 +104,30 @@ class InitialSetup(): else: log("No user selected.", 1) xbmc.executebuiltin('Addon.OpenSettings(%s)' % addonId) + return ##### ADDITIONAL PROMPTS ##### - dialog = xbmcgui.Dialog() directPaths = dialog.yesno( - heading="Playback Mode", - line1=( - "Caution! If you choose Native mode, you " - "will lose access to certain Emby features such as: " - "Emby cinema mode, direct stream/transcode options, " - "parental access schedule."), - nolabel="Addon (Default)", - yeslabel="Native (Direct Paths)") + heading=lang(30511), + line1=lang(33035), + nolabel=lang(33036), + yeslabel=lang(33037)) if directPaths: log("User opted to use direct paths.", 1) settings('useDirectPaths', value="1") # ask for credentials credentials = dialog.yesno( - heading="Network credentials", - line1= ( - "Add network credentials to allow Kodi access to your " - "content? Note: Skipping this step may generate a message " - "during the initial scan of your content if Kodi can't " - "locate your content.")) + heading=lang(30517), + line1= lang(33038)) if credentials: log("Presenting network credentials dialog.", 1) passwordsXML() musicDisabled = dialog.yesno( - heading="Music Library", - line1="Disable Emby music library?") + heading=lang(29999), + line1=lang(33039)) if musicDisabled: log("User opted to disable Emby music library.", 1) settings('enableMusic', value="false") @@ -144,11 +135,8 @@ class InitialSetup(): # Only prompt if the user didn't select direct paths for videos if not directPaths: musicAccess = dialog.yesno( - heading="Music Library", - line1=( - "Direct stream the music library? Select " - "this option only if you plan on listening " - "to music outside of your network.")) + heading=lang(29999), + line1=lang(33040)) if musicAccess: log("User opted to direct stream music.", 1) settings('streamMusic', value="true") diff --git a/resources/settings.xml b/resources/settings.xml index 77d57a33..7ca31f61 100644 --- a/resources/settings.xml +++ b/resources/settings.xml @@ -33,7 +33,7 @@ - + diff --git a/service.py b/service.py index f47be05c..311e00a9 100644 --- a/service.py +++ b/service.py @@ -167,7 +167,7 @@ class Service(): else: add = "" xbmcgui.Dialog().notification( - heading="Emby for Kodi", + heading=lang(29999), message=("%s %s%s!" % (lang(33000), user.currUser.decode('utf-8'), add.decode('utf-8'))), @@ -242,7 +242,7 @@ class Service(): break # Alert the user that server is online. xbmcgui.Dialog().notification( - heading="Emby for Kodi", + heading=lang(29999), message=lang(33003), icon="special://home/addons/plugin.video.emby/icon.png", time=2000, From ad6e0bb7daaa50ac1aed5c612b8d6d3137b65839 Mon Sep 17 00:00:00 2001 From: delphiactual Date: Sun, 19 Jun 2016 15:47:59 -0600 Subject: [PATCH 015/103] Fixed: Typo in strings.xml --- resources/language/English/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/language/English/strings.xml b/resources/language/English/strings.xml index b15b7fe8..1d3d1b92 100644 --- a/resources/language/English/strings.xml +++ b/resources/language/English/strings.xml @@ -354,7 +354,7 @@ Caution! If you choose Native mode, certain Emby features will be missing, such as: Emby cinema mode, direct stream/transcode options and parental access schedule. Addon (Default) Native (Direct Paths) - "Add network credentials to allow Kodi access to your content? Important: Kodi will need to be restarted to see the credentials. They can also be added at a later time. + Add network credentials to allow Kodi access to your content? Important: Kodi will need to be restarted to see the credentials. They can also be added at a later time. Disable Emby music library? Direct stream the music library? Select this option if the music library will be remotely accessed. From f90349d6a849b0d1e8f923dee37bd108158edc6e Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Sun, 19 Jun 2016 17:23:46 -0500 Subject: [PATCH 016/103] Fix typo --- resources/lib/itemtypes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/lib/itemtypes.py b/resources/lib/itemtypes.py index 0bf87c69..d433d65b 100644 --- a/resources/lib/itemtypes.py +++ b/resources/lib/itemtypes.py @@ -218,7 +218,7 @@ class Items(object): "You may need to verify your network credentials in the " "add-on settings or use the Emby path substitution " "to format your path correctly (Emby dashboard > library). " - "Stop syncing?" % playurl)) + "Stop syncing?" % path)) if resp: window('emby_shouldStop', value="true") return False From 3c60eddf55aab78ac46546701421e0817d594d7c Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Sun, 19 Jun 2016 19:24:42 -0500 Subject: [PATCH 017/103] Add refresh item to context menu --- contextmenu.py | 121 +++++++++++++------------ resources/language/English/strings.xml | 1 + resources/lib/read_embyserver.py | 16 +++- 3 files changed, 79 insertions(+), 59 deletions(-) diff --git a/contextmenu.py b/contextmenu.py index fa21c38a..a983ab59 100644 --- a/contextmenu.py +++ b/contextmenu.py @@ -19,95 +19,100 @@ sys.path.append(_base_resource) ################################################################################################# +import api import artwork -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 -from utils import Logging, settings language as lang, kodiSQL +from utils import Logging, settings, language as lang, kodiSQL +log = Logging('ContextMenu').log ################################################################################################# -log = Logging('ContextMenu').log - - -#Kodi contextmenu item to configure the emby settings -#for now used to set ratings but can later be used to sync individual items etc. +# Kodi contextmenu item to configure the emby settings if __name__ == '__main__': - itemid = xbmc.getInfoLabel("ListItem.DBID").decode("utf-8") - itemtype = xbmc.getInfoLabel("ListItem.DBTYPE").decode("utf-8") + + kodiId = xbmc.getInfoLabel('ListItem.DBID').decode('utf-8') + itemType = xbmc.getInfoLabel('ListItem.DBTYPE').decode('utf-8') + itemId = "" - emby = embyserver.Read_EmbyServer() + 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: + log("Itemtype is unknown.") + + if (not kodiId or kodiId == "-1") and xbmc.getInfoLabel("ListItem.Property(embyid)"): + itemId = xbmc.getInfoLabel("ListItem.Property(embyid)") - 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: + elif kodiId and itemType: embyconn = kodiSQL('emby') embycursor = embyconn.cursor() emby_db = embydb.Embydb_Functions(embycursor) - item = emby_db.getItem_byKodiId(itemid, itemtype) + item = emby_db.getItem_byKodiId(kodiId, itemType) embycursor.close() - if item: embyid = item[0] - - log("Contextmenu opened for embyid: %s - itemtype: %s" %(embyid,itemtype)) + try: + itemId = item[0] + except TypeError: + pass - if embyid: - item = emby.getItem(embyid) + + log("Found ItemId: %s/Itemtype: %s" % (itemId, itemType), 1) + if itemId: + + emby = embyserver.Read_EmbyServer() + item = emby.getItem(itemId) API = api.API(item) userdata = API.getUserData() likes = userdata['Likes'] favourite = userdata['Favorite'] - options=[] - if likes == True: - #clear like for the item - options.append(lang(30402)) - if likes == False or likes == None: - #Like the item - options.append(lang(30403)) - if likes == True or likes == None: - #Dislike the item - options.append(lang(30404)) - if favourite == False: - #Add to emby favourites - options.append(lang(30405)) - if favourite == True: - #Remove from emby favourites + options = [] + + if favourite: + # Remove from emby favourites options.append(lang(30406)) - if itemtype == "song": - #Set custom song rating + else: + # Add to emby favourites + options.append(lang(30405)) + + if itemType == "song": + # Set custom song rating options.append(lang(30407)) - #delete item + # Refresh item + options.append(lang(30410)) + # Delete item options.append(lang(30409)) - - #addon settings + # Addon settings options.append(lang(30408)) - #display select dialog and process results - header = lang(30401) - ret = xbmcgui.Dialog().select(header, options) + # Display select dialog and process results + + ret = xbmcgui.Dialog().select(lang(30401), options) if ret != -1: + if options[ret] == lang(30410): + emby.refreshItem(itemId) if options[ret] == lang(30402): - emby.updateUserRating(embyid, deletelike=True) + emby.updateUserRating(itemId, deletelike=True) if options[ret] == lang(30403): - emby.updateUserRating(embyid, like=True) + emby.updateUserRating(itemId, like=True) if options[ret] == lang(30404): - emby.updateUserRating(embyid, like=False) + emby.updateUserRating(itemId, like=False) if options[ret] == lang(30405): - emby.updateUserRating(embyid, favourite=True) + emby.updateUserRating(itemId, favourite=True) if options[ret] == lang(30406): - emby.updateUserRating(embyid, favourite=False) + emby.updateUserRating(itemId, favourite=False) if options[ret] == lang(30407): kodiconn = kodiSQL('music') kodicursor = kodiconn.cursor() @@ -122,7 +127,7 @@ if __name__ == '__main__': musicutils.updateRatingToFile(newvalue, API.getFilePath()) if settings('enableExportSongRating') == "true": like, favourite, deletelike = musicutils.getEmbyRatingFromKodiRating(newvalue) - emby.updateUserRating(embyid, like, favourite, deletelike) + emby.updateUserRating(itemId, like, favourite, deletelike) query = ' '.join(( "UPDATE song","SET rating = ?", "WHERE idSong = ?" )) kodicursor.execute(query, (newvalue,itemid,)) kodiconn.commit() @@ -140,13 +145,13 @@ if __name__ == '__main__': line1=("Delete file from Emby Server? This will " "also delete the file(s) from disk!")) if not resp: - log("User skipped deletion for: %s." % embyid, 1) + log("User skipped deletion for: %s." % itemId, 1) delete = False if delete: import downloadutils doUtils = downloadutils.DownloadUtils() - url = "{server}/emby/Items/%s?format=json" % embyid + url = "{server}/emby/Items/%s?format=json" % itemId log("Deleting request: %s" % embyid, 0) doUtils.downloadUrl(url, action_type="DELETE") @@ -160,4 +165,4 @@ if __name__ == '__main__': 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 + 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 1d3d1b92..1655957a 100644 --- a/resources/language/English/strings.xml +++ b/resources/language/English/strings.xml @@ -266,6 +266,7 @@ Set custom song rating Emby addon settings Delete item from the server + Refresh this item Primary Server Address diff --git a/resources/lib/read_embyserver.py b/resources/lib/read_embyserver.py index cc0c4b4a..bc51b5ca 100644 --- a/resources/lib/read_embyserver.py +++ b/resources/lib/read_embyserver.py @@ -564,4 +564,18 @@ class Read_EmbyServer(): log("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 + % (itemid, like, favourite, deletelike), 1) + + def refreshItem(self, itemid): + + url = "{server}/emby/Items/%s/Refresh?format=json" % itemid + params = { + + 'Recursive': True, + 'ImageRefreshMode': "FullRefresh", + 'MetadataRefreshMode': "FullRefresh", + 'ReplaceAllImages': False, + 'ReplaceAllMetadata': True + + } + self.doUtils(url, postBody=params, action_type="POST") \ No newline at end of file From 313899c8e7c8063770e8fc70d5bc91574ca32133 Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Sun, 19 Jun 2016 20:17:10 -0500 Subject: [PATCH 018/103] Fix typo --- resources/lib/itemtypes.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/lib/itemtypes.py b/resources/lib/itemtypes.py index d433d65b..197b71c5 100644 --- a/resources/lib/itemtypes.py +++ b/resources/lib/itemtypes.py @@ -18,7 +18,7 @@ import embydb_functions as embydb import kodidb_functions as kodidb import read_embyserver as embyserver import musicutils -from utils import Logging, window, settings, language as lang +from utils import Logging, window, settings, language as lang, kodiSQL ################################################################################################## @@ -100,7 +100,7 @@ class Items(object): if itemtype in ('MusicAlbum', 'MusicArtist', 'AlbumArtist', 'Audio'): if music_enabled: - musicconn = utils.kodiSQL('music') + musicconn = kodiSQL('music') musiccursor = musicconn.cursor() items_process = itemtypes[itemtype](embycursor, musiccursor) else: From 0efc37f646cedf79cad46e02e78a09043df6ef01 Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Sun, 19 Jun 2016 20:32:09 -0500 Subject: [PATCH 019/103] String & remove like/dislike To review: music rating since server does not have like/dislike anymore --- contextmenu.py | 102 ++++++++++++------------- resources/language/English/strings.xml | 5 +- resources/lib/musicutils.py | 2 +- resources/lib/read_embyserver.py | 25 +++--- 4 files changed, 63 insertions(+), 71 deletions(-) diff --git a/contextmenu.py b/contextmenu.py index a983ab59..5c16e441 100644 --- a/contextmenu.py +++ b/contextmenu.py @@ -50,7 +50,7 @@ if __name__ == '__main__': elif xbmc.getCondVisibility("Container.Content(pictures)"): itemType = "picture" else: - log("Itemtype is unknown.") + log("ItemType is unknown.") if (not kodiId or kodiId == "-1") and xbmc.getInfoLabel("ListItem.Property(embyid)"): itemId = xbmc.getInfoLabel("ListItem.Property(embyid)") @@ -67,9 +67,11 @@ if __name__ == '__main__': pass - log("Found ItemId: %s/Itemtype: %s" % (itemId, itemType), 1) + log("Found ItemId: %s ItemType: %s" % (itemId, itemType), 1) if itemId: + dialog = xbmcgui.Dialog() + emby = embyserver.Read_EmbyServer() item = emby.getItem(itemId) API = api.API(item) @@ -98,71 +100,69 @@ if __name__ == '__main__': options.append(lang(30408)) # Display select dialog and process results + resp = xbmcgui.Dialog().select(lang(30401), options) + if resp > -1: + selected = options[resp] - ret = xbmcgui.Dialog().select(lang(30401), options) - if ret != -1: - if options[ret] == lang(30410): + if selected == lang(30410): + # Refresh item emby.refreshItem(itemId) - if options[ret] == lang(30402): - emby.updateUserRating(itemId, deletelike=True) - if options[ret] == lang(30403): - emby.updateUserRating(itemId, like=True) - if options[ret] == lang(30404): - emby.updateUserRating(itemId, like=False) - if options[ret] == lang(30405): + elif selected == lang(30405): + # Add favourite emby.updateUserRating(itemId, favourite=True) - if options[ret] == lang(30406): + elif selected == lang(30406): + # Delete favourite emby.updateUserRating(itemId, favourite=False) - if options[ret] == lang(30407): + elif selected == lang(30407): + # Update song rating kodiconn = 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 settings('enableUpdateSongRating') == "true": - musicutils.updateRatingToFile(newvalue, API.getFilePath()) - if settings('enableExportSongRating') == "true": - like, favourite, deletelike = musicutils.getEmbyRatingFromKodiRating(newvalue) - emby.updateUserRating(itemId, like, favourite, deletelike) - query = ' '.join(( "UPDATE song","SET rating = ?", "WHERE idSong = ?" )) - kodicursor.execute(query, (newvalue,itemid,)) - kodiconn.commit() + query = "SELECT rating FROM song WHERE idSong = ?" + kodicursor.execute(query, (kodiId,)) + try: + value = kodicursor.fetchone()[0] + current_value = int(round(float(value),0)) + except TypeError: + pass + else: + new_value = dialog.numeric(0, lang(30411), str(current_value)) + if new_value > -1: + + new_value = int(new_value) + if new_value > 5: + new_value = 5 - if options[ret] == lang(30408): - #Open addon settings + if settings('enableUpdateSongRating') == "true": + musicutils.updateRatingToFile(new_value, API.getFilePath()) + + query = "UPDATE song SET rating = ? WHERE idSong = ?" + kodicursor.execute(query, (new_value, kodiId,)) + kodiconn.commit() + + '''if settings('enableExportSongRating') == "true": + like, favourite, deletelike = musicutils.getEmbyRatingFromKodiRating(new_value) + emby.updateUserRating(itemId, like, favourite, deletelike)''' + finally: + kodicursor.close() + + elif selected == lang(30408): + # Open addon settings xbmc.executebuiltin("Addon.OpenSettings(plugin.video.emby)") - if options[ret] == lang(30409): - #delete item from the server + elif selected == lang(30409): + # delete item from the server delete = True if 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!")) + resp = dialog.yesno( + heading=lang(29999), + line1=lang(33041)) if not resp: log("User skipped deletion for: %s." % itemId, 1) delete = False if delete: - import downloadutils - doUtils = downloadutils.DownloadUtils() - url = "{server}/emby/Items/%s?format=json" % itemId - log("Deleting request: %s" % embyid, 0) - doUtils.downloadUrl(url, action_type="DELETE") - - '''if 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")''' + log("Deleting request: %s" % itemId, 0) + emby.deleteItem(itemId) 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 1655957a..9c6d4ad2 100644 --- a/resources/language/English/strings.xml +++ b/resources/language/English/strings.xml @@ -258,15 +258,13 @@ Emby options - Clear like for this item - Like this item - Dislike this item Add to Emby favorites Remove from Emby favorites Set custom song rating Emby addon settings Delete item from the server Refresh this item + Set custom song rating (0-5) Primary Server Address @@ -358,5 +356,6 @@ Add network credentials to allow Kodi access to your content? Important: Kodi will need to be restarted to see the credentials. They can also be added at a later time. Disable Emby music library? Direct stream the music library? Select this option if the music library will be remotely accessed. + Delete file(s) from Emby Server? This will also delete the file(s) from disk! diff --git a/resources/lib/musicutils.py b/resources/lib/musicutils.py index dd44e830..66da24a1 100644 --- a/resources/lib/musicutils.py +++ b/resources/lib/musicutils.py @@ -15,11 +15,11 @@ import base64 import read_embyserver as embyserver from utils import Logging, window +log = Logging('MusicTools').log ################################################################################################# # Helper for the music library, intended to fix missing song ID3 tags on Emby -log = Logging('MusicTools').log def getRealFileName(filename, isTemp=False): #get the filename path accessible by python if possible... diff --git a/resources/lib/read_embyserver.py b/resources/lib/read_embyserver.py index bc51b5ca..5e62425e 100644 --- a/resources/lib/read_embyserver.py +++ b/resources/lib/read_embyserver.py @@ -539,32 +539,20 @@ class Read_EmbyServer(): return sorted_items - def updateUserRating(self, itemid, like=None, favourite=None, deletelike=False): + def updateUserRating(self, itemid, favourite=None): # Updates the user rating to Emby doUtils = self.doUtils if favourite: url = "{server}/emby/Users/{UserId}/FavoriteItems/%s?format=json" % itemid doUtils(url, action_type="POST") - elif favourite == False: + elif not favourite: url = "{server}/emby/Users/{UserId}/FavoriteItems/%s?format=json" % itemid doUtils(url, action_type="DELETE") - - if not deletelike and like: - url = "{server}/emby/Users/{UserId}/Items/%s/Rating?Likes=true&format=json" % itemid - doUtils(url, action_type="POST") - elif not deletelike and like is False: - url = "{server}/emby/Users/{UserId}/Items/%s/Rating?Likes=false&format=json" % itemid - doUtils(url, action_type="POST") - elif deletelike: - url = "{server}/emby/Users/{UserId}/Items/%s/Rating?format=json" % itemid - doUtils(url, action_type="DELETE") else: log("Error processing user rating.", 1) - log("Update user rating to emby for itemid: %s " - "| like: %s | favourite: %s | deletelike: %s" - % (itemid, like, favourite, deletelike), 1) + log("Update user rating to emby for itemid: %s | favourite: %s" % (itemid, favourite), 1) def refreshItem(self, itemid): @@ -578,4 +566,9 @@ class Read_EmbyServer(): 'ReplaceAllMetadata': True } - self.doUtils(url, postBody=params, action_type="POST") \ No newline at end of file + self.doUtils(url, postBody=params, action_type="POST") + + def deleteItem(self, itemid): + + url = "{server}/emby/Items/%s?format=json" % itemId + self.doUtils(url, action_type="DELETE") \ No newline at end of file From 423a4c9c40f435aec1c029928818ede64eac582b Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Sun, 19 Jun 2016 20:39:22 -0500 Subject: [PATCH 020/103] Update refresh So it works for music modifications and such --- contextmenu.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contextmenu.py b/contextmenu.py index 5c16e441..b690fd8f 100644 --- a/contextmenu.py +++ b/contextmenu.py @@ -165,4 +165,4 @@ if __name__ == '__main__': emby.deleteItem(itemId) xbmc.sleep(500) - xbmc.executebuiltin('Container.Update') \ No newline at end of file + xbmc.executebuiltin('Container.Refresh') \ No newline at end of file From f47ead1ddcc1ba746753ea9ebe594f9547283d05 Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Sun, 19 Jun 2016 23:09:07 -0500 Subject: [PATCH 021/103] Fix logging --- resources/lib/entrypoint.py | 40 ++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/resources/lib/entrypoint.py b/resources/lib/entrypoint.py index 581af349..942a0d78 100644 --- a/resources/lib/entrypoint.py +++ b/resources/lib/entrypoint.py @@ -24,12 +24,11 @@ import playlist import playbackutils as pbutils import playutils import api -from utils import Logging, window, settings +from utils import Logging, window, settings, language as lang +log = Logging('Entrypoint').log ################################################################################################# -log = Logging('Entrypoint').log - def doPlayback(itemid, dbid): @@ -46,7 +45,7 @@ def resetAuth(): "Emby might lock your account if you fail to log in too many times. " "Proceed anyway?")) if resp == 1: - log("EMBY", "Reset login attempts.", 1) + log("Reset login attempts.", 1) window('emby_serverStatus', value="Auth") else: xbmc.executebuiltin('Addon.OpenSettings(plugin.video.emby)') @@ -108,16 +107,15 @@ def resetDeviceId(): window('emby_deviceId', clear=True) deviceId = clientinfo.ClientInfo().getDeviceId(reset=True) except Exception as e: - log("EMBY", "Failed to generate a new device Id: %s" % e, 1) + log("Failed to generate a new device Id: %s" % e, 1) dialog.ok( heading="Emby for Kodi", line1=language(33032)) else: - log("EMBY", "Successfully removed old deviceId: %s New deviceId: %s" - % (deviceId_old, deviceId), 1) + log("Successfully removed old deviceId: %s New deviceId: %s" % (deviceId_old, deviceId), 1) dialog.ok( - heading="Emby for Kodi", - line1=language(33033)) + heading=lang(29999), + line1=lang(33033)) xbmc.executebuiltin('RestartApp') ##### Delete Item @@ -141,7 +139,7 @@ def deleteItem(): elif xbmc.getCondVisibility('Container.Content(pictures)'): itemtype = "picture" else: - log("EMBY delete", "Unknown type, unable to proceed.", 1) + log("Unknown type, unable to proceed.", 1) return embyconn = utils.kodiSQL('emby') @@ -153,7 +151,7 @@ def deleteItem(): try: embyid = item[0] except TypeError: - log("EMBY delete", "Unknown embyId, unable to proceed.", 1) + log("Unknown embyId, unable to proceed.", 1) return if settings('skipContextMenu') != "true": @@ -162,12 +160,12 @@ def deleteItem(): line1=("Delete file from Emby Server? This will " "also delete the file(s) from disk!")) if not resp: - log("EMBY delete", "User skipped deletion for: %s." % embyid, 1) + log("User skipped deletion for: %s." % embyid, 1) return doUtils = downloadutils.DownloadUtils() url = "{server}/emby/Items/%s?format=json" % embyid - log("EMBY delete", "Deleting request: %s" % embyid, 0) + log("Deleting request: %s" % embyid, 0) doUtils.downloadUrl(url, action_type="DELETE") ##### ADD ADDITIONAL USERS ##### @@ -249,7 +247,7 @@ def addUser(): return # Subtract any additional users - log("EMBY", "Displaying list of users: %s" % users) + log("Displaying list of users: %s" % users) resp = dialog.select("Add user to the session", users) # post additional user if resp > -1: @@ -264,7 +262,7 @@ def addUser(): time=1000) except: - log("EMBY", "Failed to add user to session.") + log("Failed to add user to session.") dialog.notification( heading="Error", message="Unable to add/remove user from the session.", @@ -320,7 +318,7 @@ def getThemeMedia(): tvtunes = xbmcaddon.Addon(id="script.tvtunes") tvtunes.setSetting('custom_path_enable', "true") tvtunes.setSetting('custom_path', library) - log("EMBY", "TV Tunes custom path is enabled and set.", 1) + log("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. @@ -470,7 +468,7 @@ def refreshPlaylist(): sound=False) except Exception as e: - log("EMBY", "Refresh playlists/nodes failed: %s" % e, 1) + log("Refresh playlists/nodes failed: %s" % e, 1) dialog.notification( heading="Emby for Kodi", message="Emby playlists/nodes refresh failed", @@ -512,7 +510,7 @@ def BrowseContent(viewname, browse_type="", folderid=""): break if viewname is not None: - log("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'))) + log("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: @@ -1045,7 +1043,7 @@ def getExtraFanArt(embyId,embyPath): if embyId: #only proceed if we actually have a emby id - log("EMBY", "Requesting extrafanart for Id: %s" % embyId, 0) + log("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 @@ -1074,7 +1072,7 @@ def getExtraFanArt(embyId,embyPath): xbmcvfs.copy(backdrop, fanartFile) count += 1 else: - log("EMBY", "Found cached backdrop.", 2) + log("Found cached backdrop.", 2) # Use existing cached images dirs, files = xbmcvfs.listdir(fanartDir) for file in files: @@ -1085,7 +1083,7 @@ def getExtraFanArt(embyId,embyPath): url=fanartFile, listitem=li) except Exception as e: - log("EMBY", "Error getting extrafanart: %s" % e, 0) + log("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 From 49157bbbeac7c3b77de4395dde0580398cca42f0 Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Mon, 20 Jun 2016 13:58:17 -0500 Subject: [PATCH 022/103] Fix logging typo --- resources/lib/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/lib/utils.py b/resources/lib/utils.py index b203ba24..77220304 100644 --- a/resources/lib/utils.py +++ b/resources/lib/utils.py @@ -267,7 +267,7 @@ def reset(): window('emby_shouldStop', value="true") count = 10 while window('emby_dbScan') == "true": - logMsg("EMBY", "Sync is running, will retry: %s..." % count) + log("Sync is running, will retry: %s..." % count) count -= 1 if count == 0: dialog.ok("Warning", "Could not stop the database from running. Try again.") From 48fbce22114755e54a5d6f85573287e999f638c1 Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Mon, 20 Jun 2016 13:59:55 -0500 Subject: [PATCH 023/103] Fix typo --- resources/lib/artwork.py | 76 +++++++++++++++++++--------------------- 1 file changed, 36 insertions(+), 40 deletions(-) diff --git a/resources/lib/artwork.py b/resources/lib/artwork.py index 4ed0b8d9..fab75b6b 100644 --- a/resources/lib/artwork.py +++ b/resources/lib/artwork.py @@ -14,7 +14,7 @@ import xbmcvfs import clientinfo import image_cache_thread -from utils import Logging, window, settings, kodiSQL +from utils import Logging, window, settings, language as lang, kodiSQL ################################################################################################# @@ -165,25 +165,23 @@ class Artwork(): except TypeError: pass - def FullTextureCacheSync(self): + def fullTextureCacheSync(self): # This method will sync all Kodi artwork to textures13.db # and cache them locally. This takes diskspace! dialog = xbmcgui.Dialog() if not dialog.yesno( - heading="Image Texture Cache", - line1=( - "Running the image cache process can take some time. " - "Are you sure you want continue?")): + heading=lang(29999), + line1=lang(33042)): return log("Doing Image Cache Sync", 1) pdialog = xbmcgui.DialogProgress() - pdialog.create("Emby for Kodi", "Image Cache Sync") + pdialog.create(lang(29999), lang(33043)) # ask to rest all existing or not - if dialog.yesno("Image Texture Cache", "Reset all existing cache data first?"): + if dialog.yesno(lang(29999), lang(33044)): log("Resetting all cache data first.", 1) # Remove all existing textures first @@ -228,11 +226,10 @@ class Artwork(): percentage = int((float(count) / float(total))*100) message = "%s of %s (%s)" % (count, total, self.imageCacheThreads) - pdialog.update(percentage, "Updating Image Cache: %s" % message) + pdialog.update(percentage, "%s %s" % (lang(33045), message)) self.cacheTexture(url[0]) count += 1 - # Cache all entries in music DB connection = kodiSQL('music') cursor = connection.cursor() @@ -250,19 +247,18 @@ class Artwork(): percentage = int((float(count) / float(total))*100) message = "%s of %s" % (count, total) - pdialog.update(percentage, "Updating Image Cache: %s" % message) + pdialog.update(percentage, "%s %s" % (lang(33045), message)) self.cacheTexture(url[0]) count += 1 - - pdialog.update(100, "Waiting for all threads to exit: %s" % len(self.imageCacheThreads)) + pdialog.update(100, "%s %s" % (lang(33046), len(self.imageCacheThreads))) log("Waiting for all threads to exit", 1) while len(self.imageCacheThreads): for thread in self.imageCacheThreads: if thread.isFinished: self.imageCacheThreads.remove(thread) - pdialog.update(100, "Waiting for all threads to exit: %s" % len(self.imageCacheThreads)) + pdialog.update(100, "%s %s" % (lang(33046), len(self.imageCacheThreads))) log("Waiting for all threads to exit: %s" % len(self.imageCacheThreads), 1) xbmc.sleep(500) @@ -311,7 +307,7 @@ class Artwork(): self.addWorkerImageCacheThread(url) - def addArtwork(self, artwork, kodi_id, media_type, cursor): + def addArtwork(self, artwork, kodiId, mediaType, cursor): # Kodi conversion table kodiart = { @@ -342,7 +338,7 @@ class Artwork(): "AND media_type = ?", "AND type LIKE ?" )) - cursor.execute(query, (kodi_id, media_type, "fanart%",)) + cursor.execute(query, (kodiId, mediaType, "fanart%",)) rows = cursor.fetchall() if len(rows) > backdropsNumber: @@ -354,15 +350,15 @@ class Artwork(): "AND media_type = ?", "AND type LIKE ?" )) - cursor.execute(query, (kodi_id, media_type, "fanart_",)) + cursor.execute(query, (kodiId, mediaType, "fanart_",)) # Process backdrops and extra fanart index = "" for backdrop in backdrops: self.addOrUpdateArt( image_url=backdrop, - kodi_id=kodi_id, - media_type=media_type, + kodi_id=kodiId, + media_type=mediaType, image_type="%s%s" % ("fanart", index), cursor=cursor) @@ -377,8 +373,8 @@ class Artwork(): for artType in kodiart[art]: self.addOrUpdateArt( image_url=artwork[art], - kodi_id=kodi_id, - media_type=media_type, + kodi_id=kodiId, + media_type=mediaType, image_type=artType, cursor=cursor) @@ -386,14 +382,14 @@ class Artwork(): # Process the rest artwork type that Kodi can use self.addOrUpdateArt( image_url=artwork[art], - kodi_id=kodi_id, - media_type=media_type, + kodi_id=kodiId, + media_type=mediaType, image_type=kodiart[art], cursor=cursor) - def addOrUpdateArt(self, image_url, kodi_id, media_type, image_type, cursor): + def addOrUpdateArt(self, imageUrl, kodiId, mediaType, imageType, cursor): # Possible that the imageurl is an empty string - if image_url: + if imageUrl: cacheimage = False query = ' '.join(( @@ -404,13 +400,13 @@ class Artwork(): "AND media_type = ?", "AND type = ?" )) - cursor.execute(query, (kodi_id, media_type, image_type,)) + cursor.execute(query, (kodiId, mediaType, imageType,)) try: # Update the artwork url = cursor.fetchone()[0] except TypeError: # Add the artwork cacheimage = True - log("Adding Art Link for kodiId: %s (%s)" % (kodi_id, image_url), 2) + log("Adding Art Link for kodiId: %s (%s)" % (kodiId, imageUrl), 2) query = ( ''' @@ -419,20 +415,20 @@ class Artwork(): VALUES (?, ?, ?, ?) ''' ) - cursor.execute(query, (kodi_id, media_type, image_type, image_url)) + cursor.execute(query, (kodiId, mediaType, imageType, imageUrl)) else: # Only cache artwork if it changed - if url != image_url: + if url != imageUrl: cacheimage = True # Only for the main backdrop, poster if (window('emby_initialScan') != "true" and - image_type in ("fanart", "poster")): + imageType in ("fanart", "poster")): # Delete current entry before updating with the new one self.deleteCachedArtwork(url) log("Updating Art url for %s kodiId: %s (%s) -> (%s)" - % (image_type, kodi_id, url, image_url), 1) + % (imageType, kodiId, url, imageUrl), 1) query = ' '.join(( @@ -442,13 +438,13 @@ class Artwork(): "AND media_type = ?", "AND type = ?" )) - cursor.execute(query, (image_url, kodi_id, media_type, image_type)) + cursor.execute(query, (imageUrl, kodiId, mediaType, imageType)) # Cache fanart and poster in Kodi texture cache - if cacheimage and image_type in ("fanart", "poster"): - self.cacheTexture(image_url) + if cacheimage and imageType in ("fanart", "poster"): + self.cacheTexture(imageUrl) - def deleteArtwork(self, kodi_id, media_type, cursor): + def deleteArtwork(self, kodiId, mediaType, cursor): query = ' '.join(( @@ -457,13 +453,13 @@ class Artwork(): "WHERE media_id = ?", "AND media_type = ?" )) - cursor.execute(query, (kodi_id, 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"): + imageType = row[1] + if imageType in ("poster", "fanart"): self.deleteCachedArtwork(url) def deleteCachedArtwork(self, url): @@ -513,10 +509,10 @@ class Artwork(): return people - def getUserArtwork(self, item_id, item_type): + def getUserArtwork(self, itemId, itemType): # Load user information set by UserClient image = ("%s/emby/Users/%s/Images/%s?Format=original" - % (self.server, item_id, item_type)) + % (self.server, itemId, itemType)) return image def getAllArtwork(self, item, parentInfo=False): From 924c3a4a0549b0ed0069365b69f0935d0a6f4dec Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Mon, 20 Jun 2016 14:04:10 -0500 Subject: [PATCH 024/103] Fix arguments --- resources/lib/artwork.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/resources/lib/artwork.py b/resources/lib/artwork.py index fab75b6b..f2cd0a32 100644 --- a/resources/lib/artwork.py +++ b/resources/lib/artwork.py @@ -356,10 +356,10 @@ class Artwork(): index = "" for backdrop in backdrops: self.addOrUpdateArt( - image_url=backdrop, - kodi_id=kodiId, - media_type=mediaType, - image_type="%s%s" % ("fanart", index), + imageUrl=backdrop, + kodiId=kodiId, + mediaType=mediaType, + imageType="%s%s" % ("fanart", index), cursor=cursor) if backdropsNumber > 1: @@ -372,19 +372,19 @@ class Artwork(): # Primary art is processed as thumb and poster for Kodi. for artType in kodiart[art]: self.addOrUpdateArt( - image_url=artwork[art], - kodi_id=kodiId, - media_type=mediaType, - image_type=artType, + 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( - image_url=artwork[art], - kodi_id=kodiId, - media_type=mediaType, - image_type=kodiart[art], + imageUrl=artwork[art], + kodiId=kodiId, + mediaType=mediaType, + imageType=kodiart[art], cursor=cursor) def addOrUpdateArt(self, imageUrl, kodiId, mediaType, imageType, cursor): From 3e1aa94c786f6b21bb50469ce6a19f929d45c94a Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Mon, 20 Jun 2016 20:57:29 -0500 Subject: [PATCH 025/103] Strings - translation --- default.py | 2 +- resources/language/English/strings.xml | 180 ++++++++++--------------- resources/lib/entrypoint.py | 146 ++++++++++---------- resources/lib/itemtypes.py | 35 ++--- resources/lib/kodimonitor.py | 2 +- resources/lib/librarysync.py | 36 ++--- resources/lib/playutils.py | 4 +- resources/lib/userclient.py | 4 +- resources/lib/utils.py | 38 +++--- resources/lib/websocket_client.py | 6 +- 10 files changed, 201 insertions(+), 252 deletions(-) diff --git a/default.py b/default.py index 110ae76d..3912b4f5 100644 --- a/default.py +++ b/default.py @@ -130,7 +130,7 @@ class Main(): elif mode == "texturecache": import artwork - artwork.Artwork().FullTextureCacheSync() + artwork.Artwork().fullTextureCacheSync() else: entrypoint.doMainListing() diff --git a/resources/language/English/strings.xml b/resources/language/English/strings.xml index 9c6d4ad2..ee2dc385 100644 --- a/resources/language/English/strings.xml +++ b/resources/language/English/strings.xml @@ -6,56 +6,23 @@ Primary Server Address Play from HTTP instead of SMB Log level + Device Name + Advanced Username Port Number + Number of recent Music Albums to show: + Number of recent Movies to show: + Number of recent TV episodes to show: - - - - - - Network Username: - Network Password: - Transcode: - - Emby - Network - Device Name - - Advanced - - Number of recent Movies to show: - Number of recent TV episodes to show: - Number of recent Music Albums to show: - Mark watched at start of playback: - Set Season poster for episodes - - Genre Filter ... - Play All from Here Refresh Delete - Add Movie to CouchPotato - Incorrect Username/Password Username not found - Deleting Waiting for server to delete - Server Default - Title - Year - Premiere Date - Date Created - Critic Rating - Community Rating - Play Count - Budget - - Sort By - None Action Adventure @@ -80,69 +47,40 @@ Genre Filter Confirm file deletion - Delete this item? This action will delete media and associated data files. - Mark Watched - Mark Unwatched - Add to Favorites - Remove from Favorites - Sort By ... + Mark watched + Mark unwatched + + Sort by Sort Order Descending Sort Order Ascending - Show People Resume Resume from Start from beginning - Interface - Include Stream Info - Include People - Include Overview Offer delete after playback For Episodes For Movies - Background Art Refresh Rate (seconds) + Add Resume Percent Add Episode Number Show Load Progress Loading Content Retrieving Data + Done - Processing Item : - Play Error - This item is not playable - Local path detected - Your MB3 Server contains local paths. Please change server paths to UNC or change XBMB3C setting 'Play from Stream' to true. Path: - Warning - Debug logging enabled. - This will affect performance. + Warning Error - Monitoring service is not running - If you have just installed please restart Kodi Search - Enable Theme Music (Requires Restart) - - Loop Theme Music - Enable Background Image (Requires Restart) - Services - - Skin does not support setting views - Select item action (Requires Restart) - - Sort NextUp by Show Title Enable Enhanced Images (eg CoverArt) Metadata Artwork Video Quality - Enable Suggested Loader (Requires Restart) - Add Season Number - Flatten Seasons - - Direct Play - HTTP - Direct Play + Direct Play Transcoding Server Detection Succeeded Found server @@ -176,39 +114,24 @@ TV Genres TV Networks TV Actors - Playlists - Search - Set Views - - Select User + Playlists - Profiling enabled. - Please remember to turn off when finished testing. - Error in ArtworkRotationThread + Set Views + Select User Unable to connect to server - Error in LoadMenuOptionsThread - - Enable Playlists Loader (Requires Restart) Songs Albums Album Artists Artists Music Genres - - Enable Theme Videos (Requires Restart) - - Loop Theme Videos - - AutoPlay remaining episodes in a season - Compress Artwork - Latest - In Progress - NextUp + + Latest + In Progress + NextUp User Views Report Metrics - Use Kodi Sorting - Runtime - + Random Movies Random Episodes Random Items @@ -220,15 +143,9 @@ Sync Movie BoxSets Reset local Kodi database - Enable watched/resume status sync - DB Sync Indication: - Play Count Sync Indication: Enable HTTPS Force Transcoding Codecs - Enable Netflix style next up notification - - The number of seconds before the end to show the notification - Show Emby Info dialog on play/select action Enable server connection message on startup Recently added Home Videos @@ -267,12 +184,6 @@ Set custom song rating (0-5) - Primary Server Address - Play from HTTP instead of SMB - Log level - Username - Port Number - Verify Host SSL Certificate Client SSL certificate Use alternate address @@ -357,5 +268,52 @@ Disable Emby music library? Direct stream the music library? Select this option if the music library will be remotely accessed. Delete file(s) from Emby Server? This will also delete the file(s) from disk! + Running the caching process may take some time. Continue anyway? + Artwork cache sync + Reset existing artwork cache? + Updating artwork cache: + Waiting for all threads to exit: + Kodi can't locate file: + You may need to verify your network credentials in the add-on settings or use the Emby path substitution to format your path correctly (Emby dashboard > library). Stop syncing? + Added: + If you fail to log in too many times, the Emby server might lock your account. Proceed anyway? + Live TV Channels (experimental) + Live TV Recordings (experimental) + Settings + Add user to session + Refresh Emby playlists/Video nodes + Perform manual sync + Repair local database (force update all content) + Perform local database reset + Cache all artwork + Sync Emby Theme Media to Kodi + Add/Remove user from the session + Add user + Remove user + Remove user from the session + Success! + Removed from viewing session: + Added to viewing session: + Unable to add/remove user from the session. + The task succeeded + The task failed + Direct Stream + Playback method for your themes + The settings file does not exist in TV Tunes. Change a setting and run the task again. + Are you sure you want to reset your local Kodi database? + Modify/Remove network credentials + Modify + Remove + Removed: + Enter the network username + Enter the network password + Added network credentials for: + 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" + Modify the server name or IP address + Enter the server name or IP address + Could not reset the database. Try again. + Remove all cached artwork? + Reset all Emby add-on settings? + Database reset has completed, Kodi will now restart to apply the changes. - + \ No newline at end of file diff --git a/resources/lib/entrypoint.py b/resources/lib/entrypoint.py index 942a0d78..ea63add0 100644 --- a/resources/lib/entrypoint.py +++ b/resources/lib/entrypoint.py @@ -30,21 +30,19 @@ log = Logging('Entrypoint').log ################################################################################################# -def doPlayback(itemid, dbid): +def doPlayback(itemId, dbId): emby = embyserver.Read_EmbyServer() - item = emby.getItem(itemid) - pbutils.PlaybackUtils(item).play(itemid, dbid) + 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: + heading=lang(30132), + line1=lang(33050)) + if resp: log("Reset login attempts.", 1) window('emby_serverStatus', value="Auth") else: @@ -58,6 +56,7 @@ def addDirectoryItem(label, path, folder=True): 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 = window('Emby.nodes.total') @@ -68,39 +67,54 @@ def doMainListing(): if not path: path = window('Emby.nodes.%s.content' % i) label = window('Emby.nodes.%s.title' % i) - node_type = 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) + node = 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 ''' - #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") + '''if path and xbmc.getCondVisibility("Window.IsActive(Pictures)") and node == "photos": + addDirectoryItem(label, path) + elif path and xbmc.getCondVisibility("Window.IsActive(VideoLibrary)") + and node != "photos": + addDirectoryItem(label, path) + elif path and not xbmc.getCondVisibility("Window.IsActive(VideoLibrary) | + Window.IsActive(Pictures) | Window.IsActive(MusicLibrary)"): + addDirectoryItem(label, path)''' + + if path: + if xbmc.getCondVisibility("Window.IsActive(Pictures)") and node == "photos": + addDirectoryItem(label, path) + elif xbmc.getCondVisibility("Window.IsActive(VideoLibrary)") and node != "photos": + addDirectoryItem(label, path) + else: + addDirectoryItem(label, path) + + # experimental live tv nodes + if not xbmc.getCondVisibility("Window.IsActive(Pictures)"): + addDirectoryItem(lang(33051), + "plugin://plugin.video.emby/?mode=browsecontent&type=tvchannels&folderid=root") + addDirectoryItem(lang(33052), + "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") + addDirectoryItem(lang(30517), "plugin://plugin.video.emby/?mode=passwords") + addDirectoryItem(lang(33053), "plugin://plugin.video.emby/?mode=settings") + addDirectoryItem(lang(33054), "plugin://plugin.video.emby/?mode=adduser") + addDirectoryItem(lang(33055), "plugin://plugin.video.emby/?mode=refreshplaylist") + addDirectoryItem(lang(33056), "plugin://plugin.video.emby/?mode=manualsync") + addDirectoryItem(lang(33057), "plugin://plugin.video.emby/?mode=repair") + addDirectoryItem(lang(33058), "plugin://plugin.video.emby/?mode=reset") + addDirectoryItem(lang(33059), "plugin://plugin.video.emby/?mode=texturecache") + addDirectoryItem(lang(33060), "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 = window('emby_deviceId') try: @@ -109,8 +123,8 @@ def resetDeviceId(): except Exception as e: log("Failed to generate a new device Id: %s" % e, 1) dialog.ok( - heading="Emby for Kodi", - line1=language(33032)) + heading=lang(29999), + line1=lang(33032)) else: log("Successfully removed old deviceId: %s New deviceId: %s" % (deviceId_old, deviceId), 1) dialog.ok( @@ -123,21 +137,21 @@ 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)') + itemId = xbmc.getInfoLabel('ListItem.Property(embyid)') else: - dbid = xbmc.getInfoLabel('ListItem.DBID') - itemtype = xbmc.getInfoLabel('ListItem.DBTYPE') + dbId = xbmc.getInfoLabel('ListItem.DBID') + itemType = xbmc.getInfoLabel('ListItem.DBTYPE') - if not itemtype: + if not itemType: if xbmc.getCondVisibility('Container.Content(albums)'): - itemtype = "album" + itemType = "album" elif xbmc.getCondVisibility('Container.Content(artists)'): - itemtype = "artist" + itemType = "artist" elif xbmc.getCondVisibility('Container.Content(songs)'): - itemtype = "song" + itemType = "song" elif xbmc.getCondVisibility('Container.Content(pictures)'): - itemtype = "picture" + itemType = "picture" else: log("Unknown type, unable to proceed.", 1) return @@ -145,7 +159,7 @@ def deleteItem(): embyconn = utils.kodiSQL('emby') embycursor = embyconn.cursor() emby_db = embydb.Embydb_Functions(embycursor) - item = emby_db.getItem_byKodiId(dbid, itemtype) + item = emby_db.getItem_byKodiId(dbId, itemType) embycursor.close() try: @@ -156,17 +170,13 @@ def deleteItem(): if 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!")) + heading=lang(29999), + line1=lang(33041)) if not resp: - log("User skipped deletion for: %s." % embyid, 1) + log("User skipped deletion for: %s." % itemId, 1) return - doUtils = downloadutils.DownloadUtils() - url = "{server}/emby/Items/%s?format=json" % embyid - log("Deleting request: %s" % embyid, 0) - doUtils.downloadUrl(url, action_type="DELETE") + embyserver.Read_EmbyServer().deleteItem(itemId) ##### ADD ADDITIONAL USERS ##### def addUser(): @@ -203,7 +213,7 @@ def addUser(): # Display dialog if there's additional users if additionalUsers: - option = dialog.select("Add/Remove user from the session", ["Add user", "Remove user"]) + option = dialog.select(lang(33061), [lang(33062), lang(33063)]) # Users currently in the session additionalUserlist = {} additionalUsername = [] @@ -216,15 +226,15 @@ def addUser(): if option == 1: # User selected Remove user - resp = dialog.select("Remove user from the session", additionalUsername) + resp = dialog.select(lang(33064), 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, + heading=lang(29999), + message="%s %s" % (lang(33066), selected), icon="special://home/addons/plugin.video.emby/icon.png", time=1000) @@ -256,16 +266,16 @@ def addUser(): 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, + heading=lang(29999), + message="%s %s" % (lang(33067), selected), icon="special://home/addons/plugin.video.emby/icon.png", time=1000) except: log("Failed to add user to session.") dialog.notification( - heading="Error", - message="Unable to add/remove user from the session.", + heading=lang(29999), + message=lang(33068), icon=xbmcgui.NOTIFICATION_ERROR) # Add additional user images @@ -297,7 +307,7 @@ def getThemeMedia(): playback = None # Choose playback method - resp = dialog.select("Playback method for your themes", ["Direct Play", "Direct Stream"]) + resp = dialog.select(lang(33072), [lang(30165), lang(33071)]) if resp == 0: playback = "DirectPlay" elif resp == 1: @@ -322,11 +332,7 @@ def getThemeMedia(): 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.")) + dialog.ok(heading=lang(29999), line1=lang(33073))0 xbmc.executebuiltin('Addon.OpenSettings(script.tvtunes)') return @@ -442,8 +448,8 @@ def getThemeMedia(): nfo_file.close() dialog.notification( - heading="Emby for Kodi", - message="Themes added!", + heading=lang(29999), + message=lang(33069), icon="special://home/addons/plugin.video.emby/icon.png", time=1000, sound=False) @@ -461,8 +467,8 @@ def refreshPlaylist(): # Refresh views lib.refreshViews() dialog.notification( - heading="Emby for Kodi", - message="Emby playlists/nodes refreshed", + heading=lang(29999), + message=lang(33069), icon="special://home/addons/plugin.video.emby/icon.png", time=1000, sound=False) @@ -470,8 +476,8 @@ def refreshPlaylist(): except Exception as e: log("Refresh playlists/nodes failed: %s" % e, 1) dialog.notification( - heading="Emby for Kodi", - message="Emby playlists/nodes refresh failed", + heading=lang(29999), + message=lang(33070), icon=xbmcgui.NOTIFICATION_ERROR, time=1000, sound=False) diff --git a/resources/lib/itemtypes.py b/resources/lib/itemtypes.py index 197b71c5..934efcee 100644 --- a/resources/lib/itemtypes.py +++ b/resources/lib/itemtypes.py @@ -212,13 +212,8 @@ class Items(object): # Verify if direct path is accessible or not if window('emby_pathverified') != "true" and not xbmcvfs.exists(path): resp = xbmcgui.Dialog().yesno( - heading="Can't validate path", - line1=( - "Kodi can't locate file: %s. " - "You may need to verify your network credentials in the " - "add-on settings or use the Emby path substitution " - "to format your path correctly (Emby dashboard > library). " - "Stop syncing?" % path)) + heading=lang(29999), + line1="%s %s. %s" % (lang(33047), path, lang(33048))) if resp: window('emby_shouldStop', value="true") return False @@ -230,8 +225,8 @@ class Items(object): 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, + heading=lang(29999), + message="%s %s" % (lang(33049), name), icon="special://home/addons/plugin.video.emby/icon.png", time=time, sound=False) @@ -571,9 +566,7 @@ class Movies(Items): try: movieid = emby_dbitem[0] fileid = emby_dbitem[1] - log( - "Update playstate for movie: %s fileid: %s" - % (item['Name'], fileid), 1) + log("Update playstate for movie: %s fileid: %s" % (item['Name'], fileid), 1) except TypeError: return @@ -2373,19 +2366,19 @@ class Music(Items): log("Deleted %s: %s from kodi database" % (mediatype, itemid), 1) - def removeSong(self, kodiid): + def removeSong(self, kodiId): kodicursor = self.kodicursor - self.artwork.deleteArtwork(kodiid, "song", self.kodicursor) - self.kodicursor.execute("DELETE FROM song WHERE idSong = ?", (kodiid,)) + self.artwork.deleteArtwork(kodiId, "song", self.kodicursor) + self.kodicursor.execute("DELETE FROM song WHERE idSong = ?", (kodiId,)) - def removeAlbum(self, kodiid): + def removeAlbum(self, kodiId): - self.artwork.deleteArtwork(kodiid, "album", self.kodicursor) - self.kodicursor.execute("DELETE FROM album WHERE idAlbum = ?", (kodiid,)) + self.artwork.deleteArtwork(kodiId, "album", self.kodicursor) + self.kodicursor.execute("DELETE FROM album WHERE idAlbum = ?", (kodiId,)) - def removeArtist(self, kodiid): + def removeArtist(self, kodiId): - self.artwork.deleteArtwork(kodiid, "artist", self.kodicursor) - self.kodicursor.execute("DELETE FROM artist WHERE idArtist = ?", (kodiid,)) + self.artwork.deleteArtwork(kodiId, "artist", self.kodicursor) + self.kodicursor.execute("DELETE FROM artist WHERE idArtist = ?", (kodiId,)) \ No newline at end of file diff --git a/resources/lib/kodimonitor.py b/resources/lib/kodimonitor.py index cc217de9..16baa487 100644 --- a/resources/lib/kodimonitor.py +++ b/resources/lib/kodimonitor.py @@ -94,7 +94,7 @@ class KodiMonitor(xbmc.Monitor): try: itemid = emby_dbitem[0] except TypeError: - log("No kodiid returned.", 1) + log("No kodiId returned.", 1) else: url = "{server}/emby/Users/{UserId}/Items/%s?format=json" % itemid result = doUtils.downloadUrl(url) diff --git a/resources/lib/librarysync.py b/resources/lib/librarysync.py index 6d047148..e31a83cb 100644 --- a/resources/lib/librarysync.py +++ b/resources/lib/librarysync.py @@ -320,7 +320,7 @@ class LibrarySync(threading.Thread): window('emby_initialScan', clear=True) if forceddialog: xbmcgui.Dialog().notification( - heading="Emby for Kodi", + heading=lang(29999), message="%s %s %s" % (message, lang(33025), str(elapsedtotal).split('.')[0]), icon="special://home/addons/plugin.video.emby/icon.png", @@ -541,7 +541,7 @@ class LibrarySync(threading.Thread): self.vnodes.singleNode(totalnodes, "channels", "movies", "channels") totalnodes += 1 # Save total - utils.window('Emby.nodes.total', str(totalnodes)) + window('Emby.nodes.total', str(totalnodes)) # Remove any old referenced views log("Removing views: %s" % current_views, 1) @@ -567,7 +567,7 @@ class LibrarySync(threading.Thread): # Get items per view if pdialog: pdialog.update( - heading="Emby for Kodi", + heading=lang(29999), message="%s %s..." % (lang(33017), view['name'])) # Initial or repair sync @@ -596,7 +596,7 @@ class LibrarySync(threading.Thread): ##### PROCESS BOXSETS ##### if pdialog: - pdialog.update(heading="Emby for Kodi", message=lang(33018)) + pdialog.update(heading=lang(29999), message=lang(33018)) boxsets = self.emby.getBoxset(dialog=pdialog) total = boxsets['TotalRecordCount'] @@ -642,7 +642,7 @@ class LibrarySync(threading.Thread): if pdialog: pdialog.update( - heading="Emby for Kodi", + heading=lang(29999), message="%s %s..." % (lang(33019), viewName)) # Initial or repair sync @@ -688,7 +688,7 @@ class LibrarySync(threading.Thread): # Get items per view if pdialog: pdialog.update( - heading="Emby for Kodi", + heading=lang(29999), message="%s %s..." % (lang(33020), view['name'])) all_embytvshows = self.emby.getShows(view['id'], dialog=pdialog) @@ -743,7 +743,7 @@ class LibrarySync(threading.Thread): if pdialog: pdialog.update( - heading="Emby for Kodi", + heading=lang(29999), message="%s %s..." % (lang(33021), itemtype)) all_embyitems = process[itemtype][0](dialog=pdialog) @@ -900,13 +900,13 @@ class LibrarySync(threading.Thread): except Exception as e: window('emby_dbScan', clear=True) xbmcgui.Dialog().ok( - heading="Emby for Kodi", + heading=lang(29999), line1=( "Library sync thread has exited! " "You should restart Kodi now. " "Please report this on the forum.")) raise - + @utils.profiling() def run_internal(self): dialog = xbmcgui.Dialog() @@ -934,10 +934,10 @@ class LibrarySync(threading.Thread): log("Database version out of date: %s minimum version required: %s" % (currentVersion, minVersion), 0) - resp = dialog.yesno("Emby for Kodi", lang(33022)) + resp = dialog.yesno(lang(29999), lang(33022)) if not resp: log("Database version is out of date! USER IGNORED!", 0) - dialog.ok("Emby for Kodi", lang(33023)) + dialog.ok(lang(29999), lang(33023)) else: utils.reset() @@ -958,7 +958,7 @@ class LibrarySync(threading.Thread): "to know which Kodi versions are supported.", 0) dialog.ok( - heading="Emby for Kodi", + heading=lang(29999), line1=lang(33024)) break @@ -1060,7 +1060,7 @@ class ManualSync(LibrarySync): if pdialog: pdialog.update( - heading="Emby for Kodi", + heading=lang(29999), message="%s %s..." % (lang(33026), viewName)) all_embymovies = self.emby.getMovies(viewId, basic=True, dialog=pdialog) @@ -1104,7 +1104,7 @@ class ManualSync(LibrarySync): embyboxsets = [] if pdialog: - pdialog.update(heading="Emby for Kodi", message=lang(33027)) + pdialog.update(heading=lang(29999), message=lang(33027)) for boxset in boxsets['Items']: @@ -1183,7 +1183,7 @@ class ManualSync(LibrarySync): if pdialog: pdialog.update( - heading="Emby for Kodi", + heading=lang(29999), message="%s %s..." % (lang(33028), viewName)) all_embymvideos = self.emby.getMusicVideos(viewId, basic=True, dialog=pdialog) @@ -1269,7 +1269,7 @@ class ManualSync(LibrarySync): if pdialog: pdialog.update( - heading="Emby for Kodi", + heading=lang(29999), message="%s %s..." % (lang(33029), viewName)) all_embytvshows = self.emby.getShows(viewId, basic=True, dialog=pdialog) @@ -1314,7 +1314,7 @@ class ManualSync(LibrarySync): # Get all episodes in view if pdialog: pdialog.update( - heading="Emby for Kodi", + heading=lang(29999), message="%s %s..." % (lang(33030), viewName)) all_embyepisodes = self.emby.getEpisodes(viewId, basic=True, dialog=pdialog) @@ -1402,7 +1402,7 @@ class ManualSync(LibrarySync): for data_type in ['artists', 'albums', 'songs']: if pdialog: pdialog.update( - heading="Emby for Kodi", + heading=lang(29999), message="%s %s..." % (lang(33031), data_type)) if data_type != "artists": all_embyitems = process[data_type][0](basic=True, dialog=pdialog) diff --git a/resources/lib/playutils.py b/resources/lib/playutils.py index 8e3c9a08..d364bbb3 100644 --- a/resources/lib/playutils.py +++ b/resources/lib/playutils.py @@ -135,7 +135,7 @@ class PlayUtils(): # Let the user know that direct play failed settings('failCount', value=str(count+1)) dialog.notification( - heading="Emby for Kodi", + heading=lang(29999), message=lang(33011), icon="special://home/addons/plugin.video.emby/icon.png", sound=False) @@ -144,7 +144,7 @@ class PlayUtils(): settings('playFromStream', value="true") settings('failCount', value="0") dialog.notification( - heading="Emby for Kodi", + heading=lang(29999), message=lang(33012), icon="special://home/addons/plugin.video.emby/icon.png", sound=False) diff --git a/resources/lib/userclient.py b/resources/lib/userclient.py index ce985647..c5d9caeb 100644 --- a/resources/lib/userclient.py +++ b/resources/lib/userclient.py @@ -221,7 +221,7 @@ class UserClient(threading.Thread): log("Access is granted.", 1) self.HasAccess = True window('emby_serverStatus', clear=True) - xbmcgui.Dialog().notification("Emby for Kodi", lang(33007)) + xbmcgui.Dialog().notification(lang(29999), lang(33007)) def loadCurrUser(self, authenticated=False): @@ -350,7 +350,7 @@ class UserClient(threading.Thread): if accessToken is not None: self.currUser = username - dialog.notification("Emby for Kodi", + dialog.notification(lang(29999), "%s %s!" % (lang(33000), self.currUser.decode('utf-8'))) settings('accessToken', value=accessToken) settings('userId%s' % username, value=result['User']['Id']) diff --git a/resources/lib/utils.py b/resources/lib/utils.py index 77220304..74f216a7 100644 --- a/resources/lib/utils.py +++ b/resources/lib/utils.py @@ -260,7 +260,7 @@ def reset(): dialog = xbmcgui.Dialog() - if not dialog.yesno("Warning", "Are you sure you want to reset your local Kodi database?"): + if not dialog.yesno(language(29999), language(33074)): return # first stop any db sync @@ -270,7 +270,7 @@ def reset(): log("Sync is running, will retry: %s..." % count) count -= 1 if count == 0: - dialog.ok("Warning", "Could not stop the database from running. Try again.") + dialog.ok(language(29999), language(33085)) return xbmc.sleep(1000) @@ -322,7 +322,7 @@ def reset(): cursor.close() # Offer to wipe cached thumbnails - resp = dialog.yesno("Warning", "Remove all cached artwork?") + resp = dialog.yesno(language(29999), language(33086)) if resp: log("Resetting all cached artwork.", 0) # Remove all existing textures first @@ -353,7 +353,7 @@ def reset(): settings('SyncInstallRunDone', value="false") # Remove emby info - resp = dialog.yesno("Warning", "Reset all Emby Addon settings?") + resp = dialog.yesno(language(29999), language(33087)) if resp: # Delete the settings addon = xbmcaddon.Addon() @@ -362,9 +362,7 @@ def reset(): xbmcvfs.delete(dataPath) log("Deleting: settings.xml", 1) - dialog.ok( - heading="Emby for Kodi", - line1="Database reset has completed, Kodi will now restart to apply the changes.") + dialog.ok(heading=language(29999), line1=language(33088)) xbmc.executebuiltin('RestartApp') def sourcesXML(): @@ -424,7 +422,7 @@ def passwordsXML(): credentials = settings('networkCreds') if credentials: # Present user with options - option = dialog.select("Modify/Remove network credentials", ["Modify", "Remove"]) + option = dialog.select(language(33075), [language(33076), language(33077)]) if option < 0: # User cancelled dialog @@ -444,8 +442,8 @@ def passwordsXML(): settings('networkCreds', value="") xbmcgui.Dialog().notification( - heading="Emby for Kodi", - message="%s removed from passwords.xml" % credentials, + heading=language(29999), + message="%s %s" % (language(33078), credentials), icon="special://home/addons/plugin.video.emby/icon.png", time=1000, sound=False) @@ -453,28 +451,22 @@ def passwordsXML(): elif option == 0: # User selected to modify - server = dialog.input("Modify the computer name or ip address", credentials) + server = dialog.input(language(33083), 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") + dialog.ok(heading=language(29999), line1=language(33082)) + server = dialog.input(language(33084)) if not server: return # Network username - user = dialog.input("Enter the network username") + user = dialog.input(language(33079)) if not user: return # Network password - password = dialog.input( - heading="Enter the network password", - option=xbmcgui.ALPHANUM_HIDE_INPUT) + password = dialog.input(heading=language(33080), option=xbmcgui.ALPHANUM_HIDE_INPUT) if not password: return @@ -503,8 +495,8 @@ def passwordsXML(): etree.ElementTree(root).write(xmlpath) dialog.notification( - heading="Emby for Kodi", - message="%s added to passwords.xml" % server, + heading=language(29999), + message="%s %s" % (language(33081), server), icon="special://home/addons/plugin.video.emby/icon.png", time=1000, sound=False) diff --git a/resources/lib/websocket_client.py b/resources/lib/websocket_client.py index 90e9499e..87d1e012 100644 --- a/resources/lib/websocket_client.py +++ b/resources/lib/websocket_client.py @@ -80,7 +80,7 @@ class WebSocket_Client(threading.Thread): if command == "PlayNow": dialog.notification( - heading="Emby for Kodi", + heading=lang(29999), message="%s %s" % (len(itemIds), lang(33004)), icon="special://home/addons/plugin.video.emby/icon.png", sound=False) @@ -89,7 +89,7 @@ class WebSocket_Client(threading.Thread): elif command == "PlayNext": dialog.notification( - heading="Emby for Kodi", + heading=lang(29999), message="%s %s" % (len(itemIds), lang(33005)), icon="special://home/addons/plugin.video.emby/icon.png", sound=False) @@ -244,7 +244,7 @@ class WebSocket_Client(threading.Thread): elif messageType == "ServerRestarting": if settings('supressRestartMsg') == "true": dialog.notification( - heading="Emby for Kodi", + heading=lang(29999), message=lang(33006), icon="special://home/addons/plugin.video.emby/icon.png") From cb18f17dbeae4648f0e94086b7d52093d6b0ec42 Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Mon, 20 Jun 2016 21:21:24 -0500 Subject: [PATCH 026/103] Fix typo --- resources/lib/entrypoint.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/lib/entrypoint.py b/resources/lib/entrypoint.py index ea63add0..9ecb8bd3 100644 --- a/resources/lib/entrypoint.py +++ b/resources/lib/entrypoint.py @@ -332,7 +332,7 @@ def getThemeMedia(): 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=lang(29999), line1=lang(33073))0 + dialog.ok(heading=lang(29999), line1=lang(33073)) xbmc.executebuiltin('Addon.OpenSettings(script.tvtunes)') return From 8423342371d92a5ce7c7e0f2be3445fcb88e22cd Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Mon, 20 Jun 2016 21:23:36 -0500 Subject: [PATCH 027/103] Fix active listing --- resources/lib/entrypoint.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/lib/entrypoint.py b/resources/lib/entrypoint.py index 9ecb8bd3..14e08196 100644 --- a/resources/lib/entrypoint.py +++ b/resources/lib/entrypoint.py @@ -88,7 +88,7 @@ def doMainListing(): addDirectoryItem(label, path) elif xbmc.getCondVisibility("Window.IsActive(VideoLibrary)") and node != "photos": addDirectoryItem(label, path) - else: + elif not xbmc.getCondVisibility("Window.IsActive(VideoLibrary) | Window.IsActive(Pictures) | Window.IsActive(MusicLibrary)"): addDirectoryItem(label, path) # experimental live tv nodes From 8575a1404357cc2ba8c51490123ea957c83d437f Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Mon, 20 Jun 2016 22:18:04 -0500 Subject: [PATCH 028/103] Version bump 2.2.11 --- addon.xml | 2 +- changelog.txt | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/addon.xml b/addon.xml index 67c54a29..1a78b02f 100644 --- a/addon.xml +++ b/addon.xml @@ -1,7 +1,7 @@ diff --git a/changelog.txt b/changelog.txt index 3a2da525..93b264fb 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,8 @@ +version 2.2.11 +- Preparation for feature requests +- Add option to refresh Emby items via context menu +- Minor fixes + version 2.2.10 - Add keymap action for delete content: RunPlugin(plugin://plugin.video.emby?mode=delete) - Fix various bugs From 03a1557eaf625735d966ce909a2514c24abd368f Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Tue, 21 Jun 2016 15:26:42 -0500 Subject: [PATCH 029/103] Fix typo --- resources/lib/read_embyserver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/lib/read_embyserver.py b/resources/lib/read_embyserver.py index 5e62425e..15fe2d7a 100644 --- a/resources/lib/read_embyserver.py +++ b/resources/lib/read_embyserver.py @@ -570,5 +570,5 @@ class Read_EmbyServer(): def deleteItem(self, itemid): - url = "{server}/emby/Items/%s?format=json" % itemId + url = "{server}/emby/Items/%s?format=json" % itemid self.doUtils(url, action_type="DELETE") \ No newline at end of file From b2d3b702d1f4dcd87e78da0bbfc702d028284be7 Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Wed, 22 Jun 2016 14:05:53 -0500 Subject: [PATCH 030/103] Revert incorrect modification Only use is for None, nothing else. "is" is not "==" --- resources/lib/userclient.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/lib/userclient.py b/resources/lib/userclient.py index c5d9caeb..d08e00ae 100644 --- a/resources/lib/userclient.py +++ b/resources/lib/userclient.py @@ -295,7 +295,7 @@ class UserClient(threading.Thread): elif self.getToken(): result = self.loadCurrUser() - if result is False: + if result == False: pass else: log("Current user: %s" % self.currUser, 1) From da8bf3670d067120084649994cb37cf5312d8929 Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Wed, 22 Jun 2016 14:29:53 -0500 Subject: [PATCH 031/103] Fix conflicting import --- resources/lib/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/lib/utils.py b/resources/lib/utils.py index 74f216a7..e2bfea65 100644 --- a/resources/lib/utils.py +++ b/resources/lib/utils.py @@ -12,7 +12,7 @@ import os import time import unicodedata import xml.etree.ElementTree as etree -from datetime import datetime, time +from datetime import datetime import xbmc import xbmcaddon From 2794789f05de5970e2269bfbd802d8eacfe5b168 Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Thu, 23 Jun 2016 18:50:34 -0500 Subject: [PATCH 032/103] Fix for channel not changing To be reviewed. --- resources/lib/playutils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/lib/playutils.py b/resources/lib/playutils.py index d364bbb3..ea76b085 100644 --- a/resources/lib/playutils.py +++ b/resources/lib/playutils.py @@ -36,7 +36,7 @@ class PlayUtils(): and self.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, self.item['Id']) + playurl = "%s/emby/Videos/%s/stream.ts?audioCodec=copy&videoCodec=copy" % (self.server, self.item['Id']) window('emby_%s.playmethod' % playurl, value="Transcode") elif self.item.get('MediaSources') and self.item['MediaSources'][0]['Protocol'] == "Http": @@ -244,7 +244,7 @@ class PlayUtils(): 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']) + playurl = "%s/emby/Videos/%s/stream.ts?audioCodec=copy&videoCodec=copy" % (self.server, self.item['Id']) return playurl From a84ba22908db6eda2e89e40a6333c3dc0800648e Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Thu, 23 Jun 2016 19:52:54 -0500 Subject: [PATCH 033/103] Fix for music --- resources/lib/musicutils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/lib/musicutils.py b/resources/lib/musicutils.py index 66da24a1..6ff43e01 100644 --- a/resources/lib/musicutils.py +++ b/resources/lib/musicutils.py @@ -170,7 +170,7 @@ def getAdditionalSongTags(embyid, emby_rating, API, kodicursor, emby_db, enablei # sync details to emby server. Translation needed between ID3 rating and emby likes/favourites: like, favourite, deletelike = getEmbyRatingFromKodiRating(rating) window("ignore-update-%s" %embyid, "true") #set temp windows prop to ignore the update from webclient update - emby.updateUserRating(embyid, like, favourite, deletelike) + emby.updateUserRating(embyid, favourite) return (rating, comment, hasEmbeddedCover) From 6837144ba7562cf8375de55a145a2ac9b95aa4f8 Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Mon, 27 Jun 2016 23:55:14 -0500 Subject: [PATCH 034/103] Revert changes - static=true --- resources/lib/playutils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/lib/playutils.py b/resources/lib/playutils.py index ea76b085..71c92696 100644 --- a/resources/lib/playutils.py +++ b/resources/lib/playutils.py @@ -244,7 +244,7 @@ class PlayUtils(): elif self.item['Type'] == "Audio": playurl = "%s/emby/Audio/%s/stream.mp3" % (self.server, self.item['Id']) else: - playurl = "%s/emby/Videos/%s/stream.ts?audioCodec=copy&videoCodec=copy" % (self.server, self.item['Id']) + playurl = "%s/emby/Videos/%s/stream?static=true" % (self.server, self.item['Id']) return playurl From 269205fe97f1145a35205caf7c36b5d2bcd4c185 Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Tue, 28 Jun 2016 01:09:36 -0500 Subject: [PATCH 035/103] Remove profiling --- resources/lib/librarysync.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/resources/lib/librarysync.py b/resources/lib/librarysync.py index e31a83cb..8cfa096a 100644 --- a/resources/lib/librarysync.py +++ b/resources/lib/librarysync.py @@ -670,6 +670,7 @@ class LibrarySync(threading.Thread): return True + @utils.profiling() def tvshows(self, embycursor, kodicursor, pdialog): # Get shows from emby @@ -906,7 +907,7 @@ class LibrarySync(threading.Thread): "You should restart Kodi now. " "Please report this on the forum.")) raise - @utils.profiling() + def run_internal(self): dialog = xbmcgui.Dialog() From 237cb26dc8b1f6ff29a7b09efbb938f82f4dff1d Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Tue, 28 Jun 2016 12:45:54 -0500 Subject: [PATCH 036/103] Translation - Dutch --- resources/language/Dutch/strings.xml | 305 +++++++++++++++++++++++++++ 1 file changed, 305 insertions(+) create mode 100644 resources/language/Dutch/strings.xml diff --git a/resources/language/Dutch/strings.xml b/resources/language/Dutch/strings.xml new file mode 100644 index 00000000..a75eb29f --- /dev/null +++ b/resources/language/Dutch/strings.xml @@ -0,0 +1,305 @@ + + + + Emby voor Kodi + Primaire server adres + Afspelen vanaf HTTP in plaats van SMB + Log niveau + Apparaatnaam + Geavanceerd + Gebruikersnaam + Poortnummer + Aantal recente muziekalbums die getoond worden: + Aantal recente films die getoond worden: + Aantal recente TV-series die getoond worden: + Vernieuwen + Wissen + Ongeldige gebruikersnaam/wachtwoord + Gebruikersnaam niet gevonden + Wissen... + Wacht op server voor wissen + Sorteer op + Geen + Actie + Avontuur + Animatie + Misdaad + Comedy + Documentaire + Drama + Fantasie + Vreemdtalig + Geschiedenis + Horror + Muziek + Musical + Mysterie + Romantiek + Science Fiction + Kort + Spanning + Thriller + Western + Genre Filter + Bevestig bestandsverwijdering + + Bekeken markeren + Onbekeken markeren + Sorteer op + Sorteer oplopend + Sorteer aflopend + + Hervatten + Hervatten vanaf + Start vanaf begin + Toon verwijder mogelijkheid na het afspelen + + Voor Afleveringen + + Voor Films + + Hervat-percentage toevoegen + Afleveringnummer toevoegen + Toon voortgang + Laden van content + Ontvang Data + Gereed + Waarschuwing + + Fout + Zoeken + Activeer speciale afbeeldingen (bijv. CoverArt) + + Metadata + Afbeeldingen + Video kwaliteit + + Direct Afspelen + + Transcoderen + Server Detectie Geslaagd + Gevonden server + Adres: + + Onlangs toegevoegde TV-series + Niet afgekeken TV-series + Alle Muziek + Kanalen + Onlangs toegevoegde films + Onlangs toegevoegde afleveringen + Onlangs toegevoegde albums + Niet afgekeken films + Niet afgekeken afleveringen + Volgende (NextUp) afleveringen + Favoriete films + Favoriete TV-series + + Favoriete afleveringen + Vaak afgespeelde albums + Binnenkort op TV + BoxSets + Trailers + Muziek video\'s + Foto\'s + Onbekeken films + + Film Genres + Film Studio\'s + Film Acteurs + Onbekeken afleveringen + TV Genres + TV Netwerken + TV Acteurs + Afspeellijsten + Weergaven instellen + Selecteer gebruiker + + Kan niet verbinden met server + Nummers + Albums + Album artiesten + Artiesten + Muziek Genres + Laatste + Bezig + Volgende (NextUp) + Gebruikersweergaven + Rapporteer Metrics + Willekeurige Films + Willekeurige Afleveringen + Willekeurige Items + + Aanbevolen Items + Extra\'s + + Synchroniseer Thema Muziek + Synchroniseer Extra Fanart + Synchroniseer Film Boxsets + Lokale Kodi database opnieuw instellen + + Activeer HTTPS + Forceer Transcoderen van Codecs + Activeer server verbindings melding bij het opstarten + Onlangs bekeken Thuis Video\'s + Onlangs toegevoegde Foto\'s + Favoriete Home Video\'s + Favoriete Foto\'s + Favoriete Albums + Onlangs toegevoegde Muziek Video\'s + Niet afgekeken Muziek Video\'s + Onbekeken Muziek Video\'s + + + Actief + Herstel naar standaard + Films + BoxSets + Trailers + TV-series + Seizoenen + Afleveringen + Muziek - artiesten + Muziek - albums + Muziek Video\'s + Muziek - nummers + Kanalen + + Emby opties + Toevoegen aan Emby favorieten + Verwijderen uit Emby favorieten + Instellen aangepaste waardering nummers + Emby addon instellingen + Verwijder item van de server + Dit item vernieuwen + Instellen aangepaste waardering voor nummers (0-5) + + Controleer Host SSL Certificaat + Client SSL Certificaat + Gebruik alternatief adres + Alternatief Serveradres + Gebruik alternatieve apparaatnaam + [COLOR yellow]Probeer opnieuw in te loggen[/COLOR] + Synchronisatie Opties + Toon synchronisatie voortgang + Synchroniseer lege TV-series + Activeer Muziekbibliotheek + Direct Stream muziekbibliotheek + Afspeelmodus + Forceer afbeelding caching + Limiteer afbeelding cache threads (aanbevolen voor rpi) + Activeer \"fast startup\" (vereist server plugin) + Maximaal aantal items om tegelijk van de server op te vragen + Afspelen + Netwerkreferenties + Activeer Emby bioscoopmodus + Vragen om trailers af te spelen + Sla Emby verwijderings bevestiging over voor het contextmenu (Gebruik op eigen risico) + Spring terug op hervatten (in seconden) + Forceer transcoderen H265 + Muziek metadata opties (niet compatibel met direct stream) + Importeer muziek lied waardering direct uit bestanden + Converteer muziek lied waardering naar Emby waardering + Toestaan dat waardeing in muziekbestanden worden bijgewerkt + Negeer specials in de volgende afleveringen + Gebruikers permanent toevoegen aan de sessie + Opstart vertraging (in seconden) + Activeer server herstart bericht + Activeer nieuwe inhoud notificatie + Duur van de videobibliotheek pop-up (in seconden) + Duur van de muziekbibliotheek pop-up (in seconden) + Server berichten + Genereer een nieuw apparaat Id + Sync als screensaver is uitgeschakeld + Forceer Transcoderen Hi10P + Uitgeschakeld + + Welkom + Fout bij maken van verbinding + De server is onbereikbaar + Server is online + items toegevoegd aan afspeellijst + items in de wachtrij voor afspeellijst + Server is opnieuw aan het opstarten + Toegang is ingeschakeld + Voer het wachtwoord in voor de gebruiker: + Foutieve gebruikersnaam of wachtwoord + Niet te vaak te authenticeren + Niet in staat om direct af te spelen + Direct afspelen is 3x mislukt. Afspelen vanaf HTTP Ingeschakeld. + Kies het audiokanaal + Kies de ondertiteling + Verwijder bestand van Emby server? + Trailers afspelen? + Verzamelen films van: + verzamelen boxsets + Verzamelen muziek-video\'s van: + Verzamelen TV-series van: + Verzamelen: + Gedetecteerd dat de database moet worden vernieuwd voor deze versie van Emby voor Kodi. Doorgaan? + Emby voor Kodi kan mogelijk niet correct werken tot de database is teruggezet. + Database synchronisatie proces geannuleerd. De huidige Kodi versie wordt niet ondersteund. + voltooid in: + Vergelijken van films uit: + Vergelijken van boxsets + Vergelijken van muziek-video\'s uit: + Vergelijken van TV-series uit: + Vergelijken van afleveringen uit: + Vergelijken: + Mislukt een nieuw apparaat Id te genereren. Zie je logs voor meer informatie. + Er is een nieuw apparaat Id gegenereerd. Kodi zal nu opnieuw opstarten. + Verder gaan met de volgende server? + LET OP! Als u de modus Native kiest, zullen bepaalde Emby functies ontbreken, zoals: Emby bioscoop-modus, directe afspeel/transcodeer opties en ouderlijk-toezicht planner. + Addon (Standaard) + Native (Directe paden) + Voeg netwerkreferenties toe aan Kodi om toegang tot uw content toe te staan? Belangrijk: Kodi moet opnieuw worden opgestart om de referenties te zien. Zij kunnen ook later worden toegevoegd. + Emby muziekbibliotheek uitschakelen? + Direct Stream de muziekbibliotheek? Selecteer deze optie als de muziekbibliotheek op afstand worden benaderd. + Bestand(en) van de Emby Server verwijderen? Dit zal de bestanden ook van de schijf verwijderen! + Het uitvoeren van de caching proces kan enige tijd duren. Toch verder gaan? + Afbeeldingen cache sync + Herstel bestaande afbeeldingen cache? + Bijwerken afbeeldingen cache: + Wachten op alle taken om af te sluiten: + Kodi kan bestand niet vinden: + Het kan nodig zijn om uw netwerkreferenties te controleren in de add-on-instellingen of gebruik de Emby padvervanging om je pad correct op te geven (Emby dashboard> Bibliotheek). Stop synchroniseren? + Toegevoegd: + Wanneer u zich te vaak foutief aanmeld, zal de Emby Server uw account blokkeren. Toch doorgaan? + Live TV-kanalen (experimenteel) + Live TV-opnames (experimenteel) + Instellingen + Gebruiker toevoegen aan sessie + Vernieuw Emby afspeellijsen/Video knooppunten + Handmatig synchroniseren + Reparatie lokale database (forceer-bijwerken van alle inhoud) + Lokale database herstellen + Cache alle afbeeldingen + Sync Emby Thema Media naar Kodi + Gebruiker toevoegen/verwijderen van de sessie + Gebruiker toevoegen + Gebruiker verwijderen + Gebruiker verwijderen van de sessie + Geslaagd! + Verwijderd uit de bekijken sessie: + Toegevoegd aan de bekijken sessie: + Niet in staat om gebruiker toe te voegen/verwijderen uit de sessie. + De taak is geslaagd + De taak is mislukt + Direct Stream + Afspeel methode voor uw thema\'s + Het instellingenbestand bestaat niet in TV-Tunes. Wijzig een instelling en voer de taak opnieuw uit. + Weet u zeker dat u uw lokale Kodi database opnieuw wilt instellen? + Wijzig/verwijder netwerkreferenties + Wijzig + Verwijder + Verwijderd: + Geef netwerk gebruikersnaam op: + Geeft netwerk wachtwoord op: + Netwerkreferenties toegevoegd voor: + Voer de servernaam of het IP-adres in, zoals aangegeven in uw Emby bibliotheek paden. Bijvoorbeeld, de naam van de server: \\\\SERVER-PC\\pad\\ is \"SERVER-PC\" + Wijzig de servernaam of IP-adres + Geef de servernaam of het IP-adres op + Kon de database niet opnieuw instellen. Probeer het nog eens. + Verwijder al gecachte afbeeldingen? + Alle Emby add-on-instellingen opnieuw instellen? + Database opnieuw instellen is voltooid, Kodi zal nu opnieuw opstarten om de wijzigingen toe te passen. + From 16ea2e35c36bcd780dcb99941fddbcc46891eb22 Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Tue, 28 Jun 2016 13:03:12 -0500 Subject: [PATCH 037/103] Translation - Russian,Swedish --- resources/language/Russian/strings.xml | 329 ++++++++++++++++++++++++ resources/language/Swedish/strings.xml | 330 +++++++++++++++++++++++++ 2 files changed, 659 insertions(+) create mode 100644 resources/language/Russian/strings.xml create mode 100644 resources/language/Swedish/strings.xml diff --git a/resources/language/Russian/strings.xml b/resources/language/Russian/strings.xml new file mode 100644 index 00000000..146d8cc7 --- /dev/null +++ b/resources/language/Russian/strings.xml @@ -0,0 +1,329 @@ + + + + Emby для Kodi + Основной адрес сервера + + Воспроизводить по HTTP вместо SMB + + Уровень журналирования + + Название устройства + + Расширенное + Имя пользователя + + Номер порта + + Число последних музыкальных альбомов для просмотра: + Число последних фильмов для просмотра: + Число последних ТВ-эпизодов для просмотра: + Подновить + Удалить + Неверное Имя пользователя/Пароль + Имя пользователя не найдено + Удаляется + В ожидании удаления с сервера + Сортировать по + Никакой + Действие + Приключения + Анимация + Криминал + Комедия + Документалистика + Драма + Фэнтези + Иностранное + История + Ужасы + Музыка + Мюзикл + Детектив + Мелодрама + Научная фантастика + Короткометражное + Саспенс + Триллер + Вестерн + Фильтровать по жанрам + Подтверить удаление файла + + Отметить как просмотренное + Отметить как непросмотренное + Сортировать по + Порядок сортировки по убыванию + Порядок сортировки по возрастанию + + Возобновить + Возобн. с + Начать. с начала + Предлагать удаление после воспроизведения + + Для эпизодов + + Для фильмов + + Добавить соотношение для возобновления + Добавить номер эпизода + Показывать индикатор загрузки + Загружается содержание + Получение данных + Готово + Предупреждение + + Ошибка + Поиск + Включить улучшенные рисунки (нп., CoverArt) + + Метаданные + Иллюстрация + Качество видео + + Прямое воспроизведение + + Перекодировка + Успешно обнаружен сервер + Найден сервер + Адрес: + + Недавно добавленные ТВ-программы + + ТВ-программы в процессе + + Вся музыка + Каналы + + Недавно добавленные фильмы + + Недавно добавленные эпизоды + + Недавно добавленные альбомы + Фильмы в процессе + + Эпизоды в процессе + + Следующие эпизоды + + Избранные фильмы + + Избранные ТВ-программы + + Избранные эпизоды + Часто воспроизводимые альбомы + Ожидаемое + Коллекции + Трейлеры + Муз-ые видео + Фотографии + Непросмотренные фильмы + + Киножанры + Киностудии + Киноактёры + Непросмотренные эпизоды + Тележанры + Телесети + Телеактёры + Плей-листы + Назначить виды + Выбрать пользователя + + Не удалось подсоединиться к серверу + Композиции + Альбомы + Исполнители альбома + Исполнители + Музыкальные жанры + Последнее + Выполняется + Следующее + Пользовательские виды + Отчёт метрики + Случайные фильмы + Случайные эпизоды + Случайные элементы + + Предлагаемые элементы + + Допматериалы + + Синхронизировать тематическую музыку + Синхронизировать дополнительные иллюстрации + Синхронизировать коллекции фильмов + Сбросить локальную базу данных Kodi + + Включить HTTPS + + Принудительно включить кодеки для перекодировки + Включать сообщение о подсоединении сервера при запуске + + Недавно добавленные домашние видео + + Недавно добавленные фотографии + + Избранные домашние видео + + Избранные фотографии + + Избранные альбомы + Недавно добавленные музыкальные видео + + Музыкальные видео в процессе + + Непросмотренные домашние видео + + + Активно + Очистить параметры + Фильмы + Коллекции + Трейлеры + Сериалы + Сезоны + Эпизоды + Исп-ли музыки + Муз-ые альбомы + Муз-ые видео + Музыкальные дорожки + Каналы + + Параметры Emby + Добавить в Избранное Emby + Изъять из Избранного Emby + Назначить произвольную возрастную категорию композиции + Параметры дополнения для Emby + Удалить элемент с сервера + Подновить данный элемент + Назначить произвольную возрастную категорию композиции (0-5) + + Удостоверить SSL-сертификат хоста + SSL-сертификат клиента + Использовать альтернативный адрес + Альтернативный адрес сервера + Использовать альтернативное имя устройства + [COLOR yellow]Войти повторно[/COLOR] + Параметры синхронизации + Показать прогресс синхронизации + Синхронизировать пустые ТВ-программы + Включить медиатеку музыки + Прямая трансляция медиатеки музыки + Режим воспроизведения + Принудительно кэшировать иллюстрации + Предел ветвей кэша иллюстраций (рекомендуется для rpi) + Включить быстрый запуск (требуется плагин сервера) + Максимальное число элементов для запроса с сервера за раз + Воспроизведение + Сетевые учётные данные + Включить режим кинотеатра Emby + Запрашивать для воспроизведения трейлеров + Пропускать подтверждение удаления в Emby для контекстного меню (используйте на свой страх и риск) + Переход назад при возобновлении, с + Принудительно перекодировать H265 + Параметры метаданных музыки (несовместимо с прямой трансляцией) + Импортировать оценку музыкальной композиции из файла + Преобразовать оценку музыкальной композиции в оценку Emby + Разрешить обновление оценки в файлах композиций + Игнорировать спецэпизоды среди следующих эпизодов + Постоянные пользователи для добавления в сессию + Задержка запуска, с + Включить сообщение о перезапуске сервера + Включить уведомление о новом содержании + Длительность показа всплывающего окна медиатеки видео, с + Длительность показа всплывающего окна медиатеки музыки, с + Сообщения сервера + Генерировать Id нового устройства + Синхронизировать, когда отключен хранитель экрана + Принудительно перекодировать Hi10P + Выключено + + Начало работы + Ошибка соединения + Сервер недостижим + Сервер в сети + Элементы, добавленные в плей-лист + Элементы в очереди в плей-листе + Сервер перезапускается + Доступ включён + Ввести пароль для пользователя: + Недопустимое имя пользователя или пароль. + Не удалось проверить подлинность слишком много раз + Прямое воспроизведение невозможно + Прямое воспроизведение не удалось 3 раза. Включено воспроизведение с HTTP. + Выбрать поток аудио + Выбрать поток субтитров + Удалить файл с вашего Emby Server? + Воспроизвести трейлеры? + Сбор фильмов с: + Сбор коллекций + Сбор музыкальных видео с: + Сбор ТВ-передач с: + Сбор: + Обнаружено, что базу данных необходимо пересоздать для данной версии Emby для Kodi. Приступить? + Emby для Kodi возможно не будет корректно работать до тех пор, пока базу данных не сбросят. + Процесс синхронизации базы данных отменён. Текущая версия Kodi не поддерживается. + выполнено в: + Сравниваются фильмы с: + Сравниваются коллекции + Сравниваются музыкальные видео с: + Сравниваются ТВ-передачи с: + Сравниваются ТВ-эпизоды с: + Сравниваются: + Генерирование Id нового устройства не удалось. Просмотрите ваши журналы для более подробной информации. + Было сгенерирован Id нового устройства. Kodi теперь перезапустится. + Приступить к следующему серверу? + Осторожно! Если вы выбрали режим Собственный, некоторые функции Emby будут отсутствовать, например, режим кинотеатра Emby, прямой трансляция / варианты перекодировки и расписание доступа. + Надстройка (по умолчанию) + Собственный (непосредственные пути) + Добавить сетевые учётные данные, чтобы разрешить доступ для Kodi к вашему содержанию? Важно: Чтобы увидеть учётные данные, необходимо перезапустить Kodi. Также они могут быть добавлены позднее. + Отключить музыкальную медиатеку Emby? + Транслировать напрямую музыкальную медиатеку? Выберите данный вариант, если будет удалённый доступ к музыкальной медиатеке. + Удалить файл(ы) с Emby Server? Файл(ы) будут удалены также с диска! + Работающий процесс кэширования может занять некоторое время. Продолжить по-любому? + Синхронизировать кэш иллюстраций + Сбросить кэш существующих иллюстраций? + Обновить кэш иллюстраций: + В ожидании для всех потоков, чтобы выйти: + Kodi не может обнаружить файл: + Вам может понадобиться проверить сетевые учётные данныев настройках надстройки или использовать подстановку путей в Emby, чтобы правильно форматировать свой путь (Инфопанель Emby > Медиатека). Остановить синхронизацию? + Добавлено: + Если вам не удалось войти слишком много раз, Emby server может заблокировать вашу учётную запись. Приступить по-любому? + Эфирные каналы (экспериментально) + Эфирные записи (экспериментально) + Параметры + Добавить пользователя к сеансу + Подновить узлы плей-листов/видео Emby + Выполнить ручную синхронизацию + Исправить локальную базу данных (принудительно обновить всё содержание) + Выполнить сброс локальной базы данных + Кэшировать все иллюстрации + Синхронизировать медиаданные темы Emby с Kodi + Добавить/Изъять пользователя из сеанса + Добавить пользователя + Изъять пользователя + Изъять пользователя из сеанса + Успешно! + Изъято из просматриваемого сеанса: + Добавлено в просматриваемый сеанс: + Невозможно добавить/изъять пользователя из сеанса. + Задача успешно выполнена + Задачу выполнить не удалось + Прямая трансляция + Метод воспроизведения для вашим тем + Файл параметров отсутствует в TV Tunes. Измените параметр и запустите задачу снова. + Вы действительно хотите выполнить сброс вашей локальной базы данных Kodi? + Изменить/Изъять сетевые учётные данные + Изменить + Изъять + Изъято: + Ввести сетевое имя пользователя + Ввести сетевой пароль + Добавлены сетевые учётные данные для: + Введите имя сервера или IP-адрес, как обозначено в путях в вашей медиатеке Emby. К примеру, имя сервера: \\\\SERVER-PC\\путь\\ является \"SERVER-PC\" + Изменить имя сервера или IP-адрес + Ввести имя сервера или IP-адрес + Не удалось сбросить базу данных. Повторите попытку. + Удалить все кэшированные иллюстрации? + Сбросить все параметры надстройки Emby? + Сброс базы данных завершён, Kodi теперь перезапустится, чтобы применить изменения. + diff --git a/resources/language/Swedish/strings.xml b/resources/language/Swedish/strings.xml new file mode 100644 index 00000000..7530f729 --- /dev/null +++ b/resources/language/Swedish/strings.xml @@ -0,0 +1,330 @@ + + + + Emby för Kodi + Primär serveradress + + Spela upp ifrån HTTP istället för SMB + + Lognivå + + Enhetsnamn + + Avancerat + Användarnamn + + Portnummer + + Antal nya album som ska visas: + Antal nya filmer som ska visas: + Antal nya TV-avsnitt som ska visas: + Uppdatera + Ta bort + Ogiltigt Användarnamn/Lösenord + Användarnamn kunde inte hittas + Tar bort + Väntar på server för borttagning + Sortera efter + Ingen + Action + Äventyr + Animaterat + Brott + Komedi + Dokumentär + Drama + Fantasi + Internationell + Historia + Skräck + Musik + Musikal + Mystisk + Romantik + Science Fiction + Kort + Spänning + Thriller + Västern + Genrefilter + Bekräfta borttagning utav fil? + + Markera som visad + Markera som ej visad + Sortera efter + Fallande sortering + Stigande sortering + + Fortsätt + Fortsätt från + Starta från början + Erbjud borttagning efter uppspelning + + För avsnitt + + För filmer + + Lägg till fortsättningsprocent + Lägg till avsnittsnummer + Visa förloppsmätare + Laddar innehåll + Hämtar data + Klart + Varning + + Fel + Sök + Aktivera förbättrade bilder (t.ex CoverArt) + + Metadata + Bilder + Videokvalitet + + Direktuppspelning + + Omkodning + Serversökning lyckades + Hittade server + Adress: + + Nyligen tillagda serier + + Pågående serier + + All musik + Kanaler + + Nyligen tillagda filmer + + Nyligen tillagda avsnitt + + Nyligen tillagda album + Pågående filmer + + Pågående avsnitt + + Nästa avsnitt + + Favoritfilmer + + Favoritserier + + Favoritavsnitt + Ofta spelade album + Kommande TV + Samlingar + Trailers + Musikvideor + Foton + Osedda filmer + + Filmgenrer + Filmstudior + Filmskådespelare + Osedda avsnitt + TV-genrer + TV-bolag + TV-skådespelare + Spellistor + Ställ in Vyer + Välj användare + + Kan inte koppla till servern + Låtar + Album + Albumartister + Artister + Musikgenrer + Senaste + Pågående + Nästa + Användarvyer + Rapportera Statistik + Slumpade filmer + Slumpade avsnitt + Slumpade objekt + + Rekommenderade objekt + + Extramaterial + + Synkronisera musiktema + Synkronisera extra fanart + Synkronisera filmsamlingar + Återställ lokal Kodi-databas + + Aktivera HTTPS + + Tvinga Omkodingskodecs + Emby Server uppkopplingsmeddelande vid uppstart + + Nyligen tillagda hemvideor + + Nyligen tillagda foton + + Favorithemvideor + + Favoritfoton + + Favoritalbum + Nyligen tillagda musikvideor + + Pågående musikvideor + + Osedda musikvideor + + + Aktiv + Återställ inställningar + Filmer + Samlingar + Trailers + Serier + Säsonger + Avsnitt + Musikartister + Musikalbum + Musikvideor + Låtar + Kanaler + + Emby-inställningar + Lägg till Emby-favoriter + Ta bort från Emby-favoriter + Sätt anpassat betyg för låt + Emby tilläggsinställningar + Ta bort objekt från servern + Uppdatera detta objekt + Sätt betyg på låt (0-5) + + Kontrollera värdens ssl-certifikat + Klientens ssl-certifikat + Använd alternativ adress + Alternativ serveraddress + Använd alternativt enhetsnamn + [COLOR yellow]Försök logga in igen[/COLOR] + Synkroniseringsalternativ + Visa förloppsmätare vid synkronisering + Synkronisera tomma serier + Aktivera musikbibliotek + Direktströmma musikbibliotek + Uppspelningsläge + Tvinga cachning av bilder + Begränsa bild cache trådar (rekommenderas för rpi) + Aktivera snabb upstart (kräver servertillägg) + Masximalt antal objekt att begära från servern samtidigt + Uppspelning + Nätverks inlogg + Aktivera Emby biografläge + Fråga för att spela upp trailers + Dölj Embys borttagningsbekräftelse i kontextmenyn(använd på egen risk) + Gå tillbaka vid återupptagen uppspelning (sekunder) + Tvinga omkodning av H265 + Musik metadata alternativ (ej kompatibel med direktströmning) + Importera låtbetyg direkt från filer + Konvertera låtbetyg till Emby-betyg + Tillåt uppdatering av låtbetyg i filer + Ignorera specialavsnitt i nästa avsnitt + Permanenta användare att lägga till sessionen + Uppstartsfördröjning (sekunder) + Aktivera meddelande vid omstart av servern + Aktivera meddelande vid nytt innehåll + Fördröjning av pop-up för videobiblioteket (i sekunder) + Fördröjning av pop-up för musikbiblioteket (i sekunder) + Servermeddelanden + Generera nytt enhetsID + "Synka när skärmsläckare är inaktiverad +" + Tvinga omkodning av Hi10P + Inaktiverad + + Välkommen + Fel vid uppkoppling + Kan inte nå servern + Servern är uppkopplad + objekt tillagda till spellista + objekt köade till spellista + Serverns startar om + Åtkomst är aktiverad + Skriv in lösenord för användare: + Ogiltigt användarnamn eller lösenord + Misslyckades att autentisera för många gånger + Kan inte direktspela + Direktspelning misslyckades tre gånger. Spelar upp från HTTP. + Välj ljudspår + Välj ström för undertext + Ta bort filen från din Emby server? + Spela trailers? + Hämtar filmer från: + Hämtar samlingar + Hämtar musikvideos från: + Hämtar TV-serier från: + Hämtar: + Databasen behöver återskapas för den här versionen av Emby för Kodi. Fortsätt? + Emby för Kodi kan tappa funktion tills databasen har återställts. + Avbryter synkroniseringen av databasen. Nuvarande versionen av Kodi stöds inte. + färdig på: + Jämför filmer från: + Jämför samlingar + Jämför musikvideor från: + Jämför TV-serier från: + Jämför avsnitt från: + Jämför: + Kunde inte generera ett nytt enhetsID. Se i loggarna för mer information. + Ett nytt enhetsID har genererats. Kodi kommer nu starta om. + Fortsätt med följande server? + OBS! Om du väljer \'Native\'-läget så tappar du vissa funktioner i Emby, som; Emby bioläge, direktströmning/omkodning och schema för föräldralås. + Tillägg (Standard) + Native (Direkta Sökvägar) + Lägg till nätverksuppgifter för att ge Kodi åtkomst till ditt innehåll? Viktigt: Kodi kommer behöva startas om för att se uppgifterna. Dom kan också läggas till vid ett senare tillfälle. + Inaktivera Emby musikbibliotek? + Direktströmma musikbiblioteket? Välj det här alternativet om musikbiblioteket inte finns tillgängligt lokalt. + Ta bort fil(er) från Emby Server? Det här tar också bort ifrån disk! + Caching processen kan ta lite tid. Fortsätt ändå? + Cachesynk för bilder + Återställ nuvarande bildcache + Uppdaterar bildcache: + Väntar på alla trådar att avslutas: + Kodi kan inte hitta filen: + Du kan behöva verifiera dina nätverksuppgifter i addon-inställningarna eller använda Emby sökvägsersättning för att formatera din sökväg korrekt(Emby dashboard > bibliotek). Stoppa synkronisering? + Tillagd: + Om du misslyckas att logga in för många gånger, kan Emby server låsa ner ditt konto. Fortsätt ändå? + Live TV Kanaler (experimentellt) + Live TV Inspelningar (experimentellt) + Inställningar + Lägg till användare för sessionen + Uppdatera Emby spellistor/Videonoder + Kör manuell synk + Reparera lokala databasen (tvinga uppdatering av allt innehåll) + Återställ lokala databasen + Förlagra alla bilder + Synka Emby Tema till Kodi + Lägg till/Ta bort användare från sessionen + Lägg till användare + Ta bort användare + Ta bort användare från sessionen + Lyckades! + Borttagen från sessionen: + Tillagd till sessionen: + Kan inte lägga till/ta bort användaren från sessionen + Uppgiften lyckades + Uppgiften misslyckades + Direktströmma + Uppspelningsmetod för dina teman + Inställningsfilen finns inte i TVTunes. Ändra en inställning och kör sen uppgiften igen. + Är du säker på att du vill återställa din lokala Kodi databas? + Ändra/Ta bort nätverksuppgifter + Ändra + Ta bort + Borttagen: + Ange användarnamn för nätverk + Ange nätverkslösenordet + Lade till nätverksinlogg för: + Ange servernamn eller IP-adress efter din emby bibliotekssökvägar. T.ex. servernamnet: \\\\SERVER-PC\\sökväg blir \"SERVER-PC\" + Ändra servernamnet eller IP-adressen + Ange servernamnet eller IP-adressen + Kunde inte återställa databasen. Försök igen. + Ta bort alla förlagrade bilder? + Återställ alla Emby tilläggsinställningar? + Databasen har återställts, Kodi kommer nu att starta om för att verkställa ändringarna. + From c0f0a1978f4f56853ee9d066a614cbaab0f4f581 Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Tue, 28 Jun 2016 17:24:12 -0500 Subject: [PATCH 038/103] Preparation for centralized queries To handle exceptions, etc. --- resources/lib/utils.py | 55 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 54 insertions(+), 1 deletion(-) diff --git a/resources/lib/utils.py b/resources/lib/utils.py index e2bfea65..cda4c5e2 100644 --- a/resources/lib/utils.py +++ b/resources/lib/utils.py @@ -99,7 +99,7 @@ def kodiSQL(media_type="video"): else: dbPath = getKodiVideoDBPath() - connection = sqlite3.connect(dbPath) + connection = sqlite3.connect(dbPath, timeout=20) return connection def getKodiVideoDBPath(): @@ -134,6 +134,59 @@ def getKodiMusicDBPath(): % dbVersion.get(xbmc.getInfoLabel('System.BuildVersion')[:2], "")).decode('utf-8') return dbPath +def querySQL(query, args=None, cursor=None, conntype=None): + + result = None + manualconn = False + failed = False + + if cursor is None: + if conntype is None: + log("New connection type is missing.", 1) + return result + else: + manualconn = True + connection = kodiSQL(conntype) + cursor = connection.cursor() + + attempts = 0 + while attempts < 3: + try: + log("Query: %s Args: %s" % (query, args), 2) + if args is None: + result = cursor.execute(query) + else: + result = cursor.execute(query, args) + break # Query successful, break out of while loop + except sqlite3.OperationalError as e: + if "database is locked" in e: + log("%s...Attempt: %s" % (e, attempts), 0) + attempts += 1 + xbmc.sleep(1000) + else: + log("Error sqlite3: %s" % e, 0) + if manualconn: + cursor.close() + raise + except sqlite3.Error as e: + log("Error sqlite3: %s" % e, 0) + if manualconn: + cursor.close() + raise + else: + failed = True + log("FAILED // Query: %s Args: %s" % (query, args), 1) + + if manualconn: + if failed: + cursor.close() + else: + connection.commit() + cursor.close() + + log(result, 2) + return result + ################################################################################################# # Utility methods From 9a98e2995e4649201dc30109b1c8a68cf6779a72 Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Tue, 28 Jun 2016 17:24:36 -0500 Subject: [PATCH 039/103] Remove profiling --- resources/lib/librarysync.py | 1 - 1 file changed, 1 deletion(-) diff --git a/resources/lib/librarysync.py b/resources/lib/librarysync.py index 8cfa096a..39fd0eaf 100644 --- a/resources/lib/librarysync.py +++ b/resources/lib/librarysync.py @@ -670,7 +670,6 @@ class LibrarySync(threading.Thread): return True - @utils.profiling() def tvshows(self, embycursor, kodicursor, pdialog): # Get shows from emby From 470286b2fb7f4a9979c1aa5a8de4da3cb8edf7cb Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Tue, 28 Jun 2016 17:35:27 -0500 Subject: [PATCH 040/103] Version bump 2.2.12 --- addon.xml | 2 +- changelog.txt | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/addon.xml b/addon.xml index 1a78b02f..6048b89d 100644 --- a/addon.xml +++ b/addon.xml @@ -1,7 +1,7 @@ diff --git a/changelog.txt b/changelog.txt index 93b264fb..197becbb 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,8 @@ +version 2.2.12 +- Preparation for Emby connect +- Add string translation (Dutch, Russian and Swedish) +- Various bug fixes + version 2.2.11 - Preparation for feature requests - Add option to refresh Emby items via context menu From 9a5bd10d40f32ea04dfb263238cd107ab29a6c51 Mon Sep 17 00:00:00 2001 From: SpootDev Date: Tue, 28 Jun 2016 18:28:01 -0500 Subject: [PATCH 041/103] celementtree --- resources/lib/utils.py | 5 ++++- resources/lib/videonodes.py | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/resources/lib/utils.py b/resources/lib/utils.py index cda4c5e2..34d22449 100644 --- a/resources/lib/utils.py +++ b/resources/lib/utils.py @@ -11,7 +11,10 @@ import StringIO import os import time import unicodedata -import xml.etree.ElementTree as etree +try: + import xml.etree.cElementTree as etree +except ImportError: + import xml.etree.ElementTree as etree from datetime import datetime import xbmc diff --git a/resources/lib/videonodes.py b/resources/lib/videonodes.py index bf1d20f4..a35695a3 100644 --- a/resources/lib/videonodes.py +++ b/resources/lib/videonodes.py @@ -3,7 +3,10 @@ ################################################################################################# import shutil -import xml.etree.ElementTree as etree +try: + import xml.etree.cElementTree as etree +except ImportError: + import xml.etree.ElementTree as etree import xbmc import xbmcaddon From df2600f579ded134705e7963734a62570bb49a64 Mon Sep 17 00:00:00 2001 From: kravone Date: Wed, 29 Jun 2016 21:15:26 +0200 Subject: [PATCH 042/103] Revert "Faster XML processing and lower memory" --- resources/lib/utils.py | 5 +---- resources/lib/videonodes.py | 5 +---- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/resources/lib/utils.py b/resources/lib/utils.py index 34d22449..cda4c5e2 100644 --- a/resources/lib/utils.py +++ b/resources/lib/utils.py @@ -11,10 +11,7 @@ import StringIO import os import time import unicodedata -try: - import xml.etree.cElementTree as etree -except ImportError: - import xml.etree.ElementTree as etree +import xml.etree.ElementTree as etree from datetime import datetime import xbmc diff --git a/resources/lib/videonodes.py b/resources/lib/videonodes.py index a35695a3..bf1d20f4 100644 --- a/resources/lib/videonodes.py +++ b/resources/lib/videonodes.py @@ -3,10 +3,7 @@ ################################################################################################# import shutil -try: - import xml.etree.cElementTree as etree -except ImportError: - import xml.etree.ElementTree as etree +import xml.etree.ElementTree as etree import xbmc import xbmcaddon From a9112498406b8f6a78225ea084ab0a64ddc8657a Mon Sep 17 00:00:00 2001 From: shaun Date: Sun, 3 Jul 2016 13:26:11 +1000 Subject: [PATCH 043/103] sync progress changes add (if item count greater) for sync progress dialog always show progress for full sync --- default.py | 2 +- resources/language/English/strings.xml | 2 +- resources/lib/librarysync.py | 41 +++++++++++++------------- resources/settings.xml | 5 ++-- 4 files changed, 25 insertions(+), 25 deletions(-) diff --git a/default.py b/default.py index 3912b4f5..03a338fc 100644 --- a/default.py +++ b/default.py @@ -120,7 +120,7 @@ class Main(): import librarysync lib = librarysync.LibrarySync() if mode == "manualsync": - librarysync.ManualSync().sync(dialog=True) + librarysync.ManualSync().sync() elif mode == "fastsync": lib.startSync() else: diff --git a/resources/language/English/strings.xml b/resources/language/English/strings.xml index ee2dc385..d3398f18 100644 --- a/resources/language/English/strings.xml +++ b/resources/language/English/strings.xml @@ -191,7 +191,7 @@ Use altername device Name [COLOR yellow]Retry login[/COLOR] Sync Options - Show syncing progress + Show progress if item count greater than Sync empty TV Shows Enable Music Library Direct stream music library diff --git a/resources/lib/librarysync.py b/resources/lib/librarysync.py index 39fd0eaf..d5608a19 100644 --- a/resources/lib/librarysync.py +++ b/resources/lib/librarysync.py @@ -59,14 +59,13 @@ class LibrarySync(threading.Thread): threading.Thread.__init__(self) - def progressDialog(self, title, forced=False): + def progressDialog(self, title): dialog = None - if settings('dbSyncIndicator') == "true" or forced: - dialog = xbmcgui.DialogProgressBG() - dialog.create("Emby for Kodi", title) - log("Show progress dialog: %s" % title, 2) + dialog = xbmcgui.DialogProgressBG() + dialog.create("Emby for Kodi", title) + log("Show progress dialog: %s" % title, 2) return dialog @@ -201,7 +200,7 @@ class LibrarySync(threading.Thread): connection.commit() log("Commit successful.", 1) - def fullSync(self, manualrun=False, repair=False, forceddialog=False): + def fullSync(self, manualrun=False, repair=False): # Only run once when first setting up. Can be run manually. music_enabled = settings('enableMusic') == "true" @@ -234,13 +233,11 @@ class LibrarySync(threading.Thread): message = "Manual sync" elif repair: message = "Repair sync" - forceddialog = True else: message = "Initial sync" - forceddialog = True window('emby_initialScan', value="true") - pDialog = self.progressDialog("%s" % message, forced=forceddialog) + pDialog = self.progressDialog("%s" % message) starttotal = datetime.now() # Set views @@ -318,13 +315,14 @@ class LibrarySync(threading.Thread): utils.setScreensaver(value=screensaver) window('emby_dbScan', clear=True) window('emby_initialScan', clear=True) - if forceddialog: - xbmcgui.Dialog().notification( - heading=lang(29999), - message="%s %s %s" % - (message, lang(33025), str(elapsedtotal).split('.')[0]), - icon="special://home/addons/plugin.video.emby/icon.png", - sound=False) + + xbmcgui.Dialog().notification( + heading=lang(29999), + message="%s %s %s" % + (message, lang(33025), str(elapsedtotal).split('.')[0]), + icon="special://home/addons/plugin.video.emby/icon.png", + sound=False) + return True @@ -806,11 +804,14 @@ class LibrarySync(threading.Thread): self.forceLibraryUpdate = True update_embydb = True - if self.addedItems or self.updateItems or self.userdataItems or self.removeItems: + incSyncIndicator = int(settings('incSyncIndicator')) + totalUpdates = len(self.addedItems) + len(self.updateItems) + len(self.userdataItems) + len(self.removeItems) + log("incSyncIndicator=" + str(incSyncIndicator) + " totalUpdates=" + str(totalUpdates), 1) + + if incSyncIndicator != -1 and totalUpdates > incSyncIndicator: # Only present dialog if we are going to process items pDialog = self.progressDialog('Incremental sync') - process = { 'added': self.addedItems, @@ -1018,9 +1019,9 @@ class ManualSync(LibrarySync): LibrarySync.__init__(self) - def sync(self, dialog=False): + def sync(self): - return self.fullSync(manualrun=True, forceddialog=dialog) + return self.fullSync(manualrun=True) def movies(self, embycursor, kodicursor, pdialog): diff --git a/resources/settings.xml b/resources/settings.xml index 7ca31f61..5a25f3b1 100644 --- a/resources/settings.xml +++ b/resources/settings.xml @@ -26,11 +26,10 @@ - - + - + From 1711cdec417171b88a52f6786109043eaab3d616 Mon Sep 17 00:00:00 2001 From: shaun Date: Sun, 3 Jul 2016 13:29:01 +1000 Subject: [PATCH 044/103] bump version --- addon.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addon.xml b/addon.xml index 6048b89d..159a7a34 100644 --- a/addon.xml +++ b/addon.xml @@ -1,7 +1,7 @@ From 01251001e06111e202ce3958d2924af1eb6d0858 Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Sat, 2 Jul 2016 23:36:43 -0500 Subject: [PATCH 045/103] Create CONTRIBUTING.md --- CONTRIBUTING.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..c14e4450 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,7 @@ +# How to contribute + +Thanks you for contributing to Emby for Kodi! + +* Please make pull requests towards the **develop** branch, not the master branch. +* Try to keep lines shorter than 100 to keep things clean and readable. +* Add comments if necessary. From 4ca97b8a22923d9d02fbbe362a2b0e8421206fce Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Sat, 2 Jul 2016 23:39:06 -0500 Subject: [PATCH 046/103] Remove unused import --- service.py | 1 - 1 file changed, 1 deletion(-) diff --git a/service.py b/service.py index 311e00a9..ae7b1f8e 100644 --- a/service.py +++ b/service.py @@ -10,7 +10,6 @@ from datetime import datetime import xbmc import xbmcaddon import xbmcgui -import _strptime import xbmcvfs ################################################################################################# From af85505a99ab01924579d6aedb9fcd4ce4718474 Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Sat, 2 Jul 2016 23:40:55 -0500 Subject: [PATCH 047/103] Translation spanish --- resources/language/Spanish/strings.xml | 329 +++++++++++++++++++++++++ 1 file changed, 329 insertions(+) create mode 100644 resources/language/Spanish/strings.xml diff --git a/resources/language/Spanish/strings.xml b/resources/language/Spanish/strings.xml new file mode 100644 index 00000000..c543b734 --- /dev/null +++ b/resources/language/Spanish/strings.xml @@ -0,0 +1,329 @@ + + + + Emby para Kodi + Dirección Primaria del Servidor + + Reproducir desde HTTP en vez de desde SMB + + Nivel de bitácora + + Nombre de Dispositivo + + Avanzado + Usuario + + Puerto + + Cantidad de Álbumes recientes a mostrar: + Cantidad de Películas recientes a mostrar: + Cantidad de Episodios recientes a mostrar: + Refrescar + Eliminar + Usuario/Contraseña incorrectos + Usuario no encontrado + Eliminando + Esperando a eliminación en servidor + Clasificar por + Ninguno + Acción + Aventuras + Animación + Crimen + Comedia + Documental + Drama + Fantasía + Extranjera + Historia + Horror + Música + Musical + Misterio + Romance + Ciencia Ficción + Corto + Suspenso + Thriller + Oeste + Filtro por Género + Confirmar eliminación de archivo + + Marcar como visto + Marcar como no visto + Clasificar por + Orden de clasificación Descendente + Orden de clasificación Ascendente + + Reanudar + Reanudar desde + Iniciar desde el principio + Ofrecer eliminar luego de reproducción + + Para Episodios + + Para Películas + + Añadir porcentaje de reanudar + Añadir Número de Episodio + Mostrar Progreso de Carga + Cargando Contenido + Obteniendo Datos + Completado + Advertencia + + Error + Buscar + Activar Imágenes Avanzadas (como CoverArt) + + Metadatos + Arte + Calidad de Vídeo + + Reproducción Directa + + Transcodificando + Detección de Servidor Exitosa + Servidor Encontrado + Dirección: + + Series de TV Añadidos Recientemente + + Series de TV En Progreso + + Toda la Música + Canales + + Películas Añadidas Recientemente + + Episodios Añadidos Recientemente + + Álbumes Añadidos Recientemente + Películas En Progreso + + Episodios En Progreso + + Próximos Episodios + + Películas Favoritas + + Series de TV Favoritas + + Episodios Favoritos + Álbumes Reproducidos Frecuentemente + Series a Estrenar Próximamente + Sagas + Tráilers + Videoclips + Fotos + Películas No Vistas + + Géneros de Películas + Estudios de Películas + Actores de Películas + Episodios No Vistos + Géneros de Series de TV + Cadenas de TV + Actores de Series + Listas de Reproducción + Establecer Vistas + Seleccionar Usuario + + No se puede conectar con servidor + Canciones + Álbumes + Artistas de Álbumes + Artistas + Géneros Musicales + Últimos + En Progreso + NextUp + Vistas de Usuario + Reportar Métricas + Películas Aleatorias + Episodios Aleatorios + Ítems Aleatorios + + Ítems Recomendados + + Extras + + Sincronizar Música de Tema + Sincronizar Extra Fanart + Sincronizar Sagas + Restablecer Base de datos local de Kodi + + Activar HTTPS + + Forzar Transcodificación de Códecs + Activar mensaje de conexión con servidor al inicio + + Vídeos Caseros Añadidos Recientemente + + Fotos Añadidas Recientemente + + Vídeos Caseros Favoritos + + Fotos Favoritas + + Álbumes Favoritos + Videoclips Añadidos Recientemente + + Videoclips En Progreso + + Videoclips No Vistos + + + Activo + Limpiar Configuración + Películas + Sagas + Tráilers + Series de TV + Temporadas + Episodios + Artistas de Música + Álbumes + Videoclips + Canciones + Canales + + Opciones Emby + Añadir a favoritos de Emby + Eliminar de favoritos de Emby + Establecer valoración personalizada para canción + Ajustes de complemento Emby + Eliminar ítem del servidor + Refrescar este ítem + Establecer valoración personalizada de canción (0-5) + + Verifica Certificado SSL del Host + Certificado SSL del Cliente + Usar dirección alterna + Dirección Alterna de Servidor + Usar Nombre alterno de dispositivo + [COLOR yellow]Reintentar acceso[/COLOR] + Opciones de Sincronización + Mostrar Progreso de sincronización + Sincronizar Series de TV vacías + Activar Discoteca + Transmitir directo la discoteca + Modo de Reproducción + Forzar Guardado local (caché) de Arte + Limitar hilos de guardado local de arte (recomendado para rpi) + Activar inicio rápido (requiere plugin en el servidor) + Cantidad máxima de ítems a solicitar al servidor al mismo tiempo + Reproducción + Credenciales de Red + Activar modo Emby cinema + Preguntar si reproducir tráilers + Obviar confirmación de eliminación de Emby en menú de contexto (usar a su propio riesgo) + Intervalo de Salto atrás al reanudar (en segundos) + Forzar transcodificar H.265 + Opciones de metadatos de Música (no compatible con transmisión directa) + Importar valoración de canciones directamente desde archivos + Convertir valoración de canciones a valoración Emby + Permitir actualización de valoración de canciones en los archivos + Ignorar especiales en próximos episodios + Usuarios permanentes a incluir en la sesión + Retraso en Inicio (en segundos) + Activar mensaje de reinicio del servidor + Activar notificación de nuevo contenido + Duración del popup para la videoteca (en segundos) + Duración del popup para la discoteca (en segundos) + Mensajes del Servidor + Generar un nuevo Id de dispositivo + Sincronizar cuando salvapantallas esté desactivado + Forzar Transcodificación de Hi10P + Desactivado + + Bienvenido(a) + Error de conexión + Servidor no disponible + Servidor está en línea + Ítems añadidos a lista de reproducción + Ítems encolados a lista de reproducción + El servidor está reiniciando + Acceso está activo + Introduzca contraseña para usuario: + Usuario o contraseña inválidos + Demasiados fallos de autenticación + No es posible reproducción directa + Reproducción directa falló 3 veces. Activando reproducción desde HTTP. + Elegir pista de audio + Elegir pista de subtítulos + ¿Elimiar archivo de sus servidor de Emby? + ¿Reproducir tráilers? + Obteniendo películas desde: + Obteniendo sagas + Obteniendo videoclips desde: + Obteniendo series de tv desde: + Obteniendo: + Se detectó que la base de datos debe ser re-creada para esta versión de Emby para Kodi. ¿Proceder? + Emby para Kodi podría funcionar incorrectamente hasta que la base de datos sea restablecida. + Cancelando el proceso de sincronización de la base de datos. La versión actual de Kodi no está soportada. + completado en: + Comparando películas desde: + Comparando sagas + Comparando videoclips desde: + Comparando series de tv desde: + Comparando episodios desde: + Comparando: + Falló la generación de un nuevo Id de dispositivo. Ver sus bitácoras para más información. + Se ha generado un nuevo Id de dispositivo. Kodi reinicará ahora. + ¿Proceder con el servidor a continuación? + ¡Cuidado! Si selecciona modo Nativo, algunas funciones de Emby no estarán presentes, tales como: modo Emby cinema, opciones de reproducción directa/transcodificación y calendario de acceso parental. + Complemento (Predeterminado) + Nativo (Rutas Directas) + ¿Añadir credenciales de red para permitir a Kodi acceder a su contenido? Importante: Kodi necesitará ser reiniciado para ver las credenciales. Las mismas también pueden añadirse en otro momento. + ¿Desactivar la discoteca Emby? + ¿Transmisión Directa de la discoteca? Seleccione esta opción si habrá acceso remoto a la discoteca. + ¿Eliminar archivo(s) del servidor Emby? ¡Esto también eliminará el(los) archivo(s) de su disco! + La ejecución del proceso de guardado local en caché pueder tomar un tiempo. ¿Continuar? + Sincronización de caché de Arte + ¿Restablecer caché de Arte existente? + Actualizando caché de arte: + Esperando a que todos los hilos terminen: + Kodi no puede localizar el archivo: + Puede que necesite verificar sus credenciales de red en los ajustes del complemento, o utilizar la sustitución de rutas Emby para formatear correctamente su ruta (Cuadro de Mando Emby > Biblioteca). ¿Detener la sincronización? + Añadido: + Si falla demasiadas veces en iniciar la sesión, el servidor Emby puede bloquear su cuenta. ¿Proceder de todos modos? + Canales de TV En Vivo (experimental) + Grabaciones de TV En Vivo (experimental) + Ajustes + Incluir usuario en la sesión + Refrescar listas de reproducción/nodos de Vídeo Emby + Realizar sincronización manual + Reparar la abse de datos local (forzar la actualización de todo el contenido) + Realizar restablecimiento de base de datos local + Guardar localmente en caché todo el arte + Sincronizar Media de temas Emby con Kodi + Incluir/Eliminar usuarios de la sesión + Incluir usuario + Eliminar usuario + Eliminar usuario de la sesión + ¡Éxito! + Eliminado de la sesión: + Incluido en la sesión: + No fue posible incluir/eliminar usuario de la sesión + La tarea completó exitosamente + La tarea falló + Transmisión Directa + Método de reproducción para sus temas + El archivo de configuración no existe en TV Tunes. Cambie un ajuste y ejecute la tarea de nuevo. + ¿Está seguro(a) que quiere restablecer su base de datos local de Kodi? + Modificar/Eliminar credenciales de red + Modificar + Eliminar + Eliminadas: + Introduzca el usuario de red + Introduzca la contraseña de red + Se añadieron credenciales de red para: + Introduzca el nombre del servidor o la dirección IP según se indica en sus rutas de biblioteca Emby. Por ejemplo, el nombre de servidor en la ruta \\\\SERVER-PC\\path\\ es \"SERVER-PC\" + Modificar el nombre de servidor o la dirección IP + Introduzca el nombre del servidor o la dirección IP + No pudo restablecerse la base de datos. Intente nuevamente. + ¿Remover todo el arte del caché? + ¿Restablecer todos los ajustes del complemento Emby? + Restablecimiento de la base de datos completado. Kodi reiniciará ahora para aplicar los cambios. + From 6c8a95fabb998780b923578922f08e65c10e3b0a Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Sat, 2 Jul 2016 23:41:42 -0500 Subject: [PATCH 048/103] Fix errors --- resources/lib/entrypoint.py | 4 ++-- resources/lib/musicutils.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/resources/lib/entrypoint.py b/resources/lib/entrypoint.py index 14e08196..ba4ffe22 100644 --- a/resources/lib/entrypoint.py +++ b/resources/lib/entrypoint.py @@ -163,9 +163,9 @@ def deleteItem(): embycursor.close() try: - embyid = item[0] + itemId = item[0] except TypeError: - log("Unknown embyId, unable to proceed.", 1) + log("Unknown itemId, unable to proceed.", 1) return if settings('skipContextMenu') != "true": diff --git a/resources/lib/musicutils.py b/resources/lib/musicutils.py index 6ff43e01..b89c12da 100644 --- a/resources/lib/musicutils.py +++ b/resources/lib/musicutils.py @@ -222,7 +222,7 @@ def getSongTags(file): except Exception as e: #file in use ? - log("Exception in getSongTags", str(e),0) + log("Exception in getSongTags %s" % e,0) rating = None #remove tempfile if needed.... From e7f5b452c1052d3b11d4f54c6ba150ec16209e7f Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Sat, 2 Jul 2016 23:45:15 -0500 Subject: [PATCH 049/103] Version bump 2.2.14 --- addon.xml | 2 +- changelog.txt | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/addon.xml b/addon.xml index 159a7a34..1cd6d787 100644 --- a/addon.xml +++ b/addon.xml @@ -1,7 +1,7 @@ diff --git a/changelog.txt b/changelog.txt index 197becbb..e600959b 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,11 @@ +version 2.2.14 +- Progress dialog always shows for full sync +- Add (if item count greater than) to options for inc sync progress dialog display +- Limit artwork loading to 25 threads by default +- Fix delete option +- Fix music log error +- Add string translation (Spanish) + version 2.2.12 - Preparation for Emby connect - Add string translation (Dutch, Russian and Swedish) From 5aca32c341d5b72d4eda87c9e5ed2e0454abd5b1 Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Sun, 3 Jul 2016 04:32:06 -0500 Subject: [PATCH 050/103] Prep for emby connect xml, image, qrcode --- resources/language/English/strings.xml | 14 ++ resources/lib/dialog/__init__.py | 1 + .../1080i/script-emby-connect-login.xml | 180 ++++++++++++++++++ .../skins/default/media/emby-bg-fade.png | Bin 0 -> 194 bytes resources/skins/default/media/logo-white.png | Bin 0 -> 2078 bytes .../skins/default/media/qrcode_disclaimer.png | Bin 0 -> 7694 bytes 6 files changed, 195 insertions(+) create mode 100644 resources/lib/dialog/__init__.py create mode 100644 resources/skins/default/1080i/script-emby-connect-login.xml create mode 100644 resources/skins/default/media/emby-bg-fade.png create mode 100644 resources/skins/default/media/logo-white.png create mode 100644 resources/skins/default/media/qrcode_disclaimer.png diff --git a/resources/language/English/strings.xml b/resources/language/English/strings.xml index d3398f18..b0ee4eb1 100644 --- a/resources/language/English/strings.xml +++ b/resources/language/English/strings.xml @@ -223,6 +223,20 @@ Sync when screensaver is deactivated Force Transcode Hi10P Disabled + Login + Manual login + Emby Connect + Server + Username or email + + + Sign in with Emby Connect + Username or email: + Password: + Please see our terms of use. The use of any Emby software constitutes acceptance of these terms. + Scan me + Sign in + Remind me later Welcome diff --git a/resources/lib/dialog/__init__.py b/resources/lib/dialog/__init__.py new file mode 100644 index 00000000..b93054b3 --- /dev/null +++ b/resources/lib/dialog/__init__.py @@ -0,0 +1 @@ +# Dummy file to make this directory a package. diff --git a/resources/skins/default/1080i/script-emby-connect-login.xml b/resources/skins/default/1080i/script-emby-connect-login.xml new file mode 100644 index 00000000..3d755222 --- /dev/null +++ b/resources/skins/default/1080i/script-emby-connect-login.xml @@ -0,0 +1,180 @@ + + + 100 + 0 + dialogeffect + + + Background fade + 100% + 100% + emby-bg-fade.png + + + + 600 + 33% + 15% + + Background box + box.png + 600 + 700 + + + + Emby logo + logo-white.png + 160 + 49 + 20 + 25 + + + + 500 + 50 + + Sign in emby connect + + white + font12 + top + 115 + + + + 190 + + Username email + + ffa6a6a6 + font10 + top + + + + Username field + box.png + box.png + User + 100% + 40 + ff464646 + font10 + 25 + 101 + + + + separator + 102% + 0.5 + 66 + -10 + separator.png + + + + + Password + 275 + + Password label + + ffa6a6a6 + font10 + top + + + + Password field + box.png + box.png + Password + 100% + 40 + ff464646 + font10 + 25 + 200 + 100 + + + + separator + 102% + 0.5 + 66 + -10 + separator.png + + + + + Buttons + 365 + + Sign in + box.png + box.png + + font10 + center + 100% + 50 + 201 + 101 + + + + Later + box.png + box.png + + font10 + center + 100% + 50 + 55 + 200 + + + + + Disclaimer + 490 + + Disclaimer label + + font10 + ff464646 + true + top + 340 + + + + qrcode + qrcode_disclaimer.png + 140 + 140 + 10 + 360 + + + + Scan me + + font12 + green + right + top + 151 + + + + + + + \ No newline at end of file diff --git a/resources/skins/default/media/emby-bg-fade.png b/resources/skins/default/media/emby-bg-fade.png new file mode 100644 index 0000000000000000000000000000000000000000..3374bee9b5533e2b233d12bfa77951264978e1ad GIT binary patch literal 194 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnF3?v&v(vJfv#Q>iWS0IfakNUMJx@>1_xt_*^E=NupYxp8dB4y3Jm-(|iFYK}0i{)? z0RR9HheeaN=C-Y2At}BUCq`cF1OUV+j(9R=Yq{}18mY=*Yv)J&yW}7F7W}pUYX71C zi|ZfZ-|zaB{2!JdY5X+)%;6{bi(qqe)4IfrTz%|(y$7`LAiffPn5}D9W=Z5?%{WHS zXBzw(FIYT2@_t1a-0Xk1mvOV>s!OfY$8U>$!XEZ$LBUAg{oyRnIye5(*x08x{lZ?> zK=Oy>9Z_3v?fM6aa2#Nt;jwmO81(s-inM4^rVJK)Z12W9abpru30RcK*gMUMS`>_S z`<;|n7#wJ+L5RZH4NYsY$X^ly@DsNTU>Piq&kvAv+yrl*7m zeef_n6u+(q7NlGZ93EWaNZz5h*YDk>FWg36*9B8qGA<{esKz4j9YeRAv1kSnvq%f4 zj0lc1zng%&Tk`cxjnp#sbl(0$NAK+zjjXStQ2-#*a=6?sS%awZoVMan&a*;9-imnb}{)I{;xR0Z|J=xgAk|qlBf#bd$&<*F(~uv zK(JB8{kX9_;dGalSQ&9Xx*3ss+L<0Y&XxUYIuRLB>E);KQtN8Q?ykl{-qH5Llo&Bl zR??|Nbd*%94b1(!!rV8d?KKMp0X37}Lc#3pY}!Wl&8GB6%S^;0V^9)ZY{UMfO7WKf zGr)ctT#Ueba^w6#^)v%ae#aYPX-G}IP)kLd@u>^1V~LXj`OmWB^e$W7cI@4~TTj%% zN_R#z$3{#XvF9<`cUIPt834cY)nB=~fe1dIq-i{PS#jDrL~Q_V-1&qal;ni^=6S%{ zY*5}vnngW`hWHe-jBw=%?t>16?kefN4^|vuC!QuEI!PYN>4hs6mYq-$K+zS`oc;Mu zdQ+bKWCXP|IpLx)#y7S*FFgsJYK@a`k%kZ^#_wWYczQw1m4ko~vyF_eaOiL6!&u<0 ziK!@EO7}gk?GK!ZTFr-bO7(@&lC_PtAm)8j+CM* z*rBYJeCEOX%}s7Id^)l8h@3wYthkVjMy+{14Q6sVlCi74RWqV-q;HO_D_vo=@B`xl zn{Ich+f*kp#)?}7ERL!A{IMQr*NkAaz0oMGRs&Av{w}ld3c)?1^U~SE?o8ddm5SQTtV%GZh zp>?l83DIBVyUfoeSdCUEilx>)jDOnZ{O2xJ^(X=Du}}Re(6rbAMXRfqtxS9PfUIV&8K%>r9;c<1)0@9TqiD&$qY-q~Ct zSFm96h7orbTpnW4fB?6I*(P6k(Ko-Z4}%V%AWe~FeL6X2s2CY@g2{m?dmP`Aw|y9< zEn#?>=j1nAB=g2? z@a%On=?XpEQX4ea1pMs7z+ni7XzYWpql%e!)O_@QxUi4mV@lHnpS+>UpsylHqTZu2ewVbA@0(>F}61&#J?Kd~scW~%O@WcapzVV}qk z3F|x~MMZf{4TNc`oCq)7`YQLxZIAov+GVcYAhK#)ZXb;d7d>dz&7Il*sRz1A)f|Z| zjzwT-t<4_6ctVSNZQsU`N(H_KIt)o13H+>{oBXR5Y}4*-WDpi59^ Gqy7tcn@nr~ literal 0 HcmV?d00001 diff --git a/resources/skins/default/media/qrcode_disclaimer.png b/resources/skins/default/media/qrcode_disclaimer.png new file mode 100644 index 0000000000000000000000000000000000000000..97487571948040689caf0ff518608a8f7d6b38ad GIT binary patch literal 7694 zcmcIp3pA8#+m=I$oH~)?%%~_biDHm*2}S8Z5|zCZLPlXSW|Ev`M%h$ic7)1l3o&74 z7nwIgPBY~+$va6ShgV{TG5WTx{?;UVWH7J$Sg#3F1R~b`IN&9L9creZ`?) zsQ2=$J)YK)Y;()8VWckfb)Z9*(+wc%Sf$hW+&F8ZH~szX&ojdVQ+-|IomU$}GCIlr zbYJtUj(h<>wLKxK`PzXrN8oGoS?Z}B)((FRmOkq*N_rRPobR+h((&C`%b{$i1L=-c z?25-9ey0xCxRyH^dmqZ~$uc-{$nV65yJBJ*s~zoaPDJ*N_Z!OJK!{1&2!utug7g!~ zSG+C;Y%s0N^oEc8FTeie%46mFt~K|BrQ}4?r!`Q@RN#OC^ll-^3tU-a(-YXP|5&G3 zEE4{-3+(^03;n0a^I;JUJ-w^8H_%Cp&*o1wb+G9y z5**8Az=>u@>8UKA0yM>NW)E9H4aIO zH%3KNti}f`f+VaJC0;Gng-gf}B~?4LF?19q9UdV7WclZ~`bjY?fTVksU6sawWIK7> zCNvl=4KH{cY=Jp|Oi;%H#gWKK^BeF2ouxNuBQNsvej|%xuYwl@s@Ir2jF6xX?eqo~ zd+-cAVR&$);89fHuC$X^{SGPhIA?UJ%T*> z?+=~QTz|d5>gu&QD+TDoS9bUuACa&)KFEdZP#ICKfCV~+1b;*@bk{XnD;}66awUI5 z#z-kY_*n0QHtnKiGcfLoK{BiT2prQ#^@s1#+(Zrw1dbNk$m6~b7{e`*lf4=P zR)_shoi~%}D;_H+=UcX@{_wJqr^1vvVDMS^I$(PEXtEv65cO zrNGyjKgF$4^=T6PT)3m&B#m3zv)qcv5TETU-SfTmmZ-lH>Z9*3cN4h=ywC7^yE@gZT6VyINa+}L=y;dZGJ+3V2|WLA zHQ=P6<~ZZmPW(#C`)35NjaH6ACQ7dGyEIZNdSQ_5H2YE->OKac;`7AR2LsQXxapyJ zyU9BI4j3Kz@f7w%3-8Y?T`a^z zL(Yp1ce_W8_nnSe4KcJMI^dXV+7aP&2`r*~3ojheH+e%H;2xOJH|*?|2V&|^f$afp zj}O54a5+FtW}<%!K^nC7*2bFrkx4$pTF`(uqJf-~$&1+(E3L=cuR{^)M!!Mg*&-*$^*$XTfar$gVBq&~4c`{eLsNU=tYg%LbW6hW>fur6O)nqJHuh=UsDEG_ zzxY0B!nw-3?5qM<{dgBBNKd>E?Dh%b%>}X~aNGQ4c*CZ$r;IK#nt9~-k@$T2(=89VE;|r$-`(~}Z?fPsi&ytEAUl<g^&37$5Duk%%6TX>;QXJ{RFmW_B-n;|WM zGvrzA`O&-eSLBN1!c^Am+|kj&mjYA`+~OjK{-P%JYP{TW>#C_#UgOp%4
e8p0kohpx1eD6@@mT0qJX4iLMGh+~pGV85Q8+ZF>*mY?7ryd^yD zwAk9vC+k;Z_Xh{v!B?uToBQ_Vq4%d82-7yUN*3^$@(bU$;vT@LpBBeO0XcG_oJ4A$ zVPL)WkUCTOc@5)jP;9MW#!ML_gM3y49om0+a=LtlQ_J&qy*u0i5ByfV9&(l zCJyeC6qHFecW!wy!_x36gE|ine$DDRZ=q(zt~>1)1H;=+=o?iK?~unjew)Ag+frVD zL&#zy$Gfj2>`z(nJA&=mRs!hm(iOVx-BEh~N9(t%Zy?TKZ^QMP~B;|r8go(ipQhcbuxa`JnOx6Hu?H_!IVL*dj{PM-jbwNIsrS}nw*5yEm3s; zXVNE*$%F2zAYvRoaCG$bZv&`j`yNYL6tTvVrGRpAP`HMwcBorD?TKecuf^{zz8JDl zhAdPA+f`p~&9gMkQwPv+@XdiX*3RY)Y0nN@F8==Vv;+@hog0r2Z7qshWfjbHEr;Dk z#c-++hcOLX!eLAHUNXAS-osH>Yl(YX!mzD4`gIA#1FqsyO#XwA!~L32?@aD!ujP5B zYuWRu@`-KvGg1&Xy*!uVQXJh?Lh&{UagVsu$rH!D0^B=dnTL}q3D*US7iXjPnE4>k zTc{S9dN~%{B34w3k;tG*)pe!JcIy$Bs$}kp!af3=B#jX6JOgP^ec{vp*1JD@LgnJ&aoH}JmzJJ|n9~u|OJTg3?(kA83-9kgIX5TD zZ7sur)|16d`W!T?|DHeQJFl7Y`$939qz++wC!oo}91EhkLKT63$UK_5yrBkhcE09F zK%d**Zj*Ae#0`wKN)7tN;d{tvWkXLbvTO3&LDHne4vLv-#B2iAX3ia{Wdw3|HgkNp zp{;R-JA69CcTo0yo-OiKx=P#xDcPlYziGM@T>|Rf@UH$6li{kCw2vJv^O7#Zi$q#^ zAAQ&=4SFy_H=YdCJjA=ziSZMv5HKRtU0~~h$M7cC(4(XCfwY_D`K!&mmuO`XYK5(L z8NQ^o97!p0pxx_4a{scY+tR$0+*4cG)@iX4o->VG2zgxBz_(O-{w=RAWs7s;hk z+FwV)xgkTgV64(8P>ORZCCO?P#tvsS#oHQ4V|TkWn`W47_vYr$H)S0O%ss_bv#gVk z_gSwdLsbEa9NHsMjLX-$_@=DtB!<>xK@Z>5^o<(95k_}Il&jP*&(_e-R$I0szj8k6 z7ce7kg-JJ}ljP#O)_|n6HJtm~zm?4v|5;udIlb=@vnYAqAl^q2hF%ABl8k8UuA@uo zI(d6tpUghZAI@ro8HqzmW$L#}i?YSWrynGDe5aZmiwf_5~m-8%AS7BsdYY{jv+e zN;-i%*KTUc9&*fpE1j#1{jsy%6Pw!Ra`jfC-d=|1>S3unXS6-T#A{WKm|n(%vA6vc z`v;0P?%N2Jkn`Q2)@~=ohNHGPd(kj^z&AgO6jRh-@7VM4wMt1OT5^@n&_GtodHA4f zFm?F%O?swM=~e}Cm$S8rGNI1n`JNYW2QTKI8p-FR4PU2ve2wBdGiX2lo&e%`~s|YIz9Pg*OG|O+wn3Oh(Dc{Zm7? z3-<@7`Wb_Xa}@6~q36SVRB{Kw)t-S0n0O;0AllG4kjD4h12_7=)W+1G(I7^-uH}vs zntFDC!foKD5lcyn)YVTh&!@q@m(5LCeluy5cY91f3PM8%EW>1Y#I=~o(W{^ELDK?u zXZo8-Rc!=&^TWmD)Ws?LR-xXBzM^e?d&d_(2kZ!#*o4Kv%^$g;z&DHH!euyAeTl6i zXbAE$_Rfi#_El|u-}wAvPb*7JqPa%iET->`+lK)?xL>75zz^?Zp&oeav&630D%z@% zt}ot5WpRv(^wvlWH)v-mTQ6^~R^F_8K`qrC_|>{SYVgL6niomc1Xd!mzJ|##GYEn6 zE0G&H=AWxovwA$~QTBmD$GK>yn6PrP^NsrJMEoEj`<5r2s9%2FU+~85Gn`r+LCH1R z))WY>swUv;g#sI`Qo8Pe_6FNrEo?Y1@XDS`fg=VP^7+wkM&4`(< zZtWl#oFl#ZS3<#5U8+_lk9& zcF1t&x_a-=o@18^ajGVBcQ%(?a7~gXW{{OBE~Q2{dca;jhs(P$$am;Am*LUe0GvJ( zYfCNh6dF7TWfN;GRosEOOxt$f`&b7|^-^|(tg74BlN+IUk>~N;T#p+~kb*G(c5 z%eOOYw42}RQ@J`u>*;JqQN%{55ca&>km=XOkFc`P{HaJF5xkHg`f=(fFY9(Zn$@9f zHXcVVo-EHJn`GJ2wzO)reh(BRbR?#3`JqTC8Ij@16_7(nQk|2!X;iJpH#84);bQbG^N(JOe4cSJbZYhZ(vvHo6oBn65t>$qV z-cp4tQT-s7j=R1^WIao>)jTPKYy3d5l7BRdJ_W~XRix>GQBGzaZ%w-8pS*pmPh~O` z!et-hg=?DMoM2OxYp6V8thz?4{!=ESv3XL;jbOc~pBNg4uP4Vsw>-5-57xlWx~Z_G ziL9(s`HQFUa}a_fZGXBU+14@`&~f$iuOjd+Y(B)ZIi>>F^BxkNJXtyHNp#yEfciB@ z*4GQEWdeR}l@5pwZobRRa)`L~n<&tRx4V>-uv^-Va4v&UEBdftgCmxbMrm#0JFK?0 zcri9X({}PL3juH!C&pxGZ5;DL!0_`(X%742n--}c{k>R{18^{{JT zbmKkq8!br#a#-9%_X%#%h-pZvLS*I!sulvpEL50z{AW3x zZd=OUd#_D9oe^nQW|CPXMQFHJy1IO}ZgZ8l4x|zRjNIlp##s3rZoIr#^96I#v!{cs zh)cBskMSttbZ^PG4E31bPs@r^688yUfG}SBhT=EdJ_9T5md>5uLVkBn8sw% zXb<9Zpk6%L)29yqiB!KHm+A_VlLv<^OZgP3lD+0c)%H{%sCwpO(kvMEh*xVpkpKPY8HCbG+k>=dKP?f!nf5l z6&=V930(lH7c456S#>h$TI#)dz^JDcBrL489#5>-$L?<>eTR|; zHlxt+e*h`{Z!e*UoNpJ@-w)M;i{_G!$y!QC4dmD!nx)Aap8RJY`gZt@p{i zMe8hL=P9NlH6D0HZaqz-w@!w^`Q*}Lk>$_Yus&JrZqszGttO#zQbVyKCS4m9 z!t~D~+IBdC_GyV0w7d5zT6M4}2YUmP%+#}%=^HR8?6KUxY-Mu9G6M>bq^?BWx5#^m zUq-8ox4OH6+c!1(lC(jDN%F%rhdmTI;%1GCLQz^WN_-Zr*^^8|Q-%8?Myn6|Nzl6v&Gio$CL{ zlz$aq=G+~P)VkSwHM4NR)>5)~O#jGS9DdEI?$Zw@WO<4ug_XvG$`I1yh@bz!T*69u_A(Dx=!41qg0sixWm(# zZPvyL#uGs!k#3%{7?7%ALoh^$&KqWa)8gmv+~lr3&ZOs-(_|cxT@(w8i(y&avN{6ho$Hl7gE=7_?WK7 zihvy}nzbyK-8>pe`A6CazOALi-}f>3BM!g%?>s|evb=SFUsQd!GPLDy0z6^{;hxUY z@Hc8fK5oTBHq2TcXP~^2O1!5_ootue8n>LWUaZ Date: Sun, 3 Jul 2016 22:24:39 -0500 Subject: [PATCH 051/103] Emby connect - login dialog --- resources/lib/dialog/loginconnect.py | 82 ++++++++++++++++++ .../1080i/script-emby-connect-login.xml | 37 ++------ .../skins/default/media/button-focus.png | Bin 0 -> 1023 bytes 3 files changed, 87 insertions(+), 32 deletions(-) create mode 100644 resources/lib/dialog/loginconnect.py create mode 100644 resources/skins/default/media/button-focus.png diff --git a/resources/lib/dialog/loginconnect.py b/resources/lib/dialog/loginconnect.py new file mode 100644 index 00000000..4b35946e --- /dev/null +++ b/resources/lib/dialog/loginconnect.py @@ -0,0 +1,82 @@ +# -*- coding: utf-8 -*- + +################################################################################################## + +import os + +import xbmcgui + +import userclient + +import sys +import xbmc +import xbmcaddon +import json as json +import urllib + +################################################################################################## + +addon = xbmcaddon.Addon('plugin.video.emby') + +ACTION_BACK = 92 +SIGN_IN = 200 +REMIND_LATER = 201 + + +class LoginConnect(xbmcgui.WindowXMLDialog): + + + def __init__(self, *args, **kwargs): + + self.UserClient = userclient.UserClient() + xbmcgui.WindowXMLDialog.__init__(self, *args, **kwargs) + + def __add_editcontrol(self, x, y, height, width, password=0): + + media = os.path.join(addon.getAddonInfo('path'), 'resources', 'skins', 'default', 'media') + control = xbmcgui.ControlEdit(0,0,0,0, + label="User", + font="font10", + textColor="ff464646", + focusTexture=os.path.join(media, "button-focus.png"), + noFocusTexture=os.path.join(media, "button-focus.png"), + isPassword=password) + + control.setPosition(x,y) + control.setHeight(height) + control.setWidth(width) + + self.addControl(control) + return control + + def onInit(self): + + self.user_field = self.__add_editcontrol(685,385,40,500) + self.setFocus(self.user_field) + self.password_field = self.__add_editcontrol(685,470,40,500, password=1) + self.signin_button = self.getControl(SIGN_IN) + + self.user_field.controlDown(self.password_field) + self.user_field.controlRight(self.password_field) + self.password_field.controlUp(self.user_field) + self.password_field.controlDown(self.signin_button) + self.password_field.controlRight(self.signin_button) + self.signin_button.controlUp(self.password_field) + + def onClick(self, control): + + if control == SIGN_IN: + # Sign in to emby connect + self.user = self.user_field.getText() + __password = self.password_field.getText() + + ### REVIEW ONCE CONNECT MODULE IS MADE + + elif control == REMIND_LATER: + # Remind me later + self.close() + + def onAction(self, action): + + if action == ACTION_BACK: + self.close() \ No newline at end of file diff --git a/resources/skins/default/1080i/script-emby-connect-login.xml b/resources/skins/default/1080i/script-emby-connect-login.xml index 3d755222..b5b01632 100644 --- a/resources/skins/default/1080i/script-emby-connect-login.xml +++ b/resources/skins/default/1080i/script-emby-connect-login.xml @@ -1,6 +1,6 @@ - 100 + 200 0 dialogeffect @@ -53,19 +53,6 @@ top - - Username field - box.png - box.png - User - 100% - 40 - ff464646 - font10 - 25 - 101 - - separator 102% @@ -86,20 +73,6 @@ font10 top - - - Password field - box.png - box.png - Password - 100% - 40 - ff464646 - font10 - 25 - 200 - 100 - separator @@ -113,7 +86,7 @@ Buttons - 365 + 385 Sign in box.png @@ -124,7 +97,6 @@ 100% 50 201 - 101 @@ -143,7 +115,7 @@ Disclaimer - 490 + 510 Disclaimer label @@ -170,7 +142,8 @@ green right top - 151 + 120 + 160 diff --git a/resources/skins/default/media/button-focus.png b/resources/skins/default/media/button-focus.png new file mode 100644 index 0000000000000000000000000000000000000000..d1427a2c0fa4e14f4311776d9a878fad7ec7ebcf GIT binary patch literal 1023 zcmbVLy>8P$95;$op`uQ(PziBzK>`W;e74W2t(wr!CNciL0xT$@veIafq z%GLoko`9tjFTj8p7`Pc`mEo6W(72s zy%HlAamv|(u+k_}FBlRAe6klMw)peoClN%UE$-m9(oP*Rh&GNhvVFYS3yz0@8H$a^ zV5P9QftWB46!AflTZJu7>{@(ZUPBQ~AZ%!h)1dn84sd8j0G6>7C`bo&Q$`r+%jgkM z6{JB$gGiH*YAL3LFql0e_m+jbR@Ysh`Qj&A959wz5RS)Vd92Aa+k?n7O{l0)RV9v) z@}q=#g_Pt=l?9jNK^CPfq6sK1dOjU7TjY^WQ;5@c`@(RN&kDs$1`98RNLFARmw8Rl zIqQ-?VO)yNdq*jOU6RvL7Vv)TE>&b+yQ>|QKpu^?l|{TM-hoSlQA`rna&3|Sk;5po zLep0?#SbOl({;%}x+a;5iY3hmu!>DXts_#|xr|>|OubphnvP5Zp{B8>Eo0L$*9-$| z4n}IlZ6!JLl7LjcBkp^~Rj=e)PDVUNvmT`f)f99Fl+k=ZQ{Xt@!A_KfbeumdJ2Xw$ zC0X>6gzFiN!KB1ibcy?aOMS`@|F?9I2L;PH`D<>fD?W_n_Cf{xaG@iTaPeeZCC`VQ zyIf)STJCDE_~!pQKX26VwVSnfpU+O-&%Iq--v4oS;(U4XN~j6ZjoSV12Hq+q*=laO JAJ$&H{tW@9H|zia literal 0 HcmV?d00001 From efdafa28311b90a6a2473a6ce75962987ac47342 Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Sun, 3 Jul 2016 22:25:09 -0500 Subject: [PATCH 052/103] Remove unused import --- resources/lib/dialog/loginconnect.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/resources/lib/dialog/loginconnect.py b/resources/lib/dialog/loginconnect.py index 4b35946e..bc29aea7 100644 --- a/resources/lib/dialog/loginconnect.py +++ b/resources/lib/dialog/loginconnect.py @@ -6,14 +6,6 @@ import os import xbmcgui -import userclient - -import sys -import xbmc -import xbmcaddon -import json as json -import urllib - ################################################################################################## addon = xbmcaddon.Addon('plugin.video.emby') From 272a955d4d9622aa4c6ceff4f6701ce7d80cc651 Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Sun, 3 Jul 2016 22:26:10 -0500 Subject: [PATCH 053/103] Remove useless control in dialog --- resources/lib/dialog/loginconnect.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/resources/lib/dialog/loginconnect.py b/resources/lib/dialog/loginconnect.py index bc29aea7..c93b92cb 100644 --- a/resources/lib/dialog/loginconnect.py +++ b/resources/lib/dialog/loginconnect.py @@ -49,10 +49,8 @@ class LoginConnect(xbmcgui.WindowXMLDialog): self.signin_button = self.getControl(SIGN_IN) self.user_field.controlDown(self.password_field) - self.user_field.controlRight(self.password_field) self.password_field.controlUp(self.user_field) self.password_field.controlDown(self.signin_button) - self.password_field.controlRight(self.signin_button) self.signin_button.controlUp(self.password_field) def onClick(self, control): From 0fee1975d4ca98a90ab9b142f992f8c0a969e2d2 Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Sun, 3 Jul 2016 22:45:20 -0500 Subject: [PATCH 054/103] Clean up dialog --- resources/lib/dialog/loginconnect.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/resources/lib/dialog/loginconnect.py b/resources/lib/dialog/loginconnect.py index c93b92cb..62651e4f 100644 --- a/resources/lib/dialog/loginconnect.py +++ b/resources/lib/dialog/loginconnect.py @@ -5,6 +5,7 @@ import os import xbmcgui +import xbmcaddon ################################################################################################## @@ -20,7 +21,6 @@ class LoginConnect(xbmcgui.WindowXMLDialog): def __init__(self, *args, **kwargs): - self.UserClient = userclient.UserClient() xbmcgui.WindowXMLDialog.__init__(self, *args, **kwargs) def __add_editcontrol(self, x, y, height, width, password=0): @@ -47,11 +47,14 @@ class LoginConnect(xbmcgui.WindowXMLDialog): self.setFocus(self.user_field) self.password_field = self.__add_editcontrol(685,470,40,500, password=1) self.signin_button = self.getControl(SIGN_IN) + self.remind_button = self.getControl(REMIND_LATER) + self.user_field.controlUp(self.remind_button) self.user_field.controlDown(self.password_field) self.password_field.controlUp(self.user_field) self.password_field.controlDown(self.signin_button) self.signin_button.controlUp(self.password_field) + self.remind_button.controlDown(self.user_field) def onClick(self, control): @@ -61,6 +64,7 @@ class LoginConnect(xbmcgui.WindowXMLDialog): __password = self.password_field.getText() ### REVIEW ONCE CONNECT MODULE IS MADE + self.close() elif control == REMIND_LATER: # Remind me later From 8f0343de2e49561f5d0faa9bfa1b3964f9a951fe Mon Sep 17 00:00:00 2001 From: shaun Date: Mon, 4 Jul 2016 20:34:58 +1000 Subject: [PATCH 055/103] dont auto fall back to http streaming if not selected Dont fall back to http streaming if direct file playback is expected. --- addon.xml | 2 +- resources/language/English/strings.xml | 2 +- resources/lib/playutils.py | 34 ++++++-------------------- 3 files changed, 10 insertions(+), 28 deletions(-) diff --git a/addon.xml b/addon.xml index 1cd6d787..bfc2a9b4 100644 --- a/addon.xml +++ b/addon.xml @@ -1,7 +1,7 @@ diff --git a/resources/language/English/strings.xml b/resources/language/English/strings.xml index d3398f18..9d583b62 100644 --- a/resources/language/English/strings.xml +++ b/resources/language/English/strings.xml @@ -236,7 +236,7 @@ Enter password for user: Invalid username or password Failed to authenticate too many times - Unable to direct play + Unable to direct play file Direct play failed 3 times. Enabled play from HTTP. Choose the audio stream Choose the subtitles stream diff --git a/resources/lib/playutils.py b/resources/lib/playutils.py index 71c92696..0674be71 100644 --- a/resources/lib/playutils.py +++ b/resources/lib/playutils.py @@ -2,6 +2,8 @@ ################################################################################################# +import sys + import xbmc import xbmcgui import xbmcvfs @@ -84,8 +86,6 @@ class PlayUtils(): def isDirectPlay(self): - dialog = xbmcgui.Dialog() - # Requirement: Filesystem, Accessible path if settings('playFromStream') == "true": # User forcing to play via HTTP @@ -125,30 +125,12 @@ class PlayUtils(): # Verify the path if not self.fileExists(): log("Unable to direct play.", 1) - 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=lang(29999), - 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=lang(29999), - message=lang(33012), - icon="special://home/addons/plugin.video.emby/icon.png", - sound=False) - return False + log(self.directPlay(), 1) + xbmcgui.Dialog().ok( + heading=lang(29999), + line1=lang(33011), + line2=(self.directPlay())) + sys.exit() return True From b868ca0256fa15a00294834aff81c52a5b7b7267 Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Mon, 4 Jul 2016 14:30:11 -0500 Subject: [PATCH 056/103] Update CONTRIBUTING.md --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c14e4450..e365a5a8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -3,5 +3,5 @@ Thanks you for contributing to Emby for Kodi! * Please make pull requests towards the **develop** branch, not the master branch. -* Try to keep lines shorter than 100 to keep things clean and readable. +* Try to keep the maximum line length shorter than 100 characters to keep things clean and readable. * Add comments if necessary. From 213f42f4a5d3be6c0781ab287ed290941b91b0fd Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Fri, 15 Jul 2016 18:01:59 -0500 Subject: [PATCH 057/103] Update downloadUtils requests Code reduction, fix connections not being released. --- resources/lib/downloadutils.py | 117 ++++++++++++--------------------- 1 file changed, 43 insertions(+), 74 deletions(-) diff --git a/resources/lib/downloadutils.py b/resources/lib/downloadutils.py index ea55e7d1..83ff6cba 100644 --- a/resources/lib/downloadutils.py +++ b/resources/lib/downloadutils.py @@ -193,8 +193,6 @@ class DownloadUtils(): 'Accept-Charset': 'UTF-8,*', 'Authorization': auth } - log("Header: %s" % header, 2) - else: userId = self.userId token = self.token @@ -210,44 +208,36 @@ class DownloadUtils(): 'Authorization': auth, 'X-MediaBrowser-Token': token } - log("Header: %s" % header, 2) return header def downloadUrl(self, url, postBody=None, action_type="GET", parameters=None, authenticate=True): - log("=== ENTER downloadUrl ===", 2) - + log("===== ENTER downloadUrl =====", 2) + + session = requests default_link = "" try: - # If user is authenticated + + kwargs = { + 'timeout': self.timeout, + 'json': postBody, + 'params': parameters + } + if authenticate: - # Get requests session - try: - s = self.s - # Replace for the real values - url = url.replace("{server}", self.server) - url = url.replace("{UserId}", self.userId) - # Prepare request - 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: + if self.s is not None: + session = self.s + else: # request session does not exists # Get user information self.userId = window('emby_currUser') self.server = window('emby_server%s' % self.userId) self.token = window('emby_accessToken%s' % self.userId) - header = self.getHeader() verifyssl = False - cert = None # IF user enables ssl verification if settings('sslverify') == "true": @@ -255,38 +245,16 @@ class DownloadUtils(): if settings('sslcert') != "None": verifyssl = settings('sslcert') - # Replace for the real values - url = url.replace("{server}", self.server) - url = url.replace("{UserId}", self.userId) + kwargs['headers'] = self.getHeader() - # Prepare request - if action_type == "GET": - r = requests.get(url, - json=postBody, - params=parameters, - headers=header, - timeout=self.timeout, - verify=verifyssl) + # Replace for the real values + url = url.replace("{server}", self.server) + url = url.replace("{UserId}", self.userId) + kwargs['url'] = url - elif action_type == "POST": - r = requests.post(url, - json=postBody, - headers=header, - timeout=self.timeout, - verify=verifyssl) - - elif action_type == "DELETE": - r = requests.delete(url, - json=postBody, - headers=header, - timeout=self.timeout, - verify=verifyssl) - - # If user is not authenticated - elif not authenticate: - - header = self.getHeader(authenticate=False) - verifyssl = False + else: # User is not authenticated + kwargs['url'] = url + kwargs['headers'] = self.getHeader(authenticate=False) # If user enables ssl verification try: @@ -294,32 +262,21 @@ class DownloadUtils(): if self.sslclient is not None: verifyssl = self.sslclient except AttributeError: - pass - - # Prepare request - if action_type == "GET": - r = requests.get(url, - json=postBody, - params=parameters, - headers=header, - timeout=self.timeout, - verify=verifyssl) - - elif action_type == "POST": - r = requests.post(url, - json=postBody, - headers=header, - timeout=self.timeout, - verify=verifyssl) + verifyssl = False + finally: + kwargs['verify'] = verifyssl ##### THE RESPONSE ##### - log(r.url, 2) + log(kwargs, 2) + r = self.__requests(action_type, session, **kwargs) + if r.status_code == 204: # No body in the response log("====== 204 Success ======", 2) + # Read response to release connection + r.content elif r.status_code == requests.codes.ok: - try: # UNICODE - JSON object r = r.json() @@ -330,7 +287,8 @@ class DownloadUtils(): except: if r.headers.get('content-type') != "text/html": log("Unable to convert the response for: %s" % url, 1) - else: + + else: # Bad status code r.raise_for_status() ##### EXCEPTIONS ##### @@ -393,4 +351,15 @@ class DownloadUtils(): log("Unknown error connecting to: %s" % url, 0) log(e, 1) - return default_link \ No newline at end of file + return default_link + + def __requests(self, action, session=requests, **kwargs): + + if action == "GET": + r = session.get(**kwargs) + elif action == "POST": + r = session.post(**kwargs) + elif action == "DELETE": + r = session.delete(**kwargs) + + return r \ No newline at end of file From 8b83ae8a0082c5710287de77eeffe18ac6a7c243 Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Fri, 15 Jul 2016 19:02:30 -0500 Subject: [PATCH 058/103] Clean up downloadUtils --- resources/lib/downloadutils.py | 34 ++++++++++++++++++++-------------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/resources/lib/downloadutils.py b/resources/lib/downloadutils.py index 83ff6cba..a26a6cd7 100644 --- a/resources/lib/downloadutils.py +++ b/resources/lib/downloadutils.py @@ -11,6 +11,7 @@ import xbmcgui import clientinfo from utils import Logging, window, settings +import utils ################################################################################################## @@ -217,16 +218,10 @@ class DownloadUtils(): log("===== ENTER downloadUrl =====", 2) session = requests + kwargs = {} default_link = "" try: - - kwargs = { - 'timeout': self.timeout, - 'json': postBody, - 'params': parameters - } - if authenticate: if self.s is not None: @@ -245,17 +240,16 @@ class DownloadUtils(): if settings('sslcert') != "None": verifyssl = settings('sslcert') - kwargs['headers'] = self.getHeader() + kwargs.update({ + 'verify': verifyssl, + 'headers': self.getHeader() + }) # Replace for the real values url = url.replace("{server}", self.server) url = url.replace("{UserId}", self.userId) - kwargs['url'] = url else: # User is not authenticated - kwargs['url'] = url - kwargs['headers'] = self.getHeader(authenticate=False) - # If user enables ssl verification try: verifyssl = self.sslverify @@ -263,8 +257,19 @@ class DownloadUtils(): verifyssl = self.sslclient except AttributeError: verifyssl = False - finally: - kwargs['verify'] = verifyssl + + kwargs.update({ + 'verify': verifyssl, + 'headers': self.getHeader(authenticate=False) + }) + + ##### PREPARE REQUEST ##### + kwargs.update({ + 'url': url, + 'timeout': self.timeout, + 'json': postBody, + 'params': parameters + }) ##### THE RESPONSE ##### log(kwargs, 2) @@ -353,6 +358,7 @@ class DownloadUtils(): return default_link + @utils.timeIt def __requests(self, action, session=requests, **kwargs): if action == "GET": From 06d71cf00db0cdf5e8fe74c9b3b860b9ee3024dc Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Fri, 15 Jul 2016 19:17:32 -0500 Subject: [PATCH 059/103] Fix typo --- resources/lib/downloadutils.py | 1 - 1 file changed, 1 deletion(-) diff --git a/resources/lib/downloadutils.py b/resources/lib/downloadutils.py index a26a6cd7..1e104294 100644 --- a/resources/lib/downloadutils.py +++ b/resources/lib/downloadutils.py @@ -358,7 +358,6 @@ class DownloadUtils(): return default_link - @utils.timeIt def __requests(self, action, session=requests, **kwargs): if action == "GET": From da7685b03f7ab1064032f405f5a03e1ffd3936d3 Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Sat, 16 Jul 2016 10:02:50 -0500 Subject: [PATCH 060/103] Try to fix import strptime bug Clean up of downloadutils and utils. --- resources/lib/downloadutils.py | 39 +++++++++++++++------------------ resources/lib/utils.py | 40 +++++++++++++++------------------- 2 files changed, 34 insertions(+), 45 deletions(-) diff --git a/resources/lib/downloadutils.py b/resources/lib/downloadutils.py index 1e104294..34596c2f 100644 --- a/resources/lib/downloadutils.py +++ b/resources/lib/downloadutils.py @@ -11,7 +11,6 @@ import xbmcgui import clientinfo from utils import Logging, window, settings -import utils ################################################################################################## @@ -29,11 +28,10 @@ class DownloadUtils(): # Borg - multiple instances, shared state _shared_state = {} clientInfo = clientinfo.ClientInfo() - addonName = clientInfo.getAddonName() # Requests session s = None - timeout = 30 + default_timeout = 30 def __init__(self): @@ -182,7 +180,19 @@ class DownloadUtils(): deviceId = self.clientInfo.getDeviceId() version = self.clientInfo.getVersion() - if not authenticate: + if authenticate: + auth = ( + 'MediaBrowser UserId="%s", Client="Kodi", Device="%s", DeviceId="%s", Version="%s"' + % (self.userId, deviceName, deviceId, version)) + header = { + + 'Content-type': 'application/json', + 'Accept-encoding': 'gzip', + 'Accept-Charset': 'UTF-8,*', + 'Authorization': auth, + 'X-MediaBrowser-Token': self.token + } + else: # If user is not authenticated auth = ( 'MediaBrowser Client="Kodi", Device="%s", DeviceId="%s", Version="%s"' @@ -194,21 +204,6 @@ class DownloadUtils(): 'Accept-Charset': 'UTF-8,*', 'Authorization': auth } - else: - userId = self.userId - token = self.token - # Attached to the requests session - auth = ( - 'MediaBrowser UserId="%s", Client="Kodi", Device="%s", DeviceId="%s", Version="%s"' - % (userId, deviceName, deviceId, version)) - header = { - - 'Content-type': 'application/json', - 'Accept-encoding': 'gzip', - 'Accept-Charset': 'UTF-8,*', - 'Authorization': auth, - 'X-MediaBrowser-Token': token - } return header @@ -266,14 +261,14 @@ class DownloadUtils(): ##### PREPARE REQUEST ##### kwargs.update({ 'url': url, - 'timeout': self.timeout, + 'timeout': self.default_timeout, 'json': postBody, 'params': parameters }) ##### THE RESPONSE ##### log(kwargs, 2) - r = self.__requests(action_type, session, **kwargs) + r = self._requests(action_type, session, **kwargs) if r.status_code == 204: # No body in the response @@ -358,7 +353,7 @@ class DownloadUtils(): return default_link - def __requests(self, action, session=requests, **kwargs): + def _requests(self, action, session=requests, **kwargs): if action == "GET": r = session.get(**kwargs) diff --git a/resources/lib/utils.py b/resources/lib/utils.py index cda4c5e2..fe281ef5 100644 --- a/resources/lib/utils.py +++ b/resources/lib/utils.py @@ -22,39 +22,33 @@ import xbmcvfs ################################################################################################# # Main methods -class Logging(): - - LOGGINGCLASS = None +class Logging(object): - def __init__(self, classname=""): + def __init__(self, title=""): - self.LOGGINGCLASS = classname + self.title = title - def log(self, msg, level=1): + def _getLogLevel(self, level): - self.logMsg("EMBY %s" % self.LOGGINGCLASS, msg, level) - - def logMsg(self, title, msg, level=1): - - # Get the logLevel set in UserClient try: logLevel = int(window('emby_logLevel')) except ValueError: logLevel = 0 - if logLevel >= level: + return 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 _printMsg(self, title, msg): + + try: + xbmc.log("%s -> %s" % (title, msg)) + except UnicodeEncodeError: + xbmc.log("%s -> %s" % (title, msg.encode('utf-8'))) + + def log(self, msg, level=1): + + if self._getLogLevel(level): + self._printMsg("EMBY %s" % self.title, msg) # Initiate class for utils.py document logging log = Logging('Utils').log @@ -223,7 +217,7 @@ def setScreensaver(value): def convertDate(date): try: date = datetime.strptime(date, "%Y-%m-%dT%H:%M:%SZ") - except TypeError: + except (ImportError, 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])) From 77b99210c1b36da8e3ff74a3bd0196b57ea04807 Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Sun, 17 Jul 2016 18:27:54 -0500 Subject: [PATCH 061/103] Add error mode to logging --- resources/lib/utils.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/resources/lib/utils.py b/resources/lib/utils.py index fe281ef5..0339b149 100644 --- a/resources/lib/utils.py +++ b/resources/lib/utils.py @@ -47,8 +47,13 @@ class Logging(object): def log(self, msg, level=1): + extra = "" + if level == -1: + # Error mode + extra = "::ERROR" + if self._getLogLevel(level): - self._printMsg("EMBY %s" % self.title, msg) + self._printMsg("EMBY %s%s" % (self.title, extra), msg) # Initiate class for utils.py document logging log = Logging('Utils').log From 02ab4abe517e91cb3c0bc62e3367ce3811cdd0e8 Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Mon, 18 Jul 2016 14:42:33 -0500 Subject: [PATCH 062/103] Fix not saving watched status If delete after watched was enabled. --- resources/lib/player.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/resources/lib/player.py b/resources/lib/player.py index 1cc187bb..ecba7d3e 100644 --- a/resources/lib/player.py +++ b/resources/lib/player.py @@ -465,13 +465,12 @@ class Player(xbmc.Player): if percentComplete >= markPlayedAt and offerDelete: resp = xbmcgui.Dialog().yesno(lang(30091), lang(33015), autoclose=120000) - if not resp: + if resp: + url = "{server}/emby/Items/%s?format=json" % itemid + log("Deleting request: %s" % itemid, 1) + self.doUtils(url, action_type="DELETE") + else log("User skipped deletion.", 1) - continue - - url = "{server}/emby/Items/%s?format=json" % itemid - log("Deleting request: %s" % itemid, 1) - self.doUtils(url, action_type="DELETE") self.stopPlayback(data) From 1559dfc97c668c581a2221158f852433f667caf1 Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Mon, 18 Jul 2016 15:44:11 -0500 Subject: [PATCH 063/103] Attempt to fix _strptime locked error --- service.py | 1 + 1 file changed, 1 insertion(+) diff --git a/service.py b/service.py index ae7b1f8e..9d79adfd 100644 --- a/service.py +++ b/service.py @@ -5,6 +5,7 @@ import os import sys import time +import _strptime # Workaround for threads using datetime: _striptime is locked from datetime import datetime import xbmc From cfb94b7bd05ecd2e2ac7f121d484724aa2c5ce34 Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Mon, 18 Jul 2016 15:47:42 -0500 Subject: [PATCH 064/103] Temp fix for database being locked --- resources/lib/utils.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/resources/lib/utils.py b/resources/lib/utils.py index 0339b149..8623f3c4 100644 --- a/resources/lib/utils.py +++ b/resources/lib/utils.py @@ -81,8 +81,7 @@ def settings(setting, value=None): def language(string_id): # Central string retrieval - unicode - string = xbmcaddon.Addon(id='plugin.video.emby').getLocalizedString(string_id) - return string + return xbmcaddon.Addon(id='plugin.video.emby').getLocalizedString(string_id) ################################################################################################# # Database related methods @@ -98,7 +97,7 @@ def kodiSQL(media_type="video"): else: dbPath = getKodiVideoDBPath() - connection = sqlite3.connect(dbPath, timeout=20) + connection = sqlite3.connect(dbPath, isolation_level=None, timeout=20) return connection def getKodiVideoDBPath(): From d5baf0ceedf070c402518a65f31ad186076646cb Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Mon, 18 Jul 2016 17:56:39 -0500 Subject: [PATCH 065/103] Fix typo --- resources/lib/player.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/lib/player.py b/resources/lib/player.py index ecba7d3e..dfefa0ec 100644 --- a/resources/lib/player.py +++ b/resources/lib/player.py @@ -469,7 +469,7 @@ class Player(xbmc.Player): url = "{server}/emby/Items/%s?format=json" % itemid log("Deleting request: %s" % itemid, 1) self.doUtils(url, action_type="DELETE") - else + else: log("User skipped deletion.", 1) self.stopPlayback(data) From c1632ec27adc151275c428a22b2d16e300c61e08 Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Tue, 19 Jul 2016 01:53:04 -0500 Subject: [PATCH 066/103] Version bump 2.2.16 --- addon.xml | 2 +- changelog.txt | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/addon.xml b/addon.xml index bfc2a9b4..08000cf0 100644 --- a/addon.xml +++ b/addon.xml @@ -1,7 +1,7 @@ diff --git a/changelog.txt b/changelog.txt index e600959b..a9282f60 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,8 @@ +version 2.2.16 +- Fix strptime error +- Temporary fix for database being locked +- Fix watched status failing to update if offer delete after playing is enabled but skipped + version 2.2.14 - Progress dialog always shows for full sync - Add (if item count greater than) to options for inc sync progress dialog display From f89d5c96f617226703286f1287559cfe5f3bcfa7 Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Tue, 19 Jul 2016 02:08:35 -0500 Subject: [PATCH 067/103] Transform database locked into a settings Temporary settings, because it slows down the process since it's auto-committing. Not everyone has database locked crashes. Permanent fix is being worked on in the database branch. --- resources/language/English/strings.xml | 1 + resources/lib/utils.py | 5 ++++- resources/settings.xml | 1 + 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/resources/language/English/strings.xml b/resources/language/English/strings.xml index 189d6856..1f626dd9 100644 --- a/resources/language/English/strings.xml +++ b/resources/language/English/strings.xml @@ -228,6 +228,7 @@ Emby Connect Server Username or email + Enable database locked fix (will slow syncing process) Sign in with Emby Connect diff --git a/resources/lib/utils.py b/resources/lib/utils.py index 8623f3c4..079c9514 100644 --- a/resources/lib/utils.py +++ b/resources/lib/utils.py @@ -97,7 +97,10 @@ def kodiSQL(media_type="video"): else: dbPath = getKodiVideoDBPath() - connection = sqlite3.connect(dbPath, isolation_level=None, timeout=20) + if settings('dblock') == "true": + connection = sqlite3.connect(dbPath, isolation_level=None, timeout=20) + else: + connection = sqlite3.connect(dbPath, timeout=20) return connection def getKodiVideoDBPath(): diff --git a/resources/settings.xml b/resources/settings.xml index 5a25f3b1..91006dbb 100644 --- a/resources/settings.xml +++ b/resources/settings.xml @@ -25,6 +25,7 @@
+ From e97a3a616d47774e125c684bd18c01d6b42b19b6 Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Fri, 22 Jul 2016 11:54:53 -0500 Subject: [PATCH 068/103] Add temporary setting to disable external subs For direct stream --- resources/lib/playbackutils.py | 3 ++- resources/settings.xml | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/resources/lib/playbackutils.py b/resources/lib/playbackutils.py index 4da87f27..f4cdd8b9 100644 --- a/resources/lib/playbackutils.py +++ b/resources/lib/playbackutils.py @@ -16,6 +16,7 @@ import downloadutils import playutils as putils import playlist import read_embyserver as embyserver +import shutil from utils import Logging, window, settings, language as lang ################################################################################################# @@ -228,7 +229,7 @@ class PlaybackUtils(): # Append external subtitles to stream playmethod = window('%s.playmethod' % embyitem) # Only for direct stream - if playmethod in ("DirectStream"): + if playmethod in ("DirectStream") and settings('enableExternalSubs') == "true": # Direct play automatically appends external subtitles = self.externalSubs(playurl) listitem.setSubtitles(subtitles) diff --git a/resources/settings.xml b/resources/settings.xml index 91006dbb..25e8b247 100644 --- a/resources/settings.xml +++ b/resources/settings.xml @@ -54,6 +54,7 @@ + From 793bd665541c81a994c998d364d604bc6efbbdd8 Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Fri, 22 Jul 2016 17:10:35 -0500 Subject: [PATCH 069/103] Fix crash when sleeping/waking up device --- resources/lib/downloadutils.py | 1 + resources/lib/kodimonitor.py | 5 +++++ resources/lib/userclient.py | 6 +++--- service.py | 17 ++++++++++++++++- 4 files changed, 25 insertions(+), 4 deletions(-) diff --git a/resources/lib/downloadutils.py b/resources/lib/downloadutils.py index 34596c2f..ac8bd9ab 100644 --- a/resources/lib/downloadutils.py +++ b/resources/lib/downloadutils.py @@ -289,6 +289,7 @@ class DownloadUtils(): log("Unable to convert the response for: %s" % url, 1) else: # Bad status code + log("=== Bad status response: %s ===" % r.status_code, -1) r.raise_for_status() ##### EXCEPTIONS ##### diff --git a/resources/lib/kodimonitor.py b/resources/lib/kodimonitor.py index 16baa487..6ba9e76d 100644 --- a/resources/lib/kodimonitor.py +++ b/resources/lib/kodimonitor.py @@ -195,10 +195,15 @@ class KodiMonitor(xbmc.Monitor): finally: embycursor.close()''' + elif method == "System.OnSleep": + # Connection is going to sleep + log("Marking the server as offline. System.OnSleep activating.", 1) + window('emby_online', value="sleep") elif method == "System.OnWake": # Allow network to wake up xbmc.sleep(10000) + window('emby_online', value="false") window('emby_onWake', value="true") diff --git a/resources/lib/userclient.py b/resources/lib/userclient.py index d08e00ae..60cb5456 100644 --- a/resources/lib/userclient.py +++ b/resources/lib/userclient.py @@ -23,7 +23,7 @@ class UserClient(threading.Thread): # Borg - multiple instances, shared state _shared_state = {} - stopClient = False + stop_thread = False auth = True retry = 0 @@ -430,7 +430,7 @@ class UserClient(threading.Thread): self.auth = True - if self.stopClient == True: + if self.stop_thread == True: # If stopping the client didn't work break @@ -443,4 +443,4 @@ class UserClient(threading.Thread): def stopClient(self): # When emby for kodi terminates - self.stopClient = True \ No newline at end of file + self.stop_thread = True \ No newline at end of file diff --git a/service.py b/service.py index 9d79adfd..13a9d1f0 100644 --- a/service.py +++ b/service.py @@ -231,7 +231,21 @@ class Service(): sound=False) self.server_online = False - + + elif window('emby_online') == "sleep": + # device going to sleep + if self.websocket_running: + log("Stop websocket thread") + ws.stopClient() + ws = wsc.WebSocket_Client() + self.websocket_running = False + + if self.library_running: + log("Stop library thread") + library.stopThread() + library = librarysync.LibrarySync() + self.library_running = False + else: # Server is online if not self.server_online: @@ -254,6 +268,7 @@ class Service(): # Start the userclient thread if not self.userclient_running: + log("Start user thread") self.userclient_running = True user.start() From 380fcdfc357365c97a24fd4e7ccac7d67e91a6bc Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Fri, 22 Jul 2016 18:34:26 -0500 Subject: [PATCH 070/103] Fix unicode error --- resources/lib/userclient.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/lib/userclient.py b/resources/lib/userclient.py index 60cb5456..09cae9fa 100644 --- a/resources/lib/userclient.py +++ b/resources/lib/userclient.py @@ -328,7 +328,7 @@ class UserClient(threading.Thread): else: # Manual login, user is hidden password = dialog.input( - heading="%s %s" % (lang(33008), username), + heading="%s %s" % (lang(33008), username.decode('utf-8')), option=xbmcgui.ALPHANUM_HIDE_INPUT) sha1 = hashlib.sha1(password) sha1 = sha1.hexdigest() From 76dfa1286c4a63ed06e4c1ac62e31a83d5fd8aa1 Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Sun, 24 Jul 2016 03:59:48 -0500 Subject: [PATCH 071/103] Update logging Use the logging module. Moved logging to it's own file. --- default.py | 21 ++-- resources/lib/api.py | 15 ++- resources/lib/artwork.py | 48 +++++---- resources/lib/clientinfo.py | 16 +-- resources/lib/connect.py | 50 ++++----- resources/lib/downloadutils.py | 71 ++++++------- resources/lib/embydb_functions.py | 14 +-- resources/lib/entrypoint.py | 36 ++++--- resources/lib/image_cache_thread.py | 14 +-- resources/lib/initialsetup.py | 58 ++++++----- resources/lib/itemtypes.py | 148 +++++++++++++------------- resources/lib/kodidb_functions.py | 43 ++++---- resources/lib/kodimonitor.py | 36 ++++--- resources/lib/librarysync.py | 154 ++++++++++++++-------------- resources/lib/loghandler.py | 73 +++++++++++++ resources/lib/musicutils.py | 26 +++-- resources/lib/playbackutils.py | 45 ++++---- resources/lib/player.py | 65 ++++++------ resources/lib/playlist.py | 40 ++++---- resources/lib/playutils.py | 53 +++++----- resources/lib/read_embyserver.py | 36 +++---- resources/lib/userclient.py | 77 +++++++------- resources/lib/utils.py | 92 ++++++----------- resources/lib/videonodes.py | 18 ++-- resources/lib/websocket_client.py | 37 +++---- service.py | 49 ++++----- 26 files changed, 689 insertions(+), 646 deletions(-) create mode 100644 resources/lib/loghandler.py diff --git a/default.py b/default.py index 03a338fc..9d9c098b 100644 --- a/default.py +++ b/default.py @@ -2,6 +2,7 @@ ################################################################################################# +import logging import os import sys import urlparse @@ -21,8 +22,14 @@ sys.path.append(_base_resource) import entrypoint import utils -from utils import Logging, window, language as lang -log = Logging('Default').log +from utils import window, language as lang + +################################################################################################# + +import loghandler + +loghandler.config() +log = logging.getLogger("EMBY.default") ################################################################################################# @@ -37,7 +44,7 @@ class Main(): # Parse parameters base_url = sys.argv[0] params = urlparse.parse_qs(sys.argv[2][1:]) - log("Parameter string: %s" % sys.argv[2], 0) + log.warn("Parameter string: %s" % sys.argv[2]) try: mode = params['mode'][0] itemid = params.get('id') @@ -113,7 +120,7 @@ class Main(): # Server is not online, do not run the sync xbmcgui.Dialog().ok(heading=lang(29999), line1=lang(33034)) - log("Not connected to the emby server.", 1) + log.warn("Not connected to the emby server.") return if window('emby_dbScan') != "true": @@ -126,7 +133,7 @@ class Main(): else: lib.fullSync(repair=True) else: - log("Database scan is already running.", 1) + log.warn("Database scan is already running.") elif mode == "texturecache": import artwork @@ -137,6 +144,6 @@ class Main(): if __name__ == "__main__": - log('plugin.video.emby started', 1) + log.info('plugin.video.emby started') Main() - log('plugin.video.emby stopped', 1) \ No newline at end of file + log.info('plugin.video.emby stopped') \ No newline at end of file diff --git a/resources/lib/api.py b/resources/lib/api.py index e5641e52..0d7332b5 100644 --- a/resources/lib/api.py +++ b/resources/lib/api.py @@ -4,8 +4,12 @@ ################################################################################################## -import clientinfo -from utils import Logging, settings +import logging +from utils import settings + +################################################################################################## + +log = logging.getLogger("EMBY."+__name__) ################################################################################################## @@ -13,17 +17,10 @@ from utils import Logging, settings class API(): def __init__(self, item): - - global log - log = Logging(self.__class__.__name__).log # item is the api response self.item = item - self.clientinfo = clientinfo.ClientInfo() - self.addonName = self.clientinfo.getAddonName() - - def getUserData(self): # Default favorite = False diff --git a/resources/lib/artwork.py b/resources/lib/artwork.py index f2cd0a32..4b0124f7 100644 --- a/resources/lib/artwork.py +++ b/resources/lib/artwork.py @@ -3,6 +3,7 @@ ################################################################################################# import json +import logging import requests import os import urllib @@ -12,11 +13,14 @@ import xbmc import xbmcgui import xbmcvfs -import clientinfo import image_cache_thread -from utils import Logging, window, settings, language as lang, kodiSQL +from utils import window, settings, language as lang, kodiSQL -################################################################################################# +################################################################################################## + +log = logging.getLogger("EMBY."+__name__) + +################################################################################################## class Artwork(): @@ -32,16 +36,10 @@ class Artwork(): def __init__(self): - global log - log = Logging(self.__class__.__name__).log - - self.clientinfo = clientinfo.ClientInfo() - self.addonName = self.clientinfo.getAddonName() - self.enableTextureCache = settings('enableTextureCache') == "true" self.imageCacheLimitThreads = int(settings('imageCacheLimit')) self.imageCacheLimitThreads = int(self.imageCacheLimitThreads * 5) - log("Using Image Cache Thread Count: %s" % self.imageCacheLimitThreads, 1) + log.info("Using Image Cache Thread Count: %s" % self.imageCacheLimitThreads) if not self.xbmc_port and self.enableTextureCache: self.setKodiWebServerDetails() @@ -175,14 +173,14 @@ class Artwork(): line1=lang(33042)): return - log("Doing Image Cache Sync", 1) + log.info("Doing Image Cache Sync") pdialog = xbmcgui.DialogProgress() pdialog.create(lang(29999), lang(33043)) # ask to rest all existing or not if dialog.yesno(lang(29999), lang(33044)): - log("Resetting all cache data first.", 1) + log.info("Resetting all cache data first.") # Remove all existing textures first path = xbmc.translatePath('special://thumbnails/').decode('utf-8') @@ -215,7 +213,7 @@ class Artwork(): cursor.execute("SELECT url FROM art WHERE media_type != 'actor'") # dont include actors result = cursor.fetchall() total = len(result) - log("Image cache sync about to process %s images" % total, 1) + log.info("Image cache sync about to process %s images" % total) cursor.close() count = 0 @@ -236,7 +234,7 @@ class Artwork(): cursor.execute("SELECT url FROM art") result = cursor.fetchall() total = len(result) - log("Image cache sync about to process %s images" % total, 1) + log.info("Image cache sync about to process %s images" % total) cursor.close() count = 0 @@ -252,14 +250,14 @@ class Artwork(): count += 1 pdialog.update(100, "%s %s" % (lang(33046), len(self.imageCacheThreads))) - log("Waiting for all threads to exit", 1) + log.info("Waiting for all threads to exit") while len(self.imageCacheThreads): for thread in self.imageCacheThreads: if thread.isFinished: self.imageCacheThreads.remove(thread) pdialog.update(100, "%s %s" % (lang(33046), len(self.imageCacheThreads))) - log("Waiting for all threads to exit: %s" % len(self.imageCacheThreads), 1) + log.info("Waiting for all threads to exit: %s" % len(self.imageCacheThreads)) xbmc.sleep(500) pdialog.close() @@ -282,13 +280,13 @@ class Artwork(): self.imageCacheThreads.append(newThread) return else: - log("Waiting for empty queue spot: %s" % len(self.imageCacheThreads), 2) + log.info("Waiting for empty queue spot: %s" % len(self.imageCacheThreads)) xbmc.sleep(50) def cacheTexture(self, url): # Cache a single image url to the texture cache if url and self.enableTextureCache: - log("Processing: %s" % url, 2) + log.debug("Processing: %s" % url) if not self.imageCacheLimitThreads: # Add image to texture cache by simply calling it at the http endpoint @@ -406,7 +404,7 @@ class Artwork(): except TypeError: # Add the artwork cacheimage = True - log("Adding Art Link for kodiId: %s (%s)" % (kodiId, imageUrl), 2) + log.debug("Adding Art Link for kodiId: %s (%s)" % (kodiId, imageUrl)) query = ( ''' @@ -427,8 +425,8 @@ class Artwork(): # Delete current entry before updating with the new one self.deleteCachedArtwork(url) - log("Updating Art url for %s kodiId: %s (%s) -> (%s)" - % (imageType, kodiId, url, imageUrl), 1) + log.info("Updating Art url for %s kodiId: %s (%s) -> (%s)" + % (imageType, kodiId, url, imageUrl)) query = ' '.join(( @@ -472,21 +470,21 @@ class Artwork(): cachedurl = cursor.fetchone()[0] except TypeError: - log("Could not find cached url.", 1) + log.info("Could not find cached url.") except OperationalError: - log("Database is locked. Skip deletion process.", 1) + log.info("Database is locked. Skip deletion process.") else: # Delete thumbnail as well as the entry thumbnails = xbmc.translatePath("special://thumbnails/%s" % cachedurl).decode('utf-8') - log("Deleting cached thumbnail: %s" % thumbnails, 1) + log.info("Deleting cached thumbnail: %s" % thumbnails) xbmcvfs.delete(thumbnails) try: cursor.execute("DELETE FROM texture WHERE url = ?", (url,)) connection.commit() except OperationalError: - log("Issue deleting url from cache. Skipping.", 2) + log.debug("Issue deleting url from cache. Skipping.") finally: cursor.close() diff --git a/resources/lib/clientinfo.py b/resources/lib/clientinfo.py index d5550388..7fd485e3 100644 --- a/resources/lib/clientinfo.py +++ b/resources/lib/clientinfo.py @@ -2,6 +2,7 @@ ################################################################################################# +import logging import os from uuid import uuid4 @@ -9,9 +10,13 @@ import xbmc import xbmcaddon import xbmcvfs -from utils import Logging, window, settings +from utils import window, settings -################################################################################################# +################################################################################################## + +log = logging.getLogger("EMBY."+__name__) + +################################################################################################## class ClientInfo(): @@ -19,9 +24,6 @@ class ClientInfo(): def __init__(self): - global log - log = Logging(self.__class__.__name__).log - self.addon = xbmcaddon.Addon() self.addonName = self.getAddonName() @@ -88,14 +90,14 @@ class ClientInfo(): GUID = xbmcvfs.File(GUID_file) clientId = GUID.read() if not clientId: - log("Generating a new deviceid...", 1) + log.info("Generating a new deviceid...") clientId = str("%012X" % uuid4()) GUID = xbmcvfs.File(GUID_file, 'w') GUID.write(clientId) GUID.close() - log("DeviceId loaded: %s" % clientId, 1) + log.info("DeviceId loaded: %s" % clientId) window('emby_deviceId', value=clientId) return clientId \ No newline at end of file diff --git a/resources/lib/connect.py b/resources/lib/connect.py index 87dbc2e1..aa3082a7 100644 --- a/resources/lib/connect.py +++ b/resources/lib/connect.py @@ -7,7 +7,7 @@ import requests import logging import clientinfo -from utils import Logging, window +from utils import window ################################################################################################## @@ -15,7 +15,8 @@ from utils import Logging, window from requests.packages.urllib3.exceptions import InsecureRequestWarning, InsecurePlatformWarning requests.packages.urllib3.disable_warnings(InsecureRequestWarning) requests.packages.urllib3.disable_warnings(InsecurePlatformWarning) -#logging.getLogger('requests').setLevel(logging.WARNING) + +log = logging.getLogger("EMBY."+__name__) ################################################################################################## @@ -25,7 +26,6 @@ class ConnectUtils(): # Borg - multiple instances, shared state _shared_state = {} clientInfo = clientinfo.ClientInfo() - addonName = clientInfo.getAddonName() # Requests session c = None @@ -34,26 +34,23 @@ class ConnectUtils(): def __init__(self): - global log - log = Logging(self.__class__.__name__).log - self.__dict__ = self._shared_state def setUserId(self, userId): # Reserved for userclient only self.userId = userId - log("Set connect userId: %s" % userId, 2) + log.debug("Set connect userId: %s" % userId) def setServer(self, server): # Reserved for userclient only self.server = server - log("Set connect server: %s" % server, 2) + log.debug("Set connect server: %s" % server) def setToken(self, token): # Reserved for userclient only self.token = token - log("Set connect token: %s" % token, 2) + log.debug("Set connect token: %s" % token) def startSession(self): @@ -71,7 +68,7 @@ class ConnectUtils(): if self.sslclient is not None: verify = self.sslclient except: - log("Could not load SSL settings.", 1) + log.info("Could not load SSL settings.") # Start session self.c = requests.Session() @@ -81,13 +78,13 @@ 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) + log.info("Requests session started on: %s" % self.server) def stopSession(self): try: self.c.close() - except Exception as e: - log("Requests session could not be terminated: %s" % e, 1) + except Exception: + log.warn("Requests session could not be terminated") def getHeader(self, authenticate=True): @@ -101,7 +98,7 @@ class ConnectUtils(): 'Content-type': 'application/x-www-form-urlencoded; charset=UTF-8', 'Accept': "application/json" } - log("Header: %s" % header, 1) + log.info("Header: %s" % header) else: token = self.token @@ -113,14 +110,14 @@ class ConnectUtils(): 'X-Application': "Kodi/%s" % version, 'X-Connect-UserToken': token } - log("Header: %s" % header, 1) + log.info("Header: %s" % header) return header def doUrl(self, url, data=None, postBody=None, rtype="GET", parameters=None, authenticate=True, timeout=None): - log("=== ENTER connectUrl ===", 2) + log.debug("=== ENTER connectUrl ===") default_link = "" @@ -207,25 +204,25 @@ class ConnectUtils(): verify=verifyssl) ##### THE RESPONSE ##### - log(r.url, 1) - log(r, 1) + log.info(r.url) + log.info(r) if r.status_code == 204: # No body in the response - log("====== 204 Success ======", 1) + log.info("====== 204 Success ======") elif r.status_code == requests.codes.ok: try: # UNICODE - JSON object r = r.json() - log("====== 200 Success ======", 1) - log("Response: %s" % r, 1) + log.info("====== 200 Success ======") + log.info("Response: %s" % r) return r except: if r.headers.get('content-type') != "text/html": - log("Unable to convert the response for: %s" % url, 1) + log.info("Unable to convert the response for: %s" % url) else: r.raise_for_status() @@ -236,8 +233,7 @@ class ConnectUtils(): pass except requests.exceptions.ConnectTimeout as e: - log("Server timeout at: %s" % url, 0) - log(e, 1) + log.warn("Server timeout at: %s" % url) except requests.exceptions.HTTPError as e: @@ -253,11 +249,9 @@ class ConnectUtils(): pass except requests.exceptions.SSLError as e: - log("Invalid SSL certificate for: %s" % url, 0) - log(e, 1) + log.warn("Invalid SSL certificate for: %s" % url) except requests.exceptions.RequestException as e: - log("Unknown error connecting to: %s" % url, 0) - log(e, 1) + log.warn("Unknown error connecting to: %s" % url) return default_link \ No newline at end of file diff --git a/resources/lib/downloadutils.py b/resources/lib/downloadutils.py index ac8bd9ab..47a5db59 100644 --- a/resources/lib/downloadutils.py +++ b/resources/lib/downloadutils.py @@ -10,7 +10,7 @@ import xbmc import xbmcgui import clientinfo -from utils import Logging, window, settings +from utils import window, settings ################################################################################################## @@ -18,7 +18,8 @@ from utils import Logging, window, settings from requests.packages.urllib3.exceptions import InsecureRequestWarning, InsecurePlatformWarning requests.packages.urllib3.disable_warnings(InsecureRequestWarning) requests.packages.urllib3.disable_warnings(InsecurePlatformWarning) -#logging.getLogger('requests').setLevel(logging.WARNING) + +log = logging.getLogger("EMBY."+__name__) ################################################################################################## @@ -36,38 +37,35 @@ class DownloadUtils(): def __init__(self): - global log - log = Logging(self.__class__.__name__).log - self.__dict__ = self._shared_state def setUsername(self, username): # Reserved for userclient only self.username = username - log("Set username: %s" % username, 2) + log.debug("Set username: %s" % username) def setUserId(self, userId): # Reserved for userclient only self.userId = userId - log("Set userId: %s" % userId, 2) + log.debug("Set userId: %s" % userId) def setServer(self, server): # Reserved for userclient only self.server = server - log("Set server: %s" % server, 2) + log.debug("Set server: %s" % server) def setToken(self, token): # Reserved for userclient only self.token = token - log("Set token: %s" % token, 2) + log.debug("Set token: %s" % token) def setSSL(self, ssl, sslclient): # Reserved for userclient only self.sslverify = ssl self.sslclient = sslclient - log("Verify SSL host certificate: %s" % ssl, 2) - log("SSL client side certificate: %s" % sslclient, 2) + log.debug("Verify SSL host certificate: %s" % ssl) + log.debug("SSL client side certificate: %s" % sslclient) def postCapabilities(self, deviceId): @@ -92,11 +90,11 @@ class DownloadUtils(): ) } - log("Capabilities URL: %s" % url, 2) - log("Postdata: %s" % data, 2) + log.debug("Capabilities URL: %s" % url) + log.debug("Postdata: %s" % data) self.downloadUrl(url, postBody=data, action_type="POST") - log("Posted capabilities to %s" % self.server, 2) + log.debug("Posted capabilities to %s" % self.server) # Attempt at getting sessionId url = "{server}/emby/Sessions?DeviceId=%s&format=json" % deviceId @@ -105,11 +103,11 @@ class DownloadUtils(): sessionId = result[0]['Id'] except (KeyError, TypeError): - log("Failed to retrieve sessionId.", 1) + log.info("Failed to retrieve sessionId.") else: - log("Session: %s" % result, 2) - log("SessionId: %s" % sessionId, 1) + log.debug("Session: %s" % result) + log.info("SessionId: %s" % sessionId) window('emby_sessionId', value=sessionId) # Post any permanent additional users @@ -117,8 +115,7 @@ class DownloadUtils(): if additionalUsers: additionalUsers = additionalUsers.split(',') - log("List of permanent users added to the session: %s" - % additionalUsers, 1) + log.info("List of permanent users added to the session: %s" % additionalUsers) # Get the user list from server to get the userId url = "{server}/emby/Users?format=json" @@ -155,7 +152,7 @@ class DownloadUtils(): if self.sslclient is not None: verify = self.sslclient except: - log("Could not load SSL settings.", 1) + log.info("Could not load SSL settings.") # Start session self.s = requests.Session() @@ -165,13 +162,13 @@ 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) + log.info("Requests session started on: %s" % self.server) def stopSession(self): try: self.s.close() - except: - log("Requests session could not be terminated.", 1) + except Exception: + log.warn("Requests session could not be terminated.") def getHeader(self, authenticate=True): @@ -210,7 +207,7 @@ class DownloadUtils(): def downloadUrl(self, url, postBody=None, action_type="GET", parameters=None, authenticate=True): - log("===== ENTER downloadUrl =====", 2) + log.debug("===== ENTER downloadUrl =====") session = requests kwargs = {} @@ -267,12 +264,12 @@ class DownloadUtils(): }) ##### THE RESPONSE ##### - log(kwargs, 2) + log.debug(kwargs) r = self._requests(action_type, session, **kwargs) if r.status_code == 204: # No body in the response - log("====== 204 Success ======", 2) + log.debug("====== 204 Success ======") # Read response to release connection r.content @@ -280,16 +277,16 @@ class DownloadUtils(): try: # UNICODE - JSON object r = r.json() - log("====== 200 Success ======", 2) - log("Response: %s" % r, 2) + log.debug("====== 200 Success ======") + log.debug("Response: %s" % r) return r except: if r.headers.get('content-type') != "text/html": - log("Unable to convert the response for: %s" % url, 1) + log.info("Unable to convert the response for: %s" % url) else: # Bad status code - log("=== Bad status response: %s ===" % r.status_code, -1) + log.error("=== Bad status response: %s ===" % r.status_code) r.raise_for_status() ##### EXCEPTIONS ##### @@ -297,13 +294,11 @@ class DownloadUtils(): except requests.exceptions.ConnectionError as e: # Make the addon aware of status if window('emby_online') != "false": - log("Server unreachable at: %s" % url, 0) - log(e, 2) + log.warn("Server unreachable at: %s" % url) window('emby_online', value="false") except requests.exceptions.ConnectTimeout as e: - log("Server timeout at: %s" % url, 0) - log(e, 1) + log.warn("Server timeout at: %s" % url) except requests.exceptions.HTTPError as e: @@ -330,7 +325,7 @@ class DownloadUtils(): elif status not in ("401", "Auth"): # Tell userclient token has been revoked. window('emby_serverStatus', value="401") - log("HTTP Error: %s" % e, 0) + log.warn("HTTP Error: %s" % e) xbmcgui.Dialog().notification( heading="Error connecting", message="Unauthorized.", @@ -345,12 +340,10 @@ class DownloadUtils(): pass except requests.exceptions.SSLError as e: - log("Invalid SSL certificate for: %s" % url, 0) - log(e, 1) + log.warn("Invalid SSL certificate for: %s" % url) except requests.exceptions.RequestException as e: - log("Unknown error connecting to: %s" % url, 0) - log(e, 1) + log.warn("Unknown error connecting to: %s" % url) return default_link diff --git a/resources/lib/embydb_functions.py b/resources/lib/embydb_functions.py index 73e808d8..9220fa5d 100644 --- a/resources/lib/embydb_functions.py +++ b/resources/lib/embydb_functions.py @@ -2,12 +2,14 @@ ################################################################################################# +import logging from sqlite3 import OperationalError -import clientinfo -from utils import Logging +################################################################################################## -################################################################################################# +log = logging.getLogger("EMBY."+__name__) + +################################################################################################## class Embydb_Functions(): @@ -15,14 +17,8 @@ class Embydb_Functions(): def __init__(self, embycursor): - global log - log = Logging(self.__class__.__name__).log - self.embycursor = embycursor - self.clientInfo = clientinfo.ClientInfo() - self.addonName = self.clientInfo.getAddonName() - def getViews(self): diff --git a/resources/lib/entrypoint.py b/resources/lib/entrypoint.py index ba4ffe22..e532e5e2 100644 --- a/resources/lib/entrypoint.py +++ b/resources/lib/entrypoint.py @@ -3,6 +3,7 @@ ################################################################################################# import json +import logging import os import sys import urlparse @@ -24,8 +25,11 @@ import playlist import playbackutils as pbutils import playutils import api -from utils import Logging, window, settings, language as lang -log = Logging('Entrypoint').log +from utils import window, settings, language as lang + +################################################################################################# + +log = logging.getLogger("EMBY."+__name__) ################################################################################################# @@ -43,7 +47,7 @@ def resetAuth(): heading=lang(30132), line1=lang(33050)) if resp: - log("Reset login attempts.", 1) + log.info("Reset login attempts.") window('emby_serverStatus', value="Auth") else: xbmc.executebuiltin('Addon.OpenSettings(plugin.video.emby)') @@ -121,12 +125,12 @@ def resetDeviceId(): window('emby_deviceId', clear=True) deviceId = clientinfo.ClientInfo().getDeviceId(reset=True) except Exception as e: - log("Failed to generate a new device Id: %s" % e, 1) + log.error("Failed to generate a new device Id: %s" % e) dialog.ok( heading=lang(29999), line1=lang(33032)) else: - log("Successfully removed old deviceId: %s New deviceId: %s" % (deviceId_old, deviceId), 1) + log.info("Successfully removed old deviceId: %s New deviceId: %s" % (deviceId_old, deviceId)) dialog.ok( heading=lang(29999), line1=lang(33033)) @@ -153,7 +157,7 @@ def deleteItem(): elif xbmc.getCondVisibility('Container.Content(pictures)'): itemType = "picture" else: - log("Unknown type, unable to proceed.", 1) + log.info("Unknown type, unable to proceed.") return embyconn = utils.kodiSQL('emby') @@ -165,7 +169,7 @@ def deleteItem(): try: itemId = item[0] except TypeError: - log("Unknown itemId, unable to proceed.", 1) + log.error("Unknown itemId, unable to proceed.") return if settings('skipContextMenu') != "true": @@ -173,7 +177,7 @@ def deleteItem(): heading=lang(29999), line1=lang(33041)) if not resp: - log("User skipped deletion for: %s." % itemId, 1) + log.info("User skipped deletion for: %s." % itemId) return embyserver.Read_EmbyServer().deleteItem(itemId) @@ -257,7 +261,7 @@ def addUser(): return # Subtract any additional users - log("Displaying list of users: %s" % users) + log.info("Displaying list of users: %s" % users) resp = dialog.select("Add user to the session", users) # post additional user if resp > -1: @@ -272,7 +276,7 @@ def addUser(): time=1000) except: - log("Failed to add user to session.") + log.error("Failed to add user to session.") dialog.notification( heading=lang(29999), message=lang(33068), @@ -328,7 +332,7 @@ def getThemeMedia(): tvtunes = xbmcaddon.Addon(id="script.tvtunes") tvtunes.setSetting('custom_path_enable', "true") tvtunes.setSetting('custom_path', library) - log("TV Tunes custom path is enabled and set.", 1) + log.info("TV Tunes custom path is enabled and set.") 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. @@ -474,7 +478,7 @@ def refreshPlaylist(): sound=False) except Exception as e: - log("Refresh playlists/nodes failed: %s" % e, 1) + log.error("Refresh playlists/nodes failed: %s" % e) dialog.notification( heading=lang(29999), message=lang(33070), @@ -516,7 +520,7 @@ def BrowseContent(viewname, browse_type="", folderid=""): break if viewname is not None: - log("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'))) + log.info("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: @@ -1049,7 +1053,7 @@ def getExtraFanArt(embyId,embyPath): if embyId: #only proceed if we actually have a emby id - log("Requesting extrafanart for Id: %s" % embyId, 0) + log.info("Requesting extrafanart for Id: %s" % embyId) # We need to store the images locally for this to work # because of the caching system in xbmc @@ -1078,7 +1082,7 @@ def getExtraFanArt(embyId,embyPath): xbmcvfs.copy(backdrop, fanartFile) count += 1 else: - log("Found cached backdrop.", 2) + log.debug("Found cached backdrop.") # Use existing cached images dirs, files = xbmcvfs.listdir(fanartDir) for file in files: @@ -1089,7 +1093,7 @@ def getExtraFanArt(embyId,embyPath): url=fanartFile, listitem=li) except Exception as e: - log("Error getting extrafanart: %s" % e, 0) + log.error("Error getting extrafanart: %s" % e) # 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/image_cache_thread.py b/resources/lib/image_cache_thread.py index fdf63d63..ffed967b 100644 --- a/resources/lib/image_cache_thread.py +++ b/resources/lib/image_cache_thread.py @@ -2,10 +2,13 @@ ################################################################################################# -import threading +import logging import requests +import threading -from utils import Logging +################################################################################################# + +log = logging.getLogger("EMBY."+__name__) ################################################################################################# @@ -22,9 +25,6 @@ class image_cache_thread(threading.Thread): def __init__(self): - global log - log = Logging(self.__class__.__name__).log - threading.Thread.__init__(self) @@ -44,7 +44,7 @@ class image_cache_thread(threading.Thread): def run(self): - log("Image Caching Thread Processing: %s" % self.urlToProcess, 2) + log.debug("Image Caching Thread Processing: %s" % self.urlToProcess) try: response = requests.head( @@ -56,5 +56,5 @@ class image_cache_thread(threading.Thread): # We don't need the result except: pass - log("Image Caching Thread Exited", 2) + log.debug("Image Caching Thread Exited") self.isFinished = True \ No newline at end of file diff --git a/resources/lib/initialsetup.py b/resources/lib/initialsetup.py index da0a5108..032affb1 100644 --- a/resources/lib/initialsetup.py +++ b/resources/lib/initialsetup.py @@ -3,6 +3,7 @@ ################################################################################################# import json +import logging import socket import xbmc @@ -12,7 +13,11 @@ import xbmcaddon import clientinfo import downloadutils import userclient -from utils import Logging, settings, language as lang, passwordsXML +from utils import settings, language as lang, passwordsXML + +################################################################################################# + +log = logging.getLogger("EMBY."+__name__) ################################################################################################# @@ -22,9 +27,6 @@ class InitialSetup(): def __init__(self): - global log - log = Logging(self.__class__.__name__).log - self.addonId = clientinfo.ClientInfo().getAddonId() self.doUtils = downloadutils.DownloadUtils().downloadUrl self.userClient = userclient.UserClient() @@ -37,20 +39,20 @@ class InitialSetup(): ##### SERVER INFO ##### - log("Initial setup called.", 2) + log.debug("Initial setup called.") server = self.userClient.getServer() if server: - log("Server is already set.", 2) + log.debug("Server is already set.") return - log("Looking for server...", 2) + log.debug("Looking for server...") server = self.getServerDetails() - log("Found: %s" % server, 2) + log.debug("Found: %s" % server) try: prefix, ip, port = server.replace("/", "").split(":") - except: # Failed to retrieve server information - log("getServerDetails failed.", 1) + except Exception: # Failed to retrieve server information + log.error("getServerDetails failed.") xbmc.executebuiltin('Addon.OpenSettings(%s)' % addonId) return else: @@ -60,7 +62,7 @@ class InitialSetup(): line2="%s %s" % (lang(30169), server)) if server_confirm: # Correct server found - log("Server is selected. Saving the information.", 1) + log.info("Server is selected. Saving the information.") settings('ipaddress', value=ip) settings('port', value=port) @@ -68,20 +70,20 @@ class InitialSetup(): settings('https', value="true") else: # User selected no or cancelled the dialog - log("No server selected.", 1) + log.info("No server selected.") xbmc.executebuiltin('Addon.OpenSettings(%s)' % addonId) return ##### USER INFO ##### - log("Getting user list.", 1) + log.info("Getting user list.") result = self.doUtils("%s/emby/Users/Public?format=json" % server, authenticate=False) if result == "": - log("Unable to connect to %s" % server, 1) + log.info("Unable to connect to %s" % server) return - log("Response: %s" % result, 2) + log.debug("Response: %s" % result) # Process the list of users usernames = [] users_hasPassword = [] @@ -95,14 +97,14 @@ class InitialSetup(): name = "%s (secure)" % name users_hasPassword.append(name) - log("Presenting user list: %s" % users_hasPassword, 1) + log.info("Presenting user list: %s" % users_hasPassword) user_select = dialog.select(lang(30200), users_hasPassword) if user_select > -1: selected_user = usernames[user_select] - log("Selected user: %s" % selected_user, 1) + log.info("Selected user: %s" % selected_user) settings('username', value=selected_user) else: - log("No user selected.", 1) + log.info("No user selected.") xbmc.executebuiltin('Addon.OpenSettings(%s)' % addonId) return @@ -114,7 +116,7 @@ class InitialSetup(): nolabel=lang(33036), yeslabel=lang(33037)) if directPaths: - log("User opted to use direct paths.", 1) + log.info("User opted to use direct paths.") settings('useDirectPaths', value="1") # ask for credentials @@ -122,14 +124,14 @@ class InitialSetup(): heading=lang(30517), line1= lang(33038)) if credentials: - log("Presenting network credentials dialog.", 1) + log.info("Presenting network credentials dialog.") passwordsXML() musicDisabled = dialog.yesno( heading=lang(29999), line1=lang(33039)) if musicDisabled: - log("User opted to disable Emby music library.", 1) + log.info("User opted to disable Emby music library.") settings('enableMusic', value="false") else: # Only prompt if the user didn't select direct paths for videos @@ -138,12 +140,12 @@ class InitialSetup(): heading=lang(29999), line1=lang(33040)) if musicAccess: - log("User opted to direct stream music.", 1) + log.info("User opted to direct stream music.") settings('streamMusic', value="true") def getServerDetails(self): - log("Getting Server Details from Network", 1) + log.info("Getting Server Details from Network") MULTI_GROUP = ("", 7359) MESSAGE = "who is EmbyServer?" @@ -157,15 +159,15 @@ class InitialSetup(): sock.setsockopt(socket.SOL_IP, socket.IP_MULTICAST_LOOP, 1) sock.setsockopt(socket.IPPROTO_IP, socket.SO_REUSEADDR, 1) - log("MultiGroup : %s" % str(MULTI_GROUP), 2) - log("Sending UDP Data: %s" % MESSAGE, 2) + log.debug("MultiGroup : %s" % str(MULTI_GROUP)) + log.debug("Sending UDP Data: %s" % MESSAGE) sock.sendto(MESSAGE, MULTI_GROUP) try: data, addr = sock.recvfrom(1024) # buffer size is 1024 bytes - log("Received Response: %s" % data) - except: - log("No UDP Response") + log.info("Received Response: %s" % data) + except Exception: + log.error("No UDP Response") return None else: # Get the address diff --git a/resources/lib/itemtypes.py b/resources/lib/itemtypes.py index 934efcee..7438c83f 100644 --- a/resources/lib/itemtypes.py +++ b/resources/lib/itemtypes.py @@ -2,6 +2,7 @@ ################################################################################################## +import logging import urllib from ntpath import dirname from datetime import datetime @@ -18,9 +19,13 @@ import embydb_functions as embydb import kodidb_functions as kodidb import read_embyserver as embyserver import musicutils -from utils import Logging, window, settings, language as lang, kodiSQL +from utils import window, settings, language as lang, kodiSQL -################################################################################################## +################################################################################################# + +log = logging.getLogger("EMBY."+__name__) + +################################################################################################# class Items(object): @@ -28,9 +33,6 @@ class Items(object): def __init__(self, embycursor, kodicursor): - global log - log = Logging(self.__class__.__name__).log - self.embycursor = embycursor self.kodicursor = kodicursor @@ -79,7 +81,7 @@ class Items(object): if total == 0: return False - log("Processing %s: %s" % (process, items), 1) + log.info("Processing %s: %s" % (process, items)) if pdialog: pdialog.update(heading="Processing %s: %s items" % (process, total)) @@ -171,7 +173,7 @@ class Items(object): 'remove': items_process.remove } else: - log("Unsupported itemtype: %s." % itemtype, 1) + log.info("Unsupported itemtype: %s." % itemtype) actions = {} if actions.get(process): @@ -202,7 +204,7 @@ class Items(object): if musicconn is not None: # close connection for special types - log("Updating music database.", 1) + log.info("Updating music database.") musicconn.commit() musiccursor.close() @@ -282,11 +284,11 @@ class Movies(Items): movieid = emby_dbitem[0] fileid = emby_dbitem[1] pathid = emby_dbitem[2] - log("movieid: %s fileid: %s pathid: %s" % (movieid, fileid, pathid), 1) + log.info("movieid: %s fileid: %s pathid: %s" % (movieid, fileid, pathid)) except TypeError: update_item = False - log("movieid: %s not found." % itemid, 2) + log.info("movieid: %s not found." % itemid) # movieid kodicursor.execute("select coalesce(max(idMovie),0) from movie") movieid = kodicursor.fetchone()[0] + 1 @@ -300,12 +302,12 @@ class Movies(Items): except TypeError: # item is not found, let's recreate it. update_item = False - log("movieid: %s missing from Kodi, repairing the entry." % movieid, 1) + log.info("movieid: %s missing from Kodi, repairing the entry." % movieid) if not viewtag or not viewid: # Get view tag from emby viewtag, viewid, mediatype = self.emby.getView_embyId(itemid) - log("View tag found: %s" % viewtag, 2) + log.debug("View tag found: %s" % viewtag) # fileId information checksum = API.getChecksum() @@ -348,7 +350,7 @@ class Movies(Items): try: trailer = "plugin://plugin.video.emby/trailer/?id=%s&mode=play" % result[0]['Id'] except IndexError: - log("Failed to process local trailer.", 1) + log.info("Failed to process local trailer.") trailer = None else: # Try to get the youtube trailer @@ -360,7 +362,7 @@ class Movies(Items): try: trailerId = trailer.rsplit('=', 1)[1] except IndexError: - log("Failed to process trailer: %s" % trailer, 1) + log.info("Failed to process trailer: %s" % trailer) trailer = None else: trailer = "plugin://plugin.video.youtube/play/?video_id=%s" % trailerId @@ -397,7 +399,7 @@ class Movies(Items): ##### UPDATE THE MOVIE ##### if update_item: - log("UPDATE movie itemid: %s - Title: %s" % (itemid, title), 1) + log.info("UPDATE movie itemid: %s - Title: %s" % (itemid, title)) # Update the movie entry query = ' '.join(( @@ -417,7 +419,7 @@ class Movies(Items): ##### OR ADD THE MOVIE ##### else: - log("ADD movie itemid: %s - Title: %s" % (itemid, title), 1) + log.info("ADD movie itemid: %s - Title: %s" % (itemid, title)) # Add path pathid = self.kodi_db.addPath(path) @@ -527,10 +529,10 @@ class Movies(Items): try: movieid = emby_dbitem[0] except TypeError: - log("Failed to add: %s to boxset." % movie['Name'], 1) + log.info("Failed to add: %s to boxset." % movie['Name']) continue - log("New addition to boxset %s: %s" % (title, movie['Name']), 1) + log.info("New addition to boxset %s: %s" % (title, movie['Name'])) self.kodi_db.assignBoxset(setid, movieid) # Update emby reference emby_db.updateParentId(itemid, setid) @@ -541,7 +543,7 @@ class Movies(Items): # Process removals from boxset for movie in process: movieid = current[movie] - log("Remove from boxset %s: %s" % (title, movieid)) + log.info("Remove from boxset %s: %s" % (title, movieid)) self.kodi_db.removefromBoxset(movieid) # Update emby reference emby_db.updateParentId(movie, None) @@ -566,7 +568,7 @@ class Movies(Items): try: movieid = emby_dbitem[0] fileid = emby_dbitem[1] - log("Update playstate for movie: %s fileid: %s" % (item['Name'], fileid), 1) + log.info("Update playstate for movie: %s fileid: %s" % (item['Name'], fileid)) except TypeError: return @@ -582,7 +584,7 @@ class Movies(Items): resume = API.adjustResume(userdata['Resume']) total = round(float(runtime), 6) - log("%s New resume point: %s" % (itemid, resume)) + log.debug("%s New resume point: %s" % (itemid, resume)) self.kodi_db.addPlaystate(fileid, resume, total, playcount, dateplayed) emby_db.updateReference(itemid, checksum) @@ -598,7 +600,7 @@ class Movies(Items): kodiid = emby_dbitem[0] fileid = emby_dbitem[1] mediatype = emby_dbitem[4] - log("Removing %sid: %s fileid: %s" % (mediatype, kodiid, fileid), 1) + log.info("Removing %sid: %s fileid: %s" % (mediatype, kodiid, fileid)) except TypeError: return @@ -624,7 +626,7 @@ class Movies(Items): kodicursor.execute("DELETE FROM sets WHERE idSet = ?", (kodiid,)) - log("Deleted %s %s from kodi database" % (mediatype, itemid), 1) + log.info("Deleted %s %s from kodi database" % (mediatype, itemid)) class MusicVideos(Items): @@ -664,11 +666,11 @@ class MusicVideos(Items): mvideoid = emby_dbitem[0] fileid = emby_dbitem[1] pathid = emby_dbitem[2] - log("mvideoid: %s fileid: %s pathid: %s" % (mvideoid, fileid, pathid), 1) + log.info("mvideoid: %s fileid: %s pathid: %s" % (mvideoid, fileid, pathid)) except TypeError: update_item = False - log("mvideoid: %s not found." % itemid, 2) + log.info("mvideoid: %s not found." % itemid) # mvideoid kodicursor.execute("select coalesce(max(idMVideo),0) from musicvideo") mvideoid = kodicursor.fetchone()[0] + 1 @@ -682,12 +684,12 @@ class MusicVideos(Items): except TypeError: # item is not found, let's recreate it. update_item = False - log("mvideoid: %s missing from Kodi, repairing the entry." % mvideoid, 1) + log.info("mvideoid: %s missing from Kodi, repairing the entry." % mvideoid) if not viewtag or not viewid: # Get view tag from emby viewtag, viewid, mediatype = self.emby.getView_embyId(itemid) - log("View tag found: %s" % viewtag, 2) + log.debug("View tag found: %s" % viewtag) # fileId information checksum = API.getChecksum() @@ -743,7 +745,7 @@ class MusicVideos(Items): ##### UPDATE THE MUSIC VIDEO ##### if update_item: - log("UPDATE mvideo itemid: %s - Title: %s" % (itemid, title), 1) + log.info("UPDATE mvideo itemid: %s - Title: %s" % (itemid, title)) # Update path query = "UPDATE path SET strPath = ? WHERE idPath = ?" @@ -769,7 +771,7 @@ class MusicVideos(Items): ##### OR ADD THE MUSIC VIDEO ##### else: - log("ADD mvideo itemid: %s - Title: %s" % (itemid, title), 1) + log.info("ADD mvideo itemid: %s - Title: %s" % (itemid, title)) # Add path query = ' '.join(( @@ -869,9 +871,9 @@ class MusicVideos(Items): try: mvideoid = emby_dbitem[0] fileid = emby_dbitem[1] - log( + log.info( "Update playstate for musicvideo: %s fileid: %s" - % (item['Name'], fileid), 1) + % (item['Name'], fileid)) except TypeError: return @@ -901,7 +903,7 @@ class MusicVideos(Items): mvideoid = emby_dbitem[0] fileid = emby_dbitem[1] pathid = emby_dbitem[2] - log("Removing mvideoid: %s fileid: %s" % (mvideoid, fileid, pathid), 1) + log.info("Removing mvideoid: %s fileid: %s" % (mvideoid, fileid, pathid)) except TypeError: return @@ -927,7 +929,7 @@ class MusicVideos(Items): kodicursor.execute("DELETE FROM path WHERE idPath = ?", (pathid,)) self.embycursor.execute("DELETE FROM emby WHERE emby_id = ?", (itemid,)) - log("Deleted musicvideo %s from kodi database" % itemid, 1) + log.info("Deleted musicvideo %s from kodi database" % itemid) class TVShows(Items): @@ -991,7 +993,7 @@ class TVShows(Items): API = api.API(item) if settings('syncEmptyShows') == "false" and not item.get('RecursiveItemCount'): - log("Skipping empty show: %s" % item['Name'], 1) + log.info("Skipping empty show: %s" % item['Name']) 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 @@ -1002,11 +1004,11 @@ class TVShows(Items): try: showid = emby_dbitem[0] pathid = emby_dbitem[2] - log("showid: %s pathid: %s" % (showid, pathid), 1) + log.info("showid: %s pathid: %s" % (showid, pathid)) except TypeError: update_item = False - log("showid: %s not found." % itemid, 2) + log.info("showid: %s not found." % itemid) kodicursor.execute("select coalesce(max(idShow),0) from tvshow") showid = kodicursor.fetchone()[0] + 1 @@ -1019,7 +1021,7 @@ class TVShows(Items): except TypeError: # item is not found, let's recreate it. update_item = False - log("showid: %s missing from Kodi, repairing the entry." % showid, 1) + log.info("showid: %s missing from Kodi, repairing the entry." % showid) # Force re-add episodes after the show is re-created. force_episodes = True @@ -1027,7 +1029,7 @@ class TVShows(Items): if viewtag is None or viewid is None: # Get view tag from emby viewtag, viewid, mediatype = emby.getView_embyId(itemid) - log("View tag found: %s" % viewtag, 2) + log.debug("View tag found: %s" % viewtag) # fileId information checksum = API.getChecksum() @@ -1076,7 +1078,7 @@ class TVShows(Items): ##### UPDATE THE TVSHOW ##### if update_item: - log("UPDATE tvshow itemid: %s - Title: %s" % (itemid, title), 1) + log.info("UPDATE tvshow itemid: %s - Title: %s" % (itemid, title)) # Update the tvshow entry query = ' '.join(( @@ -1094,7 +1096,7 @@ class TVShows(Items): ##### OR ADD THE TVSHOW ##### else: - log("ADD tvshow itemid: %s - Title: %s" % (itemid, title), 1) + log.info("ADD tvshow itemid: %s - Title: %s" % (itemid, title)) # Add top path toppathid = self.kodi_db.addPath(toplevelpath) @@ -1165,7 +1167,7 @@ class TVShows(Items): if force_episodes: # We needed to recreate the show entry. Re-add episodes now. - log("Repairing episodes for showid: %s %s" % (showid, title), 1) + log.info("Repairing episodes for showid: %s %s" % (showid, title)) all_episodes = emby.getEpisodesbyShow(itemid) self.added_episode(all_episodes['Items'], None) @@ -1214,11 +1216,11 @@ class TVShows(Items): episodeid = emby_dbitem[0] fileid = emby_dbitem[1] pathid = emby_dbitem[2] - log("episodeid: %s fileid: %s pathid: %s" % (episodeid, fileid, pathid), 1) + log.info("episodeid: %s fileid: %s pathid: %s" % (episodeid, fileid, pathid)) except TypeError: update_item = False - log("episodeid: %s not found." % itemid, 2) + log.info("episodeid: %s not found." % itemid) # episodeid kodicursor.execute("select coalesce(max(idEpisode),0) from episode") episodeid = kodicursor.fetchone()[0] + 1 @@ -1232,7 +1234,7 @@ class TVShows(Items): except TypeError: # item is not found, let's recreate it. update_item = False - log("episodeid: %s missing from Kodi, repairing the entry." % episodeid, 1) + log.info("episodeid: %s missing from Kodi, repairing the entry." % episodeid) # fileId information checksum = API.getChecksum() @@ -1256,7 +1258,7 @@ class TVShows(Items): seriesId = item['SeriesId'] except KeyError: # Missing seriesId, skip - log("Skipping: %s. SeriesId is missing." % itemid, 1) + log.error("Skipping: %s. SeriesId is missing." % itemid) return False season = item.get('ParentIndexNumber') @@ -1294,7 +1296,7 @@ class TVShows(Items): try: showid = show[0] except TypeError: - log("Skipping: %s. Unable to add series: %s." % (itemid, seriesId)) + log.error("Skipping: %s. Unable to add series: %s." % (itemid, seriesId)) return False seasonid = self.kodi_db.addSeason(showid, season) @@ -1331,7 +1333,7 @@ class TVShows(Items): ##### UPDATE THE EPISODE ##### if update_item: - log("UPDATE episode itemid: %s - Title: %s" % (itemid, title), 1) + log.info("UPDATE episode itemid: %s - Title: %s" % (itemid, title)) # Update the movie entry if self.kodiversion in (16, 17): @@ -1365,7 +1367,7 @@ class TVShows(Items): ##### OR ADD THE EPISODE ##### else: - log("ADD episode itemid: %s - Title: %s" % (itemid, title), 1) + log.info("ADD episode itemid: %s - Title: %s" % (itemid, title)) # Add path pathid = self.kodi_db.addPath(path) @@ -1468,9 +1470,9 @@ class TVShows(Items): kodiid = emby_dbitem[0] fileid = emby_dbitem[1] mediatype = emby_dbitem[4] - log( + log.info( "Update playstate for %s: %s fileid: %s" - % (mediatype, item['Name'], fileid), 1) + % (mediatype, item['Name'], fileid)) except TypeError: return @@ -1487,7 +1489,7 @@ class TVShows(Items): resume = API.adjustResume(userdata['Resume']) total = round(float(runtime), 6) - log("%s New resume point: %s" % (itemid, resume)) + log.debug("%s New resume point: %s" % (itemid, resume)) self.kodi_db.addPlaystate(fileid, resume, total, playcount, dateplayed) if not self.directpath and not resume: @@ -1525,7 +1527,7 @@ class TVShows(Items): pathid = emby_dbitem[2] parentid = emby_dbitem[3] mediatype = emby_dbitem[4] - log("Removing %s kodiid: %s fileid: %s" % (mediatype, kodiid, fileid), 1) + log.info("Removing %s kodiid: %s fileid: %s" % (mediatype, kodiid, fileid)) except TypeError: return @@ -1615,14 +1617,14 @@ class TVShows(Items): self.removeShow(parentid) emby_db.removeItem_byKodiId(parentid, "tvshow") - log("Deleted %s: %s from kodi database" % (mediatype, itemid), 1) + log.info("Deleted %s: %s from kodi database" % (mediatype, itemid)) def removeShow(self, kodiid): kodicursor = self.kodicursor self.artwork.deleteArtwork(kodiid, "tvshow", kodicursor) kodicursor.execute("DELETE FROM tvshow WHERE idShow = ?", (kodiid,)) - log("Removed tvshow: %s." % kodiid, 2) + log.debug("Removed tvshow: %s." % kodiid) def removeSeason(self, kodiid): @@ -1630,7 +1632,7 @@ class TVShows(Items): self.artwork.deleteArtwork(kodiid, "season", kodicursor) kodicursor.execute("DELETE FROM seasons WHERE idSeason = ?", (kodiid,)) - log("Removed season: %s." % kodiid, 2) + log.debug("Removed season: %s." % kodiid) def removeEpisode(self, kodiid, fileid): @@ -1639,7 +1641,7 @@ class TVShows(Items): self.artwork.deleteArtwork(kodiid, "episode", kodicursor) kodicursor.execute("DELETE FROM episode WHERE idEpisode = ?", (kodiid,)) kodicursor.execute("DELETE FROM files WHERE idFile = ?", (fileid,)) - log("Removed episode: %s." % kodiid, 2) + log.debug("Removed episode: %s." % kodiid) class Music(Items): @@ -1713,7 +1715,7 @@ class Music(Items): artistid = emby_dbitem[0] except TypeError: update_item = False - log("artistid: %s not found." % itemid, 2) + log.debug("artistid: %s not found." % itemid) ##### The artist details ##### lastScraped = datetime.now().strftime('%Y-%m-%d %H:%M:%S') @@ -1740,13 +1742,13 @@ class Music(Items): ##### UPDATE THE ARTIST ##### if update_item: - log("UPDATE artist itemid: %s - Name: %s" % (itemid, name), 1) + log.info("UPDATE artist itemid: %s - Name: %s" % (itemid, name)) # Update the checksum in emby table emby_db.updateReference(itemid, checksum) ##### OR ADD THE ARTIST ##### else: - log("ADD artist itemid: %s - Name: %s" % (itemid, name), 1) + log.info("ADD artist itemid: %s - Name: %s" % (itemid, name)) # 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) @@ -1794,7 +1796,7 @@ class Music(Items): albumid = emby_dbitem[0] except TypeError: update_item = False - log("albumid: %s not found." % itemid, 2) + log.debug("albumid: %s not found." % itemid) ##### The album details ##### lastScraped = datetime.now().strftime('%Y-%m-%d %H:%M:%S') @@ -1825,13 +1827,13 @@ class Music(Items): ##### UPDATE THE ALBUM ##### if update_item: - log("UPDATE album itemid: %s - Name: %s" % (itemid, name), 1) + log.info("UPDATE album itemid: %s - Name: %s" % (itemid, name)) # Update the checksum in emby table emby_db.updateReference(itemid, checksum) ##### OR ADD THE ALBUM ##### else: - log("ADD album itemid: %s - Name: %s" % (itemid, name), 1) + log.info("ADD album itemid: %s - Name: %s" % (itemid, name)) # 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) @@ -1964,7 +1966,7 @@ class Music(Items): albumid = emby_dbitem[3] except TypeError: update_item = False - log("songid: %s not found." % itemid, 2) + log.debug("songid: %s not found." % itemid) ##### The song details ##### checksum = API.getChecksum() @@ -2019,7 +2021,7 @@ class Music(Items): ##### UPDATE THE SONG ##### if update_item: - log("UPDATE song itemid: %s - Title: %s" % (itemid, title), 1) + log.info("UPDATE song itemid: %s - Title: %s" % (itemid, title)) # Update path query = "UPDATE path SET strPath = ? WHERE idPath = ?" @@ -2042,7 +2044,7 @@ class Music(Items): ##### OR ADD THE SONG ##### else: - log("ADD song itemid: %s - Title: %s" % (itemid, title), 1) + log.info("ADD song itemid: %s - Title: %s" % (itemid, title)) # Add path pathid = self.kodi_db.addPath(path) @@ -2055,27 +2057,27 @@ class Music(Items): # Verify if there's an album associated. album_name = item.get('Album') if album_name: - log("Creating virtual music album for song: %s." % itemid, 1) + log.info("Creating virtual music album for song: %s." % itemid) 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. - log("Song itemid: %s has no albumId associated." % itemid, 1) + log.error("Song itemid: %s has no albumId associated." % itemid) return False except TypeError: # No album found. Let's create it - log("Album database entry missing.", 1) + log.info("Album database entry missing.") 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] - log("Found albumid: %s" % albumid, 1) + log.info("Found albumid: %s" % albumid) except TypeError: # No album found, create a single's album - log("Failed to add album. Creating singles.", 1) + log.info("Failed to add album. Creating singles.") kodicursor.execute("select coalesce(max(idAlbum),0) from album") albumid = kodicursor.fetchone()[0] + 1 if self.kodiversion == 16: @@ -2257,7 +2259,7 @@ class Music(Items): try: kodiid = emby_dbitem[0] mediatype = emby_dbitem[4] - log("Update playstate for %s: %s" % (mediatype, item['Name']), 1) + log.info("Update playstate for %s: %s" % (mediatype, item['Name'])) except TypeError: return @@ -2296,7 +2298,7 @@ class Music(Items): try: kodiid = emby_dbitem[0] mediatype = emby_dbitem[4] - log("Removing %s kodiid: %s" % (mediatype, kodiid), 1) + log.info("Removing %s kodiid: %s" % (mediatype, kodiid)) except TypeError: return @@ -2364,7 +2366,7 @@ class Music(Items): # Remove artist self.removeArtist(kodiid) - log("Deleted %s: %s from kodi database" % (mediatype, itemid), 1) + log.info("Deleted %s: %s from kodi database" % (mediatype, itemid)) def removeSong(self, kodiId): diff --git a/resources/lib/kodidb_functions.py b/resources/lib/kodidb_functions.py index 86981e7b..9b7aca14 100644 --- a/resources/lib/kodidb_functions.py +++ b/resources/lib/kodidb_functions.py @@ -2,15 +2,19 @@ ################################################################################################## +import logging + import xbmc import api import artwork import clientinfo -from utils import Logging -################################################################################################## +################################################################################################# +log = logging.getLogger("EMBY."+__name__) + +################################################################################################# class Kodidb_Functions(): @@ -18,9 +22,6 @@ class Kodidb_Functions(): def __init__(self, cursor): - - global log - log = Logging(self.__class__.__name__).log self.cursor = cursor @@ -151,7 +152,7 @@ class Kodidb_Functions(): query = "INSERT INTO country(country_id, name) values(?, ?)" self.cursor.execute(query, (country_id, country)) - log("Add country to media, processing: %s" % country, 2) + log.debug("Add country to media, processing: %s" % country) finally: # Assign country to content query = ( @@ -185,7 +186,7 @@ class Kodidb_Functions(): query = "INSERT INTO country(idCountry, strCountry) values(?, ?)" self.cursor.execute(query, (idCountry, country)) - log("Add country to media, processing: %s" % country, 2) + log.debug("Add country to media, processing: %s" % country) finally: # Only movies have a country field @@ -230,7 +231,7 @@ class Kodidb_Functions(): query = "INSERT INTO actor(actor_id, name) values(?, ?)" self.cursor.execute(query, (actorid, name)) - log("Add people to media, processing: %s" % name, 2) + log.debug("Add people to media, processing: %s" % name) finally: # Link person to content @@ -300,7 +301,7 @@ class Kodidb_Functions(): query = "INSERT INTO actors(idActor, strActor) values(?, ?)" self.cursor.execute(query, (actorid, name)) - log("Add people to media, processing: %s" % name, 2) + log.debug("Add people to media, processing: %s" % name) finally: # Link person to content @@ -460,7 +461,7 @@ class Kodidb_Functions(): query = "INSERT INTO genre(genre_id, name) values(?, ?)" self.cursor.execute(query, (genre_id, genre)) - log("Add Genres to media, processing: %s" % genre, 2) + log.debug("Add Genres to media, processing: %s" % genre) finally: # Assign genre to item @@ -505,7 +506,7 @@ class Kodidb_Functions(): query = "INSERT INTO genre(idGenre, strGenre) values(?, ?)" self.cursor.execute(query, (idGenre, genre)) - log("Add Genres to media, processing: %s" % genre, 2) + log.debug("Add Genres to media, processing: %s" % genre) finally: # Assign genre to item @@ -564,7 +565,7 @@ class Kodidb_Functions(): query = "INSERT INTO studio(studio_id, name) values(?, ?)" self.cursor.execute(query, (studioid, studio)) - log("Add Studios to media, processing: %s" % studio, 2) + log.debug("Add Studios to media, processing: %s" % studio) finally: # Assign studio to item query = ( @@ -595,7 +596,7 @@ class Kodidb_Functions(): query = "INSERT INTO studio(idstudio, strstudio) values(?, ?)" self.cursor.execute(query, (studioid, studio)) - log("Add Studios to media, processing: %s" % studio, 2) + log.debug("Add Studios to media, processing: %s" % studio) finally: # Assign studio to item if "movie" in mediatype: @@ -726,7 +727,7 @@ class Kodidb_Functions(): self.cursor.execute(query, (kodiid, mediatype)) # Add tags - log("Adding Tags: %s" % tags, 2) + log.debug("Adding Tags: %s" % tags) for tag in tags: self.addTag(kodiid, tag, mediatype) @@ -748,7 +749,7 @@ class Kodidb_Functions(): except TypeError: # Create the tag, because it does not exist tag_id = self.createTag(tag) - log("Adding tag: %s" % tag, 2) + log.debug("Adding tag: %s" % tag) finally: # Assign tag to item @@ -777,7 +778,7 @@ class Kodidb_Functions(): except TypeError: # Create the tag tag_id = self.createTag(tag) - log("Adding tag: %s" % tag, 2) + log.debug("Adding tag: %s" % tag) finally: # Assign tag to item @@ -813,7 +814,7 @@ class Kodidb_Functions(): query = "INSERT INTO tag(tag_id, name) values(?, ?)" self.cursor.execute(query, (tag_id, name)) - log("Create tag_id: %s name: %s" % (tag_id, name), 2) + log.debug("Create tag_id: %s name: %s" % (tag_id, name)) else: # Kodi Helix query = ' '.join(( @@ -833,13 +834,13 @@ class Kodidb_Functions(): query = "INSERT INTO tag(idTag, strTag) values(?, ?)" self.cursor.execute(query, (tag_id, name)) - log("Create idTag: %s name: %s" % (tag_id, name), 2) + log.debug("Create idTag: %s name: %s" % (tag_id, name)) return tag_id def updateTag(self, oldtag, newtag, kodiid, mediatype): - log("Updating: %s with %s for %s: %s" % (oldtag, newtag, mediatype, kodiid), 2) + log.debug("Updating: %s with %s for %s: %s" % (oldtag, newtag, mediatype, kodiid)) if self.kodiversion in (15, 16, 17): # Kodi Isengard, Jarvis, Krypton @@ -856,7 +857,6 @@ class Kodidb_Functions(): except Exception as e: # The new tag we are going to apply already exists for this item # delete current tag instead - log("Exception: %s" % e, 1) query = ' '.join(( "DELETE FROM tag_link", @@ -880,7 +880,6 @@ class Kodidb_Functions(): except Exception as e: # The new tag we are going to apply already exists for this item # delete current tag instead - log("Exception: %s" % e, 1) query = ' '.join(( "DELETE FROM taglinks", @@ -941,7 +940,7 @@ class Kodidb_Functions(): def createBoxset(self, boxsetname): - log("Adding boxset: %s" % boxsetname, 2) + log.debug("Adding boxset: %s" % boxsetname) query = ' '.join(( "SELECT idSet", diff --git a/resources/lib/kodimonitor.py b/resources/lib/kodimonitor.py index 6ba9e76d..66e0b661 100644 --- a/resources/lib/kodimonitor.py +++ b/resources/lib/kodimonitor.py @@ -3,6 +3,7 @@ ################################################################################################# import json +import logging import xbmc import xbmcgui @@ -11,7 +12,11 @@ import clientinfo import downloadutils import embydb_functions as embydb import playbackutils as pbutils -from utils import Logging, window, settings, kodiSQL +from utils import window, settings, kodiSQL + +################################################################################################# + +log = logging.getLogger("EMBY."+__name__) ################################################################################################# @@ -21,23 +26,20 @@ class KodiMonitor(xbmc.Monitor): def __init__(self): - global log - log = Logging(self.__class__.__name__).log - self.clientInfo = clientinfo.ClientInfo() self.addonName = self.clientInfo.getAddonName() self.doUtils = downloadutils.DownloadUtils() - log("Kodi monitor started.", 1) + log.info("Kodi monitor started.") def onScanStarted(self, library): - log("Kodi library scan %s running." % library, 2) + log.debug("Kodi library scan %s running." % library) if library == "video": window('emby_kodiScan', value="true") def onScanFinished(self, library): - log("Kodi library scan %s finished." % library, 2) + log.debug("Kodi library scan %s finished." % library) if library == "video": window('emby_kodiScan', clear=True) @@ -62,14 +64,14 @@ class KodiMonitor(xbmc.Monitor): currentLog = settings('logLevel') if window('emby_logLevel') != currentLog: # The log level changed, set new prop - log("New log level: %s" % currentLog, 1) + log.info("New log level: %s" % currentLog) window('emby_logLevel', value=currentLog) def onNotification(self, sender, method, data): doUtils = self.doUtils if method not in ("Playlist.OnAdd"): - log("Method: %s Data: %s" % (method, data), 1) + log.info("Method: %s Data: %s" % (method, data)) if data: data = json.loads(data,'utf-8') @@ -82,7 +84,7 @@ class KodiMonitor(xbmc.Monitor): kodiid = item['id'] item_type = item['type'] except (KeyError, TypeError): - log("Item is invalid for playstate update.", 1) + log.info("Item is invalid for playstate update.") else: if ((settings('useDirectPaths') == "1" and not item_type == "song") or (item_type == "song" and settings('enableMusic') == "true")): @@ -94,11 +96,11 @@ class KodiMonitor(xbmc.Monitor): try: itemid = emby_dbitem[0] except TypeError: - log("No kodiId returned.", 1) + log.info("No kodiId returned.") else: url = "{server}/emby/Users/{UserId}/Items/%s?format=json" % itemid result = doUtils.downloadUrl(url) - log("Item: %s" % result, 2) + log.debug("Item: %s" % result) playurl = None count = 0 @@ -130,7 +132,7 @@ class KodiMonitor(xbmc.Monitor): kodiid = item['id'] item_type = item['type'] except (KeyError, TypeError): - log("Item is invalid for playstate update.", 1) + log.info("Item is invalid for playstate update.") else: # Send notification to the server. embyconn = kodiSQL('emby') @@ -140,7 +142,7 @@ class KodiMonitor(xbmc.Monitor): try: itemid = emby_dbitem[0] except TypeError: - log("Could not find itemid in emby database.", 1) + log.info("Could not find itemid in emby database.") else: # Stop from manually marking as watched unwatched, with actual playback. if window('emby_skipWatched%s' % itemid) == "true": @@ -151,10 +153,10 @@ class KodiMonitor(xbmc.Monitor): url = "{server}/emby/Users/{UserId}/PlayedItems/%s?format=json" % itemid if playcount != 0: doUtils.downloadUrl(url, action_type="POST") - log("Mark as watched for itemid: %s" % itemid, 1) + log.info("Mark as watched for itemid: %s" % itemid) else: doUtils.downloadUrl(url, action_type="DELETE") - log("Mark as unwatched for itemid: %s" % itemid, 1) + log.info("Mark as unwatched for itemid: %s" % itemid) finally: embycursor.close() @@ -197,7 +199,7 @@ class KodiMonitor(xbmc.Monitor): elif method == "System.OnSleep": # Connection is going to sleep - log("Marking the server as offline. System.OnSleep activating.", 1) + log.info("Marking the server as offline. System.OnSleep activated.") window('emby_online', value="sleep") elif method == "System.OnWake": diff --git a/resources/lib/librarysync.py b/resources/lib/librarysync.py index d5608a19..72277fa0 100644 --- a/resources/lib/librarysync.py +++ b/resources/lib/librarysync.py @@ -2,6 +2,7 @@ ################################################################################################## +import logging import sqlite3 import threading from datetime import datetime, timedelta, time @@ -20,10 +21,13 @@ import kodidb_functions as kodidb import read_embyserver as embyserver import userclient import videonodes -from utils import Logging, window, settings, language as lang +from utils import window, settings, language as lang ################################################################################################## +log = logging.getLogger("EMBY."+__name__) + +################################################################################################## class LibrarySync(threading.Thread): @@ -43,9 +47,6 @@ class LibrarySync(threading.Thread): def __init__(self): - global log - log = Logging(self.__class__.__name__).log - self.__dict__ = self._shared_state self.monitor = xbmc.Monitor() @@ -65,7 +66,7 @@ class LibrarySync(threading.Thread): dialog = xbmcgui.DialogProgressBG() dialog.create("Emby for Kodi", title) - log("Show progress dialog: %s" % title, 2) + log.debug("Show progress dialog: %s" % title) return dialog @@ -85,7 +86,7 @@ class LibrarySync(threading.Thread): for plugin in result: if plugin['Name'] == "Emby.Kodi Sync Queue": - log("Found server plugin.", 2) + log.debug("Found server plugin.") completed = self.fastSync() break @@ -105,7 +106,7 @@ class LibrarySync(threading.Thread): lastSync = "2010-01-01T00:00:00Z" lastSyncTime = utils.convertDate(lastSync) - log("Last sync run: %s" % lastSyncTime, 1) + log.info("Last sync run: %s" % lastSyncTime) # get server RetentionDateTime result = self.doUtils("{server}/emby/Emby.Kodi.SyncQueue/GetServerDateTime?format=json") @@ -115,11 +116,11 @@ class LibrarySync(threading.Thread): retention_time = "2010-01-01T00:00:00Z" retention_time = utils.convertDate(retention_time) - log("RetentionDateTime: %s" % retention_time, 1) + log.info("RetentionDateTime: %s" % retention_time) # 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) + log.info("Fast sync server retention insufficient, fall back to full sync") return False params = {'LastUpdateDT': lastSync} @@ -136,11 +137,11 @@ class LibrarySync(threading.Thread): } except (KeyError, TypeError): - log("Failed to retrieve latest updates using fast sync.", 1) + log.error("Failed to retrieve latest updates using fast sync.") return False else: - log("Fast sync changes: %s" % result, 1) + log.info("Fast sync changes: %s" % result) for action in processlist: self.triage_items(action, processlist[action]) @@ -158,14 +159,14 @@ class LibrarySync(threading.Thread): except Exception as e: # If the server plugin is not installed or an error happened. - log("An exception occurred: %s" % e, 1) + log.error("An exception occurred: %s" % e) 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) + log.info("New sync time: client time -%s min: %s" % (overlap, lastSync)) 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) + log.info("New sync time: server time -%s min: %s" % (overlap, lastSync)) finally: settings('LastIncrementalSync', value=lastSync) @@ -185,20 +186,20 @@ class LibrarySync(threading.Thread): while kodidb_scan: - log("Kodi scan is running. Waiting...", 1) + log.info("Kodi scan is running. Waiting...") kodidb_scan = window('emby_kodiScan') == "true" if self.shouldStop(): - log("Commit unsuccessful. Sync terminated.", 1) + log.info("Commit unsuccessful. Sync terminated.") break if self.monitor.waitForAbort(1): # Abort was requested while waiting. We should exit - log("Commit unsuccessful.", 1) + log.info("Commit unsuccessful.") break else: connection.commit() - log("Commit successful.", 1) + log.info("Commit successful.") def fullSync(self, manualrun=False, repair=False): # Only run once when first setting up. Can be run manually. @@ -268,8 +269,8 @@ class LibrarySync(threading.Thread): self.dbCommit(kodiconn) embyconn.commit() elapsedTime = datetime.now() - startTime - log("SyncDatabase (finished %s in: %s)" - % (itemtype, str(elapsedTime).split('.')[0]), 1) + log.info("SyncDatabase (finished %s in: %s)" + % (itemtype, str(elapsedTime).split('.')[0])) else: # Close the Kodi cursor kodicursor.close() @@ -296,8 +297,8 @@ class LibrarySync(threading.Thread): musicconn.commit() embyconn.commit() elapsedTime = datetime.now() - startTime - log("SyncDatabase (finished music in: %s)" - % (str(elapsedTime).split('.')[0]), 1) + log.info("SyncDatabase (finished music in: %s)" + % (str(elapsedTime).split('.')[0])) musiccursor.close() if pDialog: @@ -363,7 +364,7 @@ class LibrarySync(threading.Thread): if view['type'] == "mixed": sorted_views.append(view['name']) sorted_views.append(view['name']) - log("Sorted views: %s" % sorted_views, 1) + log.info("Sorted views: %s" % sorted_views) # total nodes for window properties self.vnodes.clearProperties() @@ -423,7 +424,7 @@ class LibrarySync(threading.Thread): 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) + log.info("Couldn't find corresponding grouped view: %s" % sorted_views) # Failsafe try: @@ -439,7 +440,7 @@ class LibrarySync(threading.Thread): current_tagid = view[2] except TypeError: - log("Creating viewid: %s in Emby database." % folderid, 1) + log.info("Creating viewid: %s in Emby database." % folderid) tagid = kodi_db.createTag(foldername) # Create playlist for the video library if (foldername not in playlists and @@ -458,12 +459,12 @@ class LibrarySync(threading.Thread): emby_db.addView(folderid, foldername, viewtype, tagid) else: - log(' '.join(( + log.debug(' '.join(( "Found viewid: %s" % folderid, "viewname: %s" % current_viewname, "viewtype: %s" % current_viewtype, - "tagid: %s" % current_tagid)), 2) + "tagid: %s" % current_tagid))) # View is still valid try: @@ -474,7 +475,7 @@ class LibrarySync(threading.Thread): # View was modified, update with latest info if current_viewname != foldername: - log("viewid: %s new viewname: %s" % (folderid, foldername), 1) + log.info("viewid: %s new viewname: %s" % (folderid, foldername)) tagid = kodi_db.createTag(foldername) # Update view with new info @@ -542,7 +543,7 @@ class LibrarySync(threading.Thread): window('Emby.nodes.total', str(totalnodes)) # Remove any old referenced views - log("Removing views: %s" % current_views, 1) + log.info("Removing views: %s" % current_views) for view in current_views: emby_db.removeView(view) @@ -554,7 +555,7 @@ class LibrarySync(threading.Thread): views = emby_db.getView_byType('movies') views += emby_db.getView_byType('mixed') - log("Media folders: %s" % views, 1) + log.info("Media folders: %s" % views) ##### PROCESS MOVIES ##### for view in views: @@ -589,7 +590,7 @@ class LibrarySync(threading.Thread): count += 1 movies.add_update(embymovie, view['name'], view['id']) else: - log("Movies finished.", 2) + log.debug("Movies finished.") ##### PROCESS BOXSETS ##### @@ -616,7 +617,7 @@ class LibrarySync(threading.Thread): count += 1 movies.add_updateBoxset(boxset) else: - log("Boxsets finished.", 2) + log.debug("Boxsets finished.") return True @@ -627,7 +628,7 @@ class LibrarySync(threading.Thread): mvideos = itemtypes.MusicVideos(embycursor, kodicursor) views = emby_db.getView_byType('musicvideos') - log("Media folders: %s" % views, 1) + log.info("Media folders: %s" % views) for view in views: @@ -664,7 +665,7 @@ class LibrarySync(threading.Thread): count += 1 mvideos.add_update(embymvideo, viewName, viewId) else: - log("MusicVideos finished.", 2) + log.debug("MusicVideos finished.") return True @@ -676,7 +677,7 @@ class LibrarySync(threading.Thread): views = emby_db.getView_byType('tvshows') views += emby_db.getView_byType('mixed') - log("Media folders: %s" % views, 1) + log.info("Media folders: %s" % views) for view in views: @@ -722,7 +723,7 @@ class LibrarySync(threading.Thread): pdialog.update(percentage, message="%s - %s" % (title, episodetitle)) tvshows.add_updateEpisode(episode) else: - log("TVShows finished.", 2) + log.debug("TVShows finished.") return True @@ -763,7 +764,7 @@ class LibrarySync(threading.Thread): process[itemtype][1](embyitem) else: - log("%s finished." % itemtype, 2) + log.debug("%s finished." % itemtype) return True @@ -784,7 +785,7 @@ class LibrarySync(threading.Thread): itemids.append(item['ItemId']) items = itemids - log("Queue %s: %s" % (process, items), 1) + log.info("Queue %s: %s" % (process, items)) processlist[process].extend(items) def incrementalSync(self): @@ -806,7 +807,7 @@ class LibrarySync(threading.Thread): incSyncIndicator = int(settings('incSyncIndicator')) totalUpdates = len(self.addedItems) + len(self.updateItems) + len(self.userdataItems) + len(self.removeItems) - log("incSyncIndicator=" + str(incSyncIndicator) + " totalUpdates=" + str(totalUpdates), 1) + log.info("incSyncIndicator=" + str(incSyncIndicator) + " totalUpdates=" + str(totalUpdates)) if incSyncIndicator != -1 and totalUpdates > incSyncIndicator: # Only present dialog if we are going to process items @@ -859,7 +860,7 @@ class LibrarySync(threading.Thread): if update_embydb: update_embydb = False - log("Updating emby database.", 1) + log.info("Updating emby database.") embyconn.commit() self.saveLastSync() @@ -868,7 +869,7 @@ class LibrarySync(threading.Thread): self.forceLibraryUpdate = False self.dbCommit(kodiconn) - log("Updating video library.", 1) + log.info("Updating video library.") window('emby_kodiScan', value="true") xbmc.executebuiltin('UpdateLibrary(video)') @@ -881,7 +882,7 @@ class LibrarySync(threading.Thread): def compareDBVersion(self, current, minimum): # It returns True is database is up to date. False otherwise. - log("current: %s minimum: %s" % (current, minimum), 1) + log.info("current: %s minimum: %s" % (current, minimum)) currMajor, currMinor, currPatch = current.split(".") minMajor, minMinor, minPatch = minimum.split(".") @@ -906,6 +907,7 @@ class LibrarySync(threading.Thread): "Library sync thread has exited! " "You should restart Kodi now. " "Please report this on the forum.")) + log.exception(e) raise def run_internal(self): @@ -914,7 +916,7 @@ class LibrarySync(threading.Thread): startupComplete = False - log("---===### Starting LibrarySync ###===---", 0) + log.warn("---===### Starting LibrarySync ###===---") while not self.monitor.abortRequested(): @@ -932,12 +934,12 @@ class LibrarySync(threading.Thread): uptoDate = self.compareDBVersion(currentVersion, minVersion) if not uptoDate: - log("Database version out of date: %s minimum version required: %s" - % (currentVersion, minVersion), 0) + log.warn("Database version out of date: %s minimum version required: %s" + % (currentVersion, minVersion)) resp = dialog.yesno(lang(29999), lang(33022)) if not resp: - log("Database version is out of date! USER IGNORED!", 0) + log.warn("Database version is out of date! USER IGNORED!") dialog.ok(lang(29999), lang(33023)) else: utils.reset() @@ -952,11 +954,11 @@ class LibrarySync(threading.Thread): videoDb = utils.getKodiVideoDBPath() if not xbmcvfs.exists(videoDb): # Database does not exists - log( + log.error( "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) + "to know which Kodi versions are supported.") dialog.ok( heading=lang(29999), @@ -964,13 +966,13 @@ class LibrarySync(threading.Thread): break # Run start up sync - log("Database version: %s" % settings('dbCreatedWithVersion'), 0) - log("SyncDatabase (started)", 1) + log.warn("Database version: %s" % settings('dbCreatedWithVersion')) + log.info("SyncDatabase (started)") startTime = datetime.now() librarySync = self.startSync() elapsedTime = datetime.now() - startTime - log("SyncDatabase (finished in: %s) %s" - % (str(elapsedTime).split('.')[0], librarySync), 1) + log.info("SyncDatabase (finished in: %s) %s" + % (str(elapsedTime).split('.')[0], librarySync)) # Only try the initial sync once per kodi session regardless # This will prevent an infinite loop in case something goes wrong. startupComplete = True @@ -984,32 +986,32 @@ class LibrarySync(threading.Thread): # Set in kodimonitor.py window('emby_onWake', clear=True) if window('emby_syncRunning') != "true": - log("SyncDatabase onWake (started)", 0) + log.info("SyncDatabase onWake (started)") librarySync = self.startSync() - log("SyncDatabase onWake (finished) %s" % librarySync, 0) + log.info("SyncDatabase onWake (finished) %s" % librarySync) if self.stop_thread: # Set in service.py - log("Service terminated thread.", 2) + log.debug("Service terminated thread.") break if self.monitor.waitForAbort(1): # Abort was requested while waiting. We should exit break - log("###===--- LibrarySync Stopped ---===###", 0) + log.warn("###===--- LibrarySync Stopped ---===###") def stopThread(self): self.stop_thread = True - log("Ending thread...", 2) + log.debug("Ending thread...") def suspendThread(self): self.suspend_thread = True - log("Pausing thread...", 0) + log.debug("Pausing thread...") def resumeThread(self): self.suspend_thread = False - log("Resuming thread...", 0) + log.debug("Resuming thread...") class ManualSync(LibrarySync): @@ -1032,7 +1034,7 @@ class ManualSync(LibrarySync): views = emby_db.getView_byType('movies') views += emby_db.getView_byType('mixed') - log("Media folders: %s" % views, 1) + log.info("Media folders: %s" % views) # Pull the list of movies and boxsets in Kodi try: @@ -1079,7 +1081,7 @@ class ManualSync(LibrarySync): # 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) + log.info("Movies to update for %s: %s" % (viewName, updatelist)) embymovies = self.emby.getFullItems(updatelist) total = len(updatelist) del updatelist[:] @@ -1121,7 +1123,7 @@ class ManualSync(LibrarySync): updatelist.append(itemid) embyboxsets.append(boxset) - log("Boxsets to update: %s" % updatelist, 1) + log.info("Boxsets to update: %s" % updatelist) total = len(updatelist) if pdialog: @@ -1145,13 +1147,13 @@ class ManualSync(LibrarySync): if kodimovie not in all_embymoviesIds: movies.remove(kodimovie) else: - log("Movies compare finished.", 1) + log.info("Movies compare finished.") for boxset in all_kodisets: if boxset not in all_embyboxsetsIds: movies.remove(boxset) else: - log("Boxsets compare finished.", 1) + log.info("Boxsets compare finished.") return True @@ -1162,7 +1164,7 @@ class ManualSync(LibrarySync): mvideos = itemtypes.MusicVideos(embycursor, kodicursor) views = emby_db.getView_byType('musicvideos') - log("Media folders: %s" % views, 1) + log.info("Media folders: %s" % views) # Pull the list of musicvideos in Kodi try: @@ -1202,7 +1204,7 @@ class ManualSync(LibrarySync): # 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) + log.info("MusicVideos to update for %s: %s" % (viewName, updatelist)) embymvideos = self.emby.getFullItems(updatelist) total = len(updatelist) del updatelist[:] @@ -1229,7 +1231,7 @@ class ManualSync(LibrarySync): if kodimvideo not in all_embymvideosIds: mvideos.remove(kodimvideo) else: - log("MusicVideos compare finished.", 1) + log.info("MusicVideos compare finished.") return True @@ -1241,7 +1243,7 @@ class ManualSync(LibrarySync): views = emby_db.getView_byType('tvshows') views += emby_db.getView_byType('mixed') - log("Media folders: %s" % views, 1) + log.info("Media folders: %s" % views) # Pull the list of tvshows and episodes in Kodi try: @@ -1288,7 +1290,7 @@ class ManualSync(LibrarySync): # 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) + log.info("TVShows to update for %s: %s" % (viewName, updatelist)) embytvshows = self.emby.getFullItems(updatelist) total = len(updatelist) del updatelist[:] @@ -1332,7 +1334,7 @@ class ManualSync(LibrarySync): # 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) + log.info("Episodes to update for %s: %s" % (viewName, updatelist)) embyepisodes = self.emby.getFullItems(updatelist) total = len(updatelist) del updatelist[:] @@ -1357,13 +1359,13 @@ class ManualSync(LibrarySync): if koditvshow not in all_embytvshowsIds: tvshows.remove(koditvshow) else: - log("TVShows compare finished.", 1) + log.info("TVShows compare finished.") for kodiepisode in all_kodiepisodes: if kodiepisode not in all_embyepisodesIds: tvshows.remove(kodiepisode) else: - log("Episodes compare finished.", 1) + log.info("Episodes compare finished.") return True @@ -1429,7 +1431,7 @@ class ManualSync(LibrarySync): 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" % (data_type, updatelist), 1) + log.info("%s to update: %s" % (data_type, updatelist)) embyitems = self.emby.getFullItems(updatelist) total = len(updatelist) del updatelist[:] @@ -1450,15 +1452,15 @@ class ManualSync(LibrarySync): if kodiartist not in all_embyartistsIds and all_kodiartists[kodiartist] is not None: music.remove(kodiartist) else: - log("Artist compare finished.", 1) + log.info("Artist compare finished.") for kodialbum in all_kodialbums: if kodialbum not in all_embyalbumsIds: music.remove(kodialbum) else: - log("Albums compare finished.", 1) + log.info("Albums compare finished.") for kodisong in all_kodisongs: if kodisong not in all_embysongsIds: music.remove(kodisong) else: - log("Songs compare finished.", 1) + log.info("Songs compare finished.") return True \ No newline at end of file diff --git a/resources/lib/loghandler.py b/resources/lib/loghandler.py new file mode 100644 index 00000000..616ea1b9 --- /dev/null +++ b/resources/lib/loghandler.py @@ -0,0 +1,73 @@ +# -*- coding: utf-8 -*- + +################################################################################################## + +import logging +import xbmc + +from utils import window + +################################################################################################## + + +def config(): + + logger = logging.getLogger('EMBY') + logger.addHandler(LogHandler()) + logger.setLevel(logging.DEBUG) + + +class LogHandler(logging.StreamHandler): + + def __init__(self): + + logging.StreamHandler.__init__(self) + self.setFormatter(MyFormatter()) + + def emit(self, record): + + if self._getLogLevel(record.levelno): + try: + xbmc.log(self.format(record)) + except UnicodeEncodeError: + xbmc.log(self.format(record).encode('utf-8')) + + def _getLogLevel(self, level): + + levels = { + logging.ERROR: 0, + logging.WARNING: 0, + logging.INFO: 1, + logging.DEBUG: 2, + } + try: + logLevel = int(window('emby_logLevel')) + except ValueError: + logLevel = 0 + + return logLevel >= levels[level] + + +class MyFormatter(logging.Formatter): + + def __init__(self, fmt="%(name)s -> %(message)s"): + + logging.Formatter.__init__(self, fmt) + + def format(self, record): + + # Save the original format configured by the user + # when the logger formatter was instantiated + format_orig = self._fmt + + # Replace the original format with one customized by logging level + if record.levelno in (logging.DEBUG, logging.ERROR): + self._fmt = '%(name)s -> %(levelname)s:: %(message)s' + + # Call the original formatter class to do the grunt work + result = logging.Formatter.format(self, record) + + # Restore the original format configured by the user + self._fmt = format_orig + + return result \ No newline at end of file diff --git a/resources/lib/musicutils.py b/resources/lib/musicutils.py index b89c12da..1f5bd9e9 100644 --- a/resources/lib/musicutils.py +++ b/resources/lib/musicutils.py @@ -2,6 +2,7 @@ ################################################################################################# +import logging import os import xbmc @@ -14,8 +15,11 @@ from mutagen import id3 import base64 import read_embyserver as embyserver -from utils import Logging, window -log = Logging('MusicTools').log +from utils import window + +################################################################################################# + +log = logging.getLogger("EMBY."+__name__) ################################################################################################# @@ -25,7 +29,7 @@ def getRealFileName(filename, isTemp=False): #get the filename path accessible by python if possible... if not xbmcvfs.exists(filename): - log("File does not exist! %s" % filename, 0) + log.warn("File does not exist! %s" % filename) return (False, "") #if we use os.path method on older python versions (sunch as some android builds), we need to pass arguments as string @@ -102,7 +106,7 @@ def getAdditionalSongTags(embyid, emby_rating, API, kodicursor, emby_db, enablei elif file_rating is None and not currentvalue: return (emby_rating, comment, False) - log("getAdditionalSongTags --> embyid: %s - emby_rating: %s - file_rating: %s - current rating in kodidb: %s" %(embyid, emby_rating, file_rating, currentvalue)) + log.info("getAdditionalSongTags --> embyid: %s - emby_rating: %s - file_rating: %s - current rating in kodidb: %s" %(embyid, emby_rating, file_rating, currentvalue)) updateFileRating = False updateEmbyRating = False @@ -181,7 +185,7 @@ def getSongTags(file): hasEmbeddedCover = False isTemp,filename = getRealFileName(file) - log( "getting song ID3 tags for " + filename) + log.info( "getting song ID3 tags for " + filename) try: ###### FLAC FILES ############# @@ -215,14 +219,14 @@ def getSongTags(file): #POPM rating is 0-255 and needs to be converted to 0-5 range if rating > 5: rating = (rating / 255) * 5 else: - log( "Not supported fileformat or unable to access file: %s" %(filename)) + log.info( "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 ? - log("Exception in getSongTags %s" % e,0) + log.error("Exception in getSongTags %s" % e) rating = None #remove tempfile if needed.... @@ -244,7 +248,7 @@ def updateRatingToFile(rating, file): xbmcvfs.copy(file, tempfile) tempfile = xbmc.translatePath(tempfile).decode("utf-8") - log( "setting song rating: %s for filename: %s - using tempfile: %s" %(rating,file,tempfile)) + log.info( "setting song rating: %s for filename: %s - using tempfile: %s" %(rating,file,tempfile)) if not tempfile: return @@ -261,7 +265,7 @@ def updateRatingToFile(rating, file): audio.add(id3.POPM(email="Windows Media Player 9 Series", rating=calcrating, count=1)) audio.save() else: - log( "Not supported fileformat: %s" %(tempfile)) + log.info( "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 @@ -272,14 +276,14 @@ def updateRatingToFile(rating, file): xbmcvfs.delete(file) xbmcvfs.copy(tempfile,file) else: - log( "Checksum mismatch for filename: %s - using tempfile: %s - not proceeding with file overwite!" %(rating,file,tempfile)) + log.info( "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 ? - log("Exception in updateRatingToFile %s" %e,0) + log.error("Exception in updateRatingToFile %s" % e) \ No newline at end of file diff --git a/resources/lib/playbackutils.py b/resources/lib/playbackutils.py index f4cdd8b9..f089195a 100644 --- a/resources/lib/playbackutils.py +++ b/resources/lib/playbackutils.py @@ -3,6 +3,7 @@ ################################################################################################# import json +import logging import sys import xbmc @@ -11,13 +12,16 @@ import xbmcplugin import api import artwork -import clientinfo import downloadutils import playutils as putils import playlist import read_embyserver as embyserver import shutil -from utils import Logging, window, settings, language as lang +from utils import window, settings, language as lang + +################################################################################################# + +log = logging.getLogger("EMBY."+__name__) ################################################################################################# @@ -27,14 +31,9 @@ class PlaybackUtils(): def __init__(self, item): - global log - log = Logging(self.__class__.__name__).log - 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 = window('emby_currUser') @@ -50,7 +49,7 @@ class PlaybackUtils(): listitem = xbmcgui.ListItem() playutils = putils.PlayUtils(self.item) - log("Play called.", 1) + log.info("Play called.") playurl = playutils.getPlayUrl() if not playurl: return xbmcplugin.setResolvedUrl(int(sys.argv[1]), False, listitem) @@ -73,9 +72,9 @@ class PlaybackUtils(): introsPlaylist = False dummyPlaylist = False - log("Playlist start position: %s" % startPos, 2) - log("Playlist plugin position: %s" % currentPosition, 2) - log("Playlist size: %s" % sizePlaylist, 2) + log.debug("Playlist start position: %s" % startPos) + log.debug("Playlist plugin position: %s" % currentPosition) + log.debug("Playlist size: %s" % sizePlaylist) ############### RESUME POINT ################ @@ -87,11 +86,11 @@ class PlaybackUtils(): if not propertiesPlayback: window('emby_playbackProps', value="true") - log("Setting up properties in playlist.", 1) + log.info("Setting up properties in playlist.") if not homeScreen and not seektime and window('emby_customPlaylist') != "true": - log("Adding dummy file to playlist.", 2) + log.debug("Adding dummy file to playlist.") dummyPlaylist = True playlist.add(playurl, listitem, index=startPos) # Remove the original item from playlist @@ -115,14 +114,14 @@ class PlaybackUtils(): if not resp: # User selected to not play trailers getTrailers = False - log("Skip trailers.", 1) + log.info("Skip trailers.") 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) + log.info("Adding Intro: %s" % introPlayurl) # Set listitem and properties for intros pbutils = PlaybackUtils(intro) @@ -138,7 +137,7 @@ class PlaybackUtils(): if homeScreen and not seektime and not sizePlaylist: # Extend our current playlist with the actual item to play # only if there's no playlist first - log("Adding main item to playlist.", 1) + log.info("Adding main item to playlist.") self.pl.addtoPlaylist(dbid, self.item['Type'].lower()) # Ensure that additional parts are played after the main item @@ -155,7 +154,7 @@ class PlaybackUtils(): additionalListItem = xbmcgui.ListItem() additionalPlayurl = putils.PlayUtils(part).getPlayUrl() - log("Adding additional part: %s" % partcount, 1) + log.info("Adding additional part: %s" % partcount) # Set listitem and properties for each additional parts pbutils = PlaybackUtils(part) @@ -169,13 +168,13 @@ class PlaybackUtils(): 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) + log.info("Processed as a playlist. First item is skipped.") 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) + log.debug("Resetting properties playback flag.") window('emby_playbackProps', clear=True) #self.pl.verifyPlaylist() @@ -185,7 +184,7 @@ class PlaybackUtils(): if window('emby_%s.playmethod' % playurl) == "Transcode": # Filter ISO since Emby does not probe anymore if self.item.get('VideoType') == "Iso": - log("Skipping audio/subs prompt, ISO detected.", 1) + log.info("Skipping audio/subs prompt, ISO detected.") else: playurl = playutils.audioSubsPref(playurl, listitem) window('emby_%s.playmethod' % playurl, value="Transcode") @@ -196,18 +195,18 @@ class PlaybackUtils(): ############### PLAYBACK ################ if homeScreen and seektime and window('emby_customPlaylist') != "true": - log("Play as a widget item.", 1) + log.info("Play as a widget item.") 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) + log.info("Play playlist.") xbmc.Player().play(playlist, startpos=startPos) else: - log("Play as a regular item.", 1) + log.info("Play as a regular item.") xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, listitem) def setProperties(self, playurl, listitem): diff --git a/resources/lib/player.py b/resources/lib/player.py index dfefa0ec..1fd19135 100644 --- a/resources/lib/player.py +++ b/resources/lib/player.py @@ -3,6 +3,7 @@ ################################################################################################# import json +import logging import xbmc import xbmcgui @@ -11,7 +12,11 @@ import clientinfo import downloadutils import kodidb_functions as kodidb import websocket_client as wsc -from utils import Logging, window, settings, language as lang +from utils import window, settings, language as lang + +################################################################################################# + +log = logging.getLogger("EMBY."+__name__) ################################################################################################# @@ -28,18 +33,14 @@ class Player(xbmc.Player): def __init__(self): - global log - log = Logging(self.__class__.__name__).log - 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() - log("Starting playback monitor.", 2) + log.debug("Starting playback monitor.") def GetPlayStats(self): @@ -63,7 +64,7 @@ class Player(xbmc.Player): except: pass if count == 5: # try 5 times - log("Cancelling playback report...", 1) + log.info("Cancelling playback report...") break else: count += 1 @@ -80,12 +81,12 @@ class Player(xbmc.Player): 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) + log.info("Could not find itemId, cancelling playback report...") break else: tryCount += 1 else: - log("ONPLAYBACK_STARTED: %s itemid: %s" % (currentFile, itemId), 0) + log.info("ONPLAYBACK_STARTED: %s itemid: %s" % (currentFile, itemId)) # Only proceed if an itemId was found. embyitem = "emby_%s" % currentFile @@ -98,7 +99,7 @@ class Player(xbmc.Player): 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) + log.info("Seeking to: %s" % customseek) self.xbmcplayer.seekTime(int(customseek)/10000000.0) window('emby_customPlaylist.seektime', clear=True) @@ -185,7 +186,7 @@ class Player(xbmc.Player): if mapping: # Set in playbackutils.py - log("Mapping for external subtitles index: %s" % mapping, 2) + log.debug("Mapping for external subtitles index: %s" % mapping) externalIndex = json.loads(mapping) if externalIndex.get(str(indexSubs)): @@ -203,7 +204,7 @@ class Player(xbmc.Player): # Post playback to server - log("Sending POST play started: %s." % postdata, 2) + log.debug("Sending POST play started: %s." % postdata) self.doUtils(url, postBody=postdata, action_type="POST") # Ensure we do have a runtime @@ -211,7 +212,7 @@ class Player(xbmc.Player): runtime = int(runtime) except ValueError: runtime = self.xbmcplayer.getTotalTime() - log("Runtime is missing, Kodi runtime: %s" % runtime, 1) + log.info("Runtime is missing, Kodi runtime: %s" % runtime) # Save data map for updates and position calls data = { @@ -228,7 +229,7 @@ class Player(xbmc.Player): } self.played_info[currentFile] = data - log("ADDING_FILE: %s" % self.played_info, 1) + log.info("ADDING_FILE: %s" % self.played_info) # log some playback stats '''if(itemType != None): @@ -247,7 +248,7 @@ class Player(xbmc.Player): def reportPlayback(self): - log("reportPlayback Called", 2) + log.debug("reportPlayback Called") # Get current file currentFile = self.currentFile @@ -345,7 +346,7 @@ class Player(xbmc.Player): if mapping: # Set in PlaybackUtils.py - log("Mapping for external subtitles index: %s" % mapping, 2) + log.debug("Mapping for external subtitles index: %s" % mapping) externalIndex = json.loads(mapping) if externalIndex.get(str(indexSubs)): @@ -365,13 +366,13 @@ class Player(xbmc.Player): # Report progress via websocketclient postdata = json.dumps(postdata) - log("Report: %s" % postdata, 2) + log.debug("Report: %s" % postdata) self.ws.sendProgressUpdate(postdata) def onPlayBackPaused(self): currentFile = self.currentFile - log("PLAYBACK_PAUSED: %s" % currentFile, 2) + log.debug("PLAYBACK_PAUSED: %s" % currentFile) if self.played_info.get(currentFile): self.played_info[currentFile]['paused'] = True @@ -381,7 +382,7 @@ class Player(xbmc.Player): def onPlayBackResumed(self): currentFile = self.currentFile - log("PLAYBACK_RESUMED: %s" % currentFile, 2) + log.debug("PLAYBACK_RESUMED: %s" % currentFile) if self.played_info.get(currentFile): self.played_info[currentFile]['paused'] = False @@ -391,7 +392,7 @@ class Player(xbmc.Player): def onPlayBackSeek(self, time, seekOffset): # Make position when seeking a bit more accurate currentFile = self.currentFile - log("PLAYBACK_SEEK: %s" % currentFile, 2) + log.debug("PLAYBACK_SEEK: %s" % currentFile) if self.played_info.get(currentFile): position = self.xbmcplayer.getTime() @@ -401,16 +402,16 @@ class Player(xbmc.Player): def onPlayBackStopped(self): # Will be called when user stops xbmc playing a file - log("ONPLAYBACK_STOPPED", 2) + log.debug("ONPLAYBACK_STOPPED") window('emby_customPlaylist', clear=True) window('emby_customPlaylist.seektime', clear=True) window('emby_playbackProps', clear=True) - log("Clear playlist properties.", 1) + log.info("Clear playlist properties.") self.stopAll() def onPlayBackEnded(self): # Will be called when xbmc stops playing a file - log("ONPLAYBACK_ENDED", 2) + log.debug("ONPLAYBACK_ENDED") window('emby_customPlaylist.seektime', clear=True) self.stopAll() @@ -419,15 +420,15 @@ class Player(xbmc.Player): if not self.played_info: return - log("Played_information: %s" % self.played_info, 1) + log.info("Played_information: %s" % self.played_info) # 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) + log.debug("Item path: %s" % item) + log.debug("Item data: %s" % data) runtime = data['runtime'] currentPosition = data['currentPosition'] @@ -448,8 +449,8 @@ class Player(xbmc.Player): percentComplete = 0 markPlayedAt = float(settings('markPlayed')) / 100 - log("Percent complete: %s Mark played at: %s" - % (percentComplete, markPlayedAt), 1) + log.info("Percent complete: %s Mark played at: %s" + % (percentComplete, markPlayedAt)) # Send the delete action to the server. offerDelete = False @@ -467,16 +468,16 @@ class Player(xbmc.Player): resp = xbmcgui.Dialog().yesno(lang(30091), lang(33015), autoclose=120000) if resp: url = "{server}/emby/Items/%s?format=json" % itemid - log("Deleting request: %s" % itemid, 1) + log.info("Deleting request: %s" % itemid) self.doUtils(url, action_type="DELETE") else: - log("User skipped deletion.", 1) + log.info("User skipped deletion.") self.stopPlayback(data) # Stop transcoding if playMethod == "Transcode": - log("Transcoding for %s terminated." % itemid, 1) + log.info("Transcoding for %s terminated." % itemid) deviceId = self.clientInfo.getDeviceId() url = "{server}/emby/Videos/ActiveEncodings?DeviceId=%s" % deviceId self.doUtils(url, action_type="DELETE") @@ -485,7 +486,7 @@ class Player(xbmc.Player): def stopPlayback(self, data): - log("stopPlayback called", 2) + log.debug("stopPlayback called") itemId = data['item_id'] currentPosition = data['currentPosition'] diff --git a/resources/lib/playlist.py b/resources/lib/playlist.py index 1f0819b6..6f40fa71 100644 --- a/resources/lib/playlist.py +++ b/resources/lib/playlist.py @@ -3,17 +3,21 @@ ################################################################################################# import json +import logging import xbmc import xbmcgui import xbmcplugin -import clientinfo import playutils import playbackutils import embydb_functions as embydb import read_embyserver as embyserver -from utils import Logging, window, settings, language as lang, kodiSQL +from utils import window, settings, language as lang, kodiSQL + +################################################################################################# + +log = logging.getLogger("EMBY."+__name__) ################################################################################################# @@ -23,12 +27,6 @@ class Playlist(): def __init__(self): - global log - log = Logging(self.__class__.__name__).log - - self.clientInfo = clientinfo.ClientInfo() - self.addonName = self.clientInfo.getAddonName() - self.userid = window('emby_currUser') self.server = window('emby_server%s' % self.userid) @@ -45,8 +43,8 @@ class Playlist(): playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO) playlist.clear() - log("---*** PLAY ALL ***---", 1) - log("Items: %s and start at: %s" % (itemids, startat), 1) + log.info("---*** PLAY ALL ***---") + log.info("Items: %s and start at: %s" % (itemids, startat)) started = False window('emby_customplaylist', value="true") @@ -62,14 +60,14 @@ class Playlist(): 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) + log.info("Item was not found in the database, manually adding item.") 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) + log.info("Adding %s to playlist." % itemid) if not started: started = True @@ -84,8 +82,8 @@ class Playlist(): embycursor = embyconn.cursor() emby_db = embydb.Embydb_Functions(embycursor) - log("---*** ADD TO PLAYLIST ***---", 1) - log("Items: %s" % itemids, 1) + log.info("---*** ADD TO PLAYLIST ***---") + log.info("Items: %s" % itemids) player = xbmc.Player() playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO) @@ -103,7 +101,7 @@ class Playlist(): # Add to playlist self.addtoPlaylist(dbid, mediatype) - log("Adding %s to playlist." % itemid, 1) + log.info("Adding %s to playlist." % itemid) self.verifyPlaylist() embycursor.close() @@ -126,17 +124,17 @@ class Playlist(): else: pl['params']['item'] = {'file': url} - log(xbmc.executeJSONRPC(json.dumps(pl)), 2) + log.debug(xbmc.executeJSONRPC(json.dumps(pl))) def addtoPlaylist_xbmc(self, playlist, item): playurl = playutils.PlayUtils(item).getPlayUrl() if not playurl: # Playurl failed - log("Failed to retrieve playurl.", 1) + log.info("Failed to retrieve playurl.") return - log("Playurl: %s" % playurl) + log.info("Playurl: %s" % playurl) listitem = xbmcgui.ListItem() playbackutils.PlaybackUtils(item).setProperties(playurl, listitem) @@ -160,7 +158,7 @@ class Playlist(): else: pl['params']['item'] = {'file': url} - log(xbmc.executeJSONRPC(json.dumps(pl)), 2) + log.debug(xbmc.executeJSONRPC(json.dumps(pl))) def verifyPlaylist(self): @@ -174,7 +172,7 @@ class Playlist(): 'playlistid': 1 } } - log(xbmc.executeJSONRPC(json.dumps(pl)), 2) + log.debug(xbmc.executeJSONRPC(json.dumps(pl))) def removefromPlaylist(self, position): @@ -189,4 +187,4 @@ class Playlist(): 'position': position } } - log(xbmc.executeJSONRPC(json.dumps(pl)), 2) \ No newline at end of file + log.debug(xbmc.executeJSONRPC(json.dumps(pl))) \ No newline at end of file diff --git a/resources/lib/playutils.py b/resources/lib/playutils.py index 0674be71..3375af55 100644 --- a/resources/lib/playutils.py +++ b/resources/lib/playutils.py @@ -2,6 +2,7 @@ ################################################################################################# +import logging import sys import xbmc @@ -9,7 +10,11 @@ import xbmcgui import xbmcvfs import clientinfo -from utils import Logging, window, settings, language as lang +from utils import window, settings, language as lang + +################################################################################################# + +log = logging.getLogger("EMBY."+__name__) ################################################################################################# @@ -19,12 +24,8 @@ class PlayUtils(): def __init__(self, item): - global log - log = Logging(self.__class__.__name__).log - self.item = item self.clientInfo = clientinfo.ClientInfo() - self.addonName = self.clientInfo.getAddonName() self.userid = window('emby_currUser') self.server = window('emby_server%s' % self.userid) @@ -37,19 +38,19 @@ class PlayUtils(): if (self.item.get('Type') in ("Recording", "TvChannel") and self.item.get('MediaSources') and self.item['MediaSources'][0]['Protocol'] == "Http"): # Play LiveTV or recordings - log("File protocol is http (livetv).", 1) + log.info("File protocol is http (livetv).") playurl = "%s/emby/Videos/%s/stream.ts?audioCodec=copy&videoCodec=copy" % (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 - log("File protocol is http.", 1) + log.info("File protocol is http.") playurl = self.httpPlay() window('emby_%s.playmethod' % playurl, value="DirectStream") elif self.isDirectPlay(): - log("File is direct playing.", 1) + log.info("File is direct playing.") playurl = self.directPlay() playurl = playurl.encode('utf-8') # Set playmethod property @@ -57,14 +58,14 @@ class PlayUtils(): elif self.isDirectStream(): - log("File is direct streaming.", 1) + log.info("File is direct streaming.") playurl = self.directStream() # Set playmethod property window('emby_%s.playmethod' % playurl, value="DirectStream") elif self.isTranscoding(): - log("File is transcoding.", 1) + log.info("File is transcoding.") playurl = self.transcoding() # Set playmethod property window('emby_%s.playmethod' % playurl, value="Transcode") @@ -89,7 +90,7 @@ class PlayUtils(): # Requirement: Filesystem, Accessible path if settings('playFromStream') == "true": # User forcing to play via HTTP - log("Can't direct play, play from HTTP enabled.", 1) + log.info("Can't direct play, play from HTTP enabled.") return False videotrack = self.item['MediaSources'][0]['Name'] @@ -109,23 +110,23 @@ class PlayUtils(): '2': 720, '3': 1080 } - log("Resolution is: %sP, transcode for resolution: %sP+" - % (resolution, res[transcodeH265]), 1) + log.info("Resolution is: %sP, transcode for resolution: %sP+" + % (resolution, res[transcodeH265])) if res[transcodeH265] <= resolution: return False canDirectPlay = self.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) + log.info("Can't direct play, server doesn't allow/support it.") return False location = self.item['LocationType'] if location == "FileSystem": # Verify the path if not self.fileExists(): - log("Unable to direct play.", 1) - log(self.directPlay(), 1) + log.info("Unable to direct play.") + log.info(self.directPlay()) xbmcgui.Dialog().ok( heading=lang(29999), line1=lang(33011), @@ -167,18 +168,18 @@ class PlayUtils(): # Convert path to direct play path = self.directPlay() - log("Verifying path: %s" % path, 1) + log.info("Verifying path: %s" % path) if xbmcvfs.exists(path): - log("Path exists.", 1) + log.info("Path exists.") return True elif ":" not in path: - log("Can't verify path, assumed linux. Still try to direct play.", 1) + log.info("Can't verify path, assumed linux. Still try to direct play.") return True else: - log("Failed to find file.", 1) + log.info("Failed to find file.") return False def isDirectStream(self): @@ -200,8 +201,8 @@ class PlayUtils(): '2': 720, '3': 1080 } - log("Resolution is: %sP, transcode for resolution: %sP+" - % (resolution, res[transcodeH265]), 1) + log.info("Resolution is: %sP, transcode for resolution: %sP+" + % (resolution, res[transcodeH265])) if res[transcodeH265] <= resolution: return False @@ -213,7 +214,7 @@ class PlayUtils(): # Verify the bitrate if not self.isNetworkSufficient(): - log("The network speed is insufficient to direct stream file.", 1) + log.info("The network speed is insufficient to direct stream file.") return False return True @@ -237,10 +238,10 @@ class PlayUtils(): try: sourceBitrate = int(self.item['MediaSources'][0]['Bitrate']) except (KeyError, TypeError): - log("Bitrate value is missing.", 1) + log.info("Bitrate value is missing.") else: - log("The add-on settings bitrate is: %s, the video bitrate required is: %s" - % (settings, sourceBitrate), 1) + log.info("The add-on settings bitrate is: %s, the video bitrate required is: %s" + % (settings, sourceBitrate)) if settings < sourceBitrate: return False diff --git a/resources/lib/read_embyserver.py b/resources/lib/read_embyserver.py index 15fe2d7a..f2d241a9 100644 --- a/resources/lib/read_embyserver.py +++ b/resources/lib/read_embyserver.py @@ -2,11 +2,16 @@ ################################################################################################# +import logging + import xbmc -import clientinfo import downloadutils -from utils import Logging, window, settings, kodiSQL +from utils import window, settings, kodiSQL + +################################################################################################# + +log = logging.getLogger("EMBY."+__name__) ################################################################################################# @@ -18,11 +23,6 @@ class Read_EmbyServer(): def __init__(self): - global log - log = Logging(self.__class__.__name__).log - - self.clientInfo = clientinfo.ClientInfo() - self.addonName = self.clientInfo.getAddonName() self.doUtils = downloadutils.DownloadUtils().downloadUrl self.userId = window('emby_currUser') @@ -211,7 +211,7 @@ class Read_EmbyServer(): items['TotalRecordCount'] = total except TypeError: # Failed to retrieve - log("%s:%s Failed to retrieve the server response." % (url, params), 2) + log.debug("%s:%s Failed to retrieve the server response." % (url, params)) else: index = 0 @@ -253,27 +253,27 @@ class Read_EmbyServer(): # Something happened to the connection if not throttled: throttled = True - log("Throttle activated.", 1) + log.info("Throttle activated.") if jump == highestjump: # We already tried with the highestjump, but it failed. Reset value. - log("Reset highest value.", 1) + log.info("Reset highest value.") highestjump = 0 # Lower the number by half if highestjump: throttled = False jump = highestjump - log("Throttle deactivated.", 1) + log.info("Throttle deactivated.") else: jump = int(jump/4) - log("Set jump limit to recover: %s" % jump, 2) + log.debug("Set jump limit to recover: %s" % jump) retry = 0 while window('emby_online') != "true": # Wait server to come back online if retry == 5: - log("Unable to reconnect to server. Abort process.", 1) + log.info("Unable to reconnect to server. Abort process.") return items retry += 1 @@ -301,7 +301,7 @@ class Read_EmbyServer(): increment = 10 jump += increment - log("Increase jump limit to: %s" % jump, 1) + log.info("Increase jump limit to: %s" % jump) return items def getViews(self, mediatype="", root=False, sortedlist=False): @@ -318,7 +318,7 @@ class Read_EmbyServer(): try: items = result['Items'] except TypeError: - log("Error retrieving views for type: %s" % mediatype, 2) + log.debug("Error retrieving views for type: %s" % mediatype) else: for item in items: @@ -462,7 +462,7 @@ class Read_EmbyServer(): items['TotalRecordCount'] = total except TypeError: # Failed to retrieve - log("%s:%s Failed to retrieve the server response." % (url, params), 2) + log.debug("%s:%s Failed to retrieve the server response." % (url, params)) else: index = 1 @@ -550,9 +550,9 @@ class Read_EmbyServer(): url = "{server}/emby/Users/{UserId}/FavoriteItems/%s?format=json" % itemid doUtils(url, action_type="DELETE") else: - log("Error processing user rating.", 1) + log.info("Error processing user rating.") - log("Update user rating to emby for itemid: %s | favourite: %s" % (itemid, favourite), 1) + log.info("Update user rating to emby for itemid: %s | favourite: %s" % (itemid, favourite)) def refreshItem(self, itemid): diff --git a/resources/lib/userclient.py b/resources/lib/userclient.py index 09cae9fa..37282efb 100644 --- a/resources/lib/userclient.py +++ b/resources/lib/userclient.py @@ -3,6 +3,7 @@ ################################################################################################## import hashlib +import logging import threading import xbmc @@ -13,7 +14,11 @@ import xbmcvfs import artwork import clientinfo import downloadutils -from utils import Logging, window, settings, language as lang +from utils import window, settings, language as lang + +################################################################################################## + +log = logging.getLogger("EMBY."+__name__) ################################################################################################## @@ -39,13 +44,9 @@ class UserClient(threading.Thread): def __init__(self): - global log - log = Logging(self.__class__.__name__).log - self.__dict__ = self._shared_state self.addon = xbmcaddon.Addon() - self.addonName = clientinfo.ClientInfo().getAddonName() self.doUtils = downloadutils.DownloadUtils() threading.Thread.__init__(self) @@ -63,7 +64,7 @@ class UserClient(threading.Thread): username = settings('username') if not username: - log("No username saved.", 2) + log.debug("No username saved.") return "" return username @@ -88,17 +89,17 @@ class UserClient(threading.Thread): 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) + log.debug("Returning userId from WINDOW for username: %s UserId: %s" + % (username, w_userId)) return w_userId # Verify the settings elif s_userId: - log("Returning userId from SETTINGS for username: %s userId: %s" - % (username, s_userId), 2) + log.debug("Returning userId from SETTINGS for username: %s userId: %s" + % (username, s_userId)) return s_userId # No userId found else: - log("No userId saved for username: %s." % username, 1) + log.info("No userId saved for username: %s." % username) def getServer(self, prefix=True): @@ -117,7 +118,7 @@ class UserClient(threading.Thread): server = host + ":" + port if not host: - log("No server information saved.", 2) + log.debug("No server information saved.") return False # If https is true @@ -144,17 +145,17 @@ class UserClient(threading.Thread): 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) + log.debug("Returning accessToken from WINDOW for username: %s accessToken: %s" + % (username, w_token)) return w_token # Verify the settings elif s_token: - log("Returning accessToken from SETTINGS for username: %s accessToken: %s" - % (username, s_token), 2) + log.debug("Returning accessToken from SETTINGS for username: %s accessToken: %s" + % (username, s_token)) window('emby_accessToken%s' % username, value=s_token) return s_token else: - log("No token found.", 1) + log.info("No token found.") return "" def getSSLverify(self): @@ -210,7 +211,7 @@ class UserClient(threading.Thread): if result == False: # Access is restricted, set in downloadutils.py via exception - log("Access is restricted.", 1) + log.info("Access is restricted.") self.HasAccess = False elif window('emby_online') != "true": @@ -218,7 +219,7 @@ class UserClient(threading.Thread): pass elif window('emby_serverStatus') == "restricted": - log("Access is granted.", 1) + log.info("Access is granted.") self.HasAccess = True window('emby_serverStatus', clear=True) xbmcgui.Dialog().notification(lang(29999), lang(33007)) @@ -283,12 +284,12 @@ class UserClient(threading.Thread): # If there's no settings.xml if not hasSettings: - log("No settings.xml found.", 1) + log.info("No settings.xml found.") self.auth = False return # If no user information elif not server or not username: - log("Missing server information.", 1) + log.info("Missing server information.") self.auth = False return # If there's a token, load the user @@ -298,9 +299,9 @@ class UserClient(threading.Thread): 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) + log.info("Current user: %s" % self.currUser) + log.info("Current userId: %s" % self.currUserId) + log.debug("Current accessToken: %s" % self.currToken) return ##### AUTHENTICATE USER ##### @@ -320,7 +321,7 @@ class UserClient(threading.Thread): option=xbmcgui.ALPHANUM_HIDE_INPUT) # If password dialog is cancelled if not password: - log("No password entered.", 0) + log.warn("No password entered.") window('emby_serverStatus', value="Stop") self.auth = False return @@ -335,17 +336,17 @@ class UserClient(threading.Thread): # Authenticate username and password data = {'username': username, 'password': sha1} - log(data, 2) + log.debug(data) url = "%s/emby/Users/AuthenticateByName?format=json" % server result = self.doUtils.downloadUrl(url, postBody=data, action_type="POST", authenticate=False) try: - log("Auth response: %s" % result, 1) + log.info("Auth response: %s" % result) accessToken = result['AccessToken'] except (KeyError, TypeError): - log("Failed to retrieve the api key.", 1) + log.info("Failed to retrieve the api key.") accessToken = None if accessToken is not None: @@ -354,20 +355,20 @@ class UserClient(threading.Thread): "%s %s!" % (lang(33000), self.currUser.decode('utf-8'))) settings('accessToken', value=accessToken) settings('userId%s' % username, value=result['User']['Id']) - log("User Authenticated: %s" % accessToken, 1) + log.info("User Authenticated: %s" % accessToken) self.loadCurrUser(authenticated=True) window('emby_serverStatus', clear=True) self.retry = 0 else: - log("User authentication failed.", 1) + log.error("User authentication failed.") 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) + log.info("Too many retries. " + "You can retry by resetting attempts in the addon settings.") window('emby_serverStatus', value="Stop") dialog.ok(lang(33001), lang(33010)) @@ -376,13 +377,13 @@ class UserClient(threading.Thread): def resetClient(self): - log("Reset UserClient authentication.", 1) + log.info("Reset UserClient authentication.") if self.currToken is not None: # In case of 401, removed saved token settings('accessToken', value="") window('emby_accessToken%s' % self.getUserId(), clear=True) self.currToken = None - log("User token has been removed.", 1) + log.info("User token has been removed.") self.auth = True self.currUser = None @@ -390,7 +391,7 @@ class UserClient(threading.Thread): def run(self): monitor = xbmc.Monitor() - log("----===## Starting UserClient ##===----", 0) + log.warn("----===## Starting UserClient ##===----") while not monitor.abortRequested(): @@ -425,8 +426,8 @@ class UserClient(threading.Thread): # 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) + log.debug("Server found: %s" % server) + log.debug("Username found: %s" % username) self.auth = True @@ -439,7 +440,7 @@ class UserClient(threading.Thread): break self.doUtils.stopSession() - log("##===---- UserClient Stopped ----===##", 0) + log.warn("##===---- UserClient Stopped ----===##") def stopClient(self): # When emby for kodi terminates diff --git a/resources/lib/utils.py b/resources/lib/utils.py index 079c9514..db01fd35 100644 --- a/resources/lib/utils.py +++ b/resources/lib/utils.py @@ -5,6 +5,7 @@ import cProfile import inspect import json +import logging import pstats import sqlite3 import StringIO @@ -19,46 +20,13 @@ import xbmcaddon import xbmcgui import xbmcvfs +################################################################################################# + +log = logging.getLogger("EMBY."+__name__) + ################################################################################################# # Main methods -class Logging(object): - - - def __init__(self, title=""): - - self.title = title - - def _getLogLevel(self, level): - - try: - logLevel = int(window('emby_logLevel')) - except ValueError: - logLevel = 0 - - return logLevel >= level - - def _printMsg(self, title, msg): - - try: - xbmc.log("%s -> %s" % (title, msg)) - except UnicodeEncodeError: - xbmc.log("%s -> %s" % (title, msg.encode('utf-8'))) - - def log(self, msg, level=1): - - extra = "" - if level == -1: - # Error mode - extra = "::ERROR" - - if self._getLogLevel(level): - self._printMsg("EMBY %s%s" % (self.title, extra), msg) - -# Initiate class for utils.py document logging -log = Logging('Utils').log - - def window(property, value=None, clear=False, window_id=10000): # Get or set window property WINDOW = xbmcgui.Window(window_id) @@ -143,7 +111,7 @@ def querySQL(query, args=None, cursor=None, conntype=None): if cursor is None: if conntype is None: - log("New connection type is missing.", 1) + log.info("New connection type is missing.") return result else: manualconn = True @@ -153,7 +121,7 @@ def querySQL(query, args=None, cursor=None, conntype=None): attempts = 0 while attempts < 3: try: - log("Query: %s Args: %s" % (query, args), 2) + log.debug("Query: %s Args: %s" % (query, args)) if args is None: result = cursor.execute(query) else: @@ -161,22 +129,22 @@ def querySQL(query, args=None, cursor=None, conntype=None): break # Query successful, break out of while loop except sqlite3.OperationalError as e: if "database is locked" in e: - log("%s...Attempt: %s" % (e, attempts), 0) + log.warn("%s...Attempt: %s" % (e, attempts)) attempts += 1 xbmc.sleep(1000) else: - log("Error sqlite3: %s" % e, 0) + log.error(e) if manualconn: cursor.close() raise except sqlite3.Error as e: - log("Error sqlite3: %s" % e, 0) + log.error(e) if manualconn: cursor.close() raise else: failed = True - log("FAILED // Query: %s Args: %s" % (query, args), 1) + log.info("FAILED // Query: %s Args: %s" % (query, args)) if manualconn: if failed: @@ -185,7 +153,7 @@ def querySQL(query, args=None, cursor=None, conntype=None): connection.commit() cursor.close() - log(result, 2) + log.debug(result) return result ################################################################################################# @@ -219,7 +187,7 @@ def setScreensaver(value): } } result = xbmc.executeJSONRPC(json.dumps(query)) - log("Toggling screensaver: %s %s" % (value, result), 1) + log.info("Toggling screensaver: %s %s" % (value, result)) def convertDate(date): try: @@ -300,7 +268,7 @@ def profiling(sortby="cumulative"): s = StringIO.StringIO() ps = pstats.Stats(pr, stream=s).sort_stats(sortby) ps.print_stats() - log(s.getvalue(), 1) + log.info(s.getvalue()) return result @@ -321,7 +289,7 @@ def reset(): window('emby_shouldStop', value="true") count = 10 while window('emby_dbScan') == "true": - log("Sync is running, will retry: %s..." % count) + log.info("Sync is running, will retry: %s..." % count) count -= 1 if count == 0: dialog.ok(language(29999), language(33085)) @@ -335,7 +303,7 @@ def reset(): deleteNodes() # Wipe the kodi databases - log("Resetting the Kodi video database.", 0) + log.warn("Resetting the Kodi video database.") connection = kodiSQL('video') cursor = connection.cursor() cursor.execute('SELECT tbl_name FROM sqlite_master WHERE type="table"') @@ -348,7 +316,7 @@ def reset(): cursor.close() if settings('enableMusic') == "true": - log("Resetting the Kodi music database.", 0) + log.warn("Resetting the Kodi music database.") connection = kodiSQL('music') cursor = connection.cursor() cursor.execute('SELECT tbl_name FROM sqlite_master WHERE type="table"') @@ -361,7 +329,7 @@ def reset(): cursor.close() # Wipe the emby database - log("Resetting the Emby database.", 0) + log.warn("Resetting the Emby database.") connection = kodiSQL('emby') cursor = connection.cursor() cursor.execute('SELECT tbl_name FROM sqlite_master WHERE type="table"') @@ -378,7 +346,7 @@ def reset(): # Offer to wipe cached thumbnails resp = dialog.yesno(language(29999), language(33086)) if resp: - log("Resetting all cached artwork.", 0) + log.warn("Resetting all cached artwork.") # Remove all existing textures first path = xbmc.translatePath("special://thumbnails/").decode('utf-8') if xbmcvfs.exists(path): @@ -414,7 +382,7 @@ def reset(): addondir = xbmc.translatePath(addon.getAddonInfo('profile')).decode('utf-8') dataPath = "%ssettings.xml" % addondir xbmcvfs.delete(dataPath) - log("Deleting: settings.xml", 1) + log.info("Deleting: settings.xml") dialog.ok(heading=language(29999), line1=language(33088)) xbmc.executebuiltin('RestartApp') @@ -488,11 +456,11 @@ def passwordsXML(): for path in paths: if path.find('.//from').text == "smb://%s/" % credentials: paths.remove(path) - log("Successfully removed credentials for: %s" % credentials, 1) + log.info("Successfully removed credentials for: %s" % credentials) etree.ElementTree(root).write(xmlpath) break else: - log("Failed to find saved server: %s in passwords.xml" % credentials, 1) + log.info("Failed to find saved server: %s in passwords.xml" % credentials) settings('networkCreds', value="") xbmcgui.Dialog().notification( @@ -541,7 +509,7 @@ def passwordsXML(): # Add credentials settings('networkCreds', value="%s" % server) - log("Added server: %s to passwords.xml" % server, 1) + log.info("Added server: %s to passwords.xml" % server) # Prettify and write to file try: indent(root) @@ -569,7 +537,7 @@ def playlistXSP(mediatype, tagname, viewid, viewtype="", delete=False): # Create the playlist directory if not xbmcvfs.exists(path): - log("Creating directory: %s" % path, 1) + log.info("Creating directory: %s" % path) xbmcvfs.mkdirs(path) # Only add the playlist if it doesn't already exists @@ -577,7 +545,7 @@ def playlistXSP(mediatype, tagname, viewid, viewtype="", delete=False): if delete: xbmcvfs.delete(xsppath) - log("Successfully removed playlist: %s." % tagname, 1) + log.info("Successfully removed playlist: %s." % tagname) return @@ -585,11 +553,11 @@ def playlistXSP(mediatype, tagname, viewid, viewtype="", delete=False): itemtypes = { 'homevideos': "movies" } - log("Writing playlist file to: %s" % xsppath, 1) + log.info("Writing playlist file to: %s" % xsppath) try: f = xbmcvfs.File(xsppath, 'w') except: - log("Failed to create playlist: %s" % xsppath, 1) + log.info("Failed to create playlist: %s" % xsppath) return else: f.write( @@ -603,7 +571,7 @@ def playlistXSP(mediatype, tagname, viewid, viewtype="", delete=False): '' % (itemtypes.get(mediatype, mediatype), plname, tagname)) f.close() - log("Successfully added playlist: %s" % tagname, 1) + log.info("Successfully added playlist: %s" % tagname) def deletePlaylists(): @@ -625,10 +593,10 @@ def deleteNodes(): try: shutil.rmtree("%s%s" % (path, dir.decode('utf-8'))) except: - log("Failed to delete directory: %s" % dir.decode('utf-8'), 0) + log.warn("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: - log("Failed to file: %s" % file.decode('utf-8'), 0) \ No newline at end of file + log.warn("Failed to delete 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 bf1d20f4..6873487c 100644 --- a/resources/lib/videonodes.py +++ b/resources/lib/videonodes.py @@ -2,6 +2,7 @@ ################################################################################################# +import logging import shutil import xml.etree.ElementTree as etree @@ -9,9 +10,12 @@ import xbmc import xbmcaddon import xbmcvfs -import clientinfo import utils -from utils import Logging, window, language as lang +from utils import window, language as lang + +################################################################################################# + +log = logging.getLogger("EMBY."+__name__) ################################################################################################# @@ -21,12 +25,6 @@ class VideoNodes(object): def __init__(self): - global log - log = Logging(self.__class__.__name__).log - - clientInfo = clientinfo.ClientInfo() - self.addonName = clientInfo.getAddonName() - self.kodiversion = int(xbmc.getInfoLabel('System.BuildVersion')[:2]) @@ -79,7 +77,7 @@ class VideoNodes(object): for file in files: xbmcvfs.delete(nodepath + file) - log("Sucessfully removed videonode: %s." % tagname, 1) + log.info("Sucessfully removed videonode: %s." % tagname) return # Create index entry @@ -364,7 +362,7 @@ class VideoNodes(object): def clearProperties(self): - log("Clearing nodes properties.", 1) + log.info("Clearing nodes properties.") embyprops = window('Emby.nodes.total') propnames = [ diff --git a/resources/lib/websocket_client.py b/resources/lib/websocket_client.py index 87d1e012..db0f8c00 100644 --- a/resources/lib/websocket_client.py +++ b/resources/lib/websocket_client.py @@ -3,6 +3,7 @@ ################################################################################################# import json +import logging import threading import websocket @@ -14,9 +15,13 @@ import downloadutils import librarysync import playlist import userclient -from utils import Logging, window, settings, language as lang +from utils import window, settings, language as lang -################################################################################################# +################################################################################################## + +log = logging.getLogger("EMBY."+__name__) + +################################################################################################## class WebSocket_Client(threading.Thread): @@ -29,15 +34,11 @@ class WebSocket_Client(threading.Thread): def __init__(self): - global log - log = Logging(self.__class__.__name__).log - 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() @@ -46,7 +47,7 @@ class WebSocket_Client(threading.Thread): def sendProgressUpdate(self, data): - log("sendProgressUpdate", 2) + log.debug("sendProgressUpdate") try: messageData = { @@ -55,10 +56,10 @@ class WebSocket_Client(threading.Thread): } messageString = json.dumps(messageData) self.client.send(messageString) - log("Message data: %s" % messageString, 2) + log.debug("Message data: %s" % messageString) except Exception as e: - log("Exception: %s" % e, 1) + log.exception(e) def on_message(self, ws, message): @@ -69,7 +70,7 @@ class WebSocket_Client(threading.Thread): if messageType not in ('SessionEnded'): # Mute certain events - log("Message: %s" % message, 1) + log.info("Message: %s" % message) if messageType == "Play": # A remote control play command has been sent from the server. @@ -118,10 +119,10 @@ class WebSocket_Client(threading.Thread): seekto = data['SeekPositionTicks'] seektime = seekto / 10000000.0 action(seektime) - log("Seek to %s." % seektime, 1) + log.info("Seek to %s." % seektime) else: action() - log("Command: %s completed." % command, 1) + log.info("Command: %s completed." % command) window('emby_command', value="true") @@ -254,7 +255,7 @@ class WebSocket_Client(threading.Thread): self.librarySync.refresh_views = True def on_close(self, ws): - log("Closed.", 2) + log.debug("Closed.") def on_open(self, ws): self.doUtils.postCapabilities(self.deviceId) @@ -264,7 +265,7 @@ class WebSocket_Client(threading.Thread): # Server is offline pass else: - log("Error: %s" % error, 2) + log.debug("Error: %s" % error) def run(self): @@ -281,7 +282,7 @@ class WebSocket_Client(threading.Thread): server = server.replace('http', "ws") websocket_url = "%s?api_key=%s&deviceId=%s" % (server, token, self.deviceId) - log("websocket url: %s" % websocket_url, 1) + log.info("websocket url: %s" % websocket_url) self.client = websocket.WebSocketApp(websocket_url, on_message=self.on_message, @@ -289,7 +290,7 @@ class WebSocket_Client(threading.Thread): on_close=self.on_close) self.client.on_open = self.on_open - log("----===## Starting WebSocketClient ##===----", 0) + log.warn("----===## Starting WebSocketClient ##===----") while not self.monitor.abortRequested(): @@ -301,10 +302,10 @@ class WebSocket_Client(threading.Thread): # Abort was requested, exit break - log("##===---- WebSocketClient Stopped ----===##", 0) + log.warn("##===---- WebSocketClient Stopped ----===##") def stopClient(self): self.stopWebsocket = True self.client.close() - log("Stopping thread.", 1) \ No newline at end of file + log.info("Stopping thread.") \ No newline at end of file diff --git a/service.py b/service.py index 13a9d1f0..f88b5c32 100644 --- a/service.py +++ b/service.py @@ -2,6 +2,7 @@ ################################################################################################# +import logging import os import sys import time @@ -30,7 +31,14 @@ import librarysync import player import videonodes import websocket_client as wsc -from utils import Logging, window, settings, language as lang +from utils import window, settings, language as lang + +################################################################################################# + +import loghandler + +loghandler.config() +log = logging.getLogger("EMBY.service") ################################################################################################# @@ -49,9 +57,6 @@ class Service(): def __init__(self): - global log - log = Logging(self.__class__.__name__).log - self.clientInfo = clientinfo.ClientInfo() self.addonName = self.clientInfo.getAddonName() logLevel = userclient.UserClient().getLogLevel() @@ -61,12 +66,12 @@ class Service(): window('emby_kodiProfile', value=xbmc.translatePath('special://profile')) # Initial logging - log("======== START %s ========" % self.addonName, 0) - log("Platform: %s" % (self.clientInfo.getPlatform()), 0) - log("KODI Version: %s" % xbmc.getInfoLabel('System.BuildVersion'), 0) - log("%s Version: %s" % (self.addonName, self.clientInfo.getVersion()), 0) - log("Using plugin paths: %s" % (settings('useDirectPaths') == "0"), 0) - log("Log Level: %s" % logLevel, 0) + log.warn("======== START %s ========" % self.addonName) + log.warn("Platform: %s" % (self.clientInfo.getPlatform())) + log.warn("KODI Version: %s" % xbmc.getInfoLabel('System.BuildVersion')) + log.warn("%s Version: %s" % (self.addonName, self.clientInfo.getVersion())) + log.warn("Using plugin paths: %s" % (settings('useDirectPaths') == "0")) + log.warn("Log Level: %s" % logLevel) # Reset window props for profile switch properties = [ @@ -108,8 +113,8 @@ class Service(): if window('emby_kodiProfile') != kodiProfile: # Profile change happened, terminate this thread and others - log("Kodi profile was: %s and changed to: %s. Terminating old Emby thread." - % (kodiProfile, window('emby_kodiProfile')), 1) + log.info("Kodi profile was: %s and changed to: %s. Terminating old Emby thread." + % (kodiProfile, window('emby_kodiProfile'))) break @@ -151,9 +156,8 @@ class Service(): kplayer.reportPlayback() lastProgressUpdate = datetime.today() - except Exception as e: - log("Exception in Playback Monitor Service: %s" % e, 1) - pass + except Exception: + log.exception("Exception in Playback Monitor Service") else: # Start up events self.warn_auth = True @@ -192,7 +196,7 @@ class Service(): if (user.currUser is None) and self.warn_auth: # Alert user is not authenticated and suppress future warning self.warn_auth = False - log("Not authenticated yet.", 1) + log.info("Not authenticated yet.") # User access is restricted. # Keep verifying until access is granted @@ -221,7 +225,7 @@ class Service(): # Server is offline. # Alert the user and suppress future warning if self.server_online: - log("Server is offline.", 1) + log.info("Server is offline.") window('emby_online', value="false") xbmcgui.Dialog().notification( @@ -235,13 +239,11 @@ class Service(): elif window('emby_online') == "sleep": # device going to sleep if self.websocket_running: - log("Stop websocket thread") ws.stopClient() ws = wsc.WebSocket_Client() self.websocket_running = False if self.library_running: - log("Stop library thread") library.stopThread() library = librarysync.LibrarySync() self.library_running = False @@ -263,12 +265,11 @@ class Service(): sound=False) self.server_online = True - log("Server is online and ready.", 1) + log.info("Server is online and ready.") window('emby_online', value="true") # Start the userclient thread if not self.userclient_running: - log("Start user thread") self.userclient_running = True user.start() @@ -293,14 +294,14 @@ class Service(): if self.websocket_running: ws.stopClient() - log("======== STOP %s ========" % self.addonName, 0) + log.warn("======== STOP %s ========" % self.addonName) # Delay option delay = int(settings('startupDelay')) -xbmc.log("Delaying emby startup by: %s sec..." % delay) +log.info("Delaying emby startup by: %s sec..." % delay) if delay and xbmc.Monitor().waitForAbort(delay): # Start the service - xbmc.log("Abort requested while waiting. Emby for kodi not started.") + log.warn("Abort requested while waiting. Emby for kodi not started.") else: Service().ServiceEntryPoint() \ No newline at end of file From fe1b5d8e06517276ed35982495e8f54da64f98d2 Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Sun, 24 Jul 2016 16:08:53 -0500 Subject: [PATCH 072/103] Logging fix --- contextmenu.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/contextmenu.py b/contextmenu.py index b690fd8f..5235188e 100644 --- a/contextmenu.py +++ b/contextmenu.py @@ -27,8 +27,14 @@ import read_embyserver as embyserver import embydb_functions as embydb import kodidb_functions as kodidb import musicutils as musicutils -from utils import Logging, settings, language as lang, kodiSQL -log = Logging('ContextMenu').log +from utils import settings, language as lang, kodiSQL + +################################################################################################# + +import loghandler + +loghandler.config() +log = logging.getLogger("EMBY.contextmenu") ################################################################################################# From 68c0aee1ea3f647b89de33c999ac786b77c2826c Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Sun, 24 Jul 2016 16:10:29 -0500 Subject: [PATCH 073/103] Fix logging Forgot to change actual log lines --- contextmenu.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/contextmenu.py b/contextmenu.py index 5235188e..ac1551db 100644 --- a/contextmenu.py +++ b/contextmenu.py @@ -56,7 +56,7 @@ if __name__ == '__main__': elif xbmc.getCondVisibility("Container.Content(pictures)"): itemType = "picture" else: - log("ItemType is unknown.") + log.info("ItemType is unknown.") if (not kodiId or kodiId == "-1") and xbmc.getInfoLabel("ListItem.Property(embyid)"): itemId = xbmc.getInfoLabel("ListItem.Property(embyid)") @@ -73,7 +73,7 @@ if __name__ == '__main__': pass - log("Found ItemId: %s ItemType: %s" % (itemId, itemType), 1) + log.info("Found ItemId: %s ItemType: %s" % (itemId, itemType)) if itemId: dialog = xbmcgui.Dialog() @@ -163,11 +163,11 @@ if __name__ == '__main__': heading=lang(29999), line1=lang(33041)) if not resp: - log("User skipped deletion for: %s." % itemId, 1) + log.info("User skipped deletion for: %s." % itemId) delete = False if delete: - log("Deleting request: %s" % itemId, 0) + log.info("Deleting request: %s" % itemId) emby.deleteItem(itemId) xbmc.sleep(500) From 32163117c9463bd34e7ba5fc8dc0e06137b49dec Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Sun, 24 Jul 2016 16:12:19 -0500 Subject: [PATCH 074/103] Fix missing import --- contextmenu.py | 1 + 1 file changed, 1 insertion(+) diff --git a/contextmenu.py b/contextmenu.py index ac1551db..454c7387 100644 --- a/contextmenu.py +++ b/contextmenu.py @@ -2,6 +2,7 @@ ################################################################################################# +import logging import os import sys import urlparse From 5895831ba8a4b35a6780cc6a9c4a5563f8e5ebf8 Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Sun, 24 Jul 2016 18:46:24 -0500 Subject: [PATCH 075/103] Clean up logging Moved incremental log line to display only if there's actual items to process. --- resources/lib/librarysync.py | 2 +- resources/lib/loghandler.py | 2 +- service.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/resources/lib/librarysync.py b/resources/lib/librarysync.py index 72277fa0..4c7ff088 100644 --- a/resources/lib/librarysync.py +++ b/resources/lib/librarysync.py @@ -807,11 +807,11 @@ class LibrarySync(threading.Thread): incSyncIndicator = int(settings('incSyncIndicator')) totalUpdates = len(self.addedItems) + len(self.updateItems) + len(self.userdataItems) + len(self.removeItems) - log.info("incSyncIndicator=" + str(incSyncIndicator) + " totalUpdates=" + str(totalUpdates)) if incSyncIndicator != -1 and totalUpdates > incSyncIndicator: # Only present dialog if we are going to process items pDialog = self.progressDialog('Incremental sync') + log.info("incSyncIndicator=" + str(incSyncIndicator) + " totalUpdates=" + str(totalUpdates)) process = { diff --git a/resources/lib/loghandler.py b/resources/lib/loghandler.py index 616ea1b9..b576e078 100644 --- a/resources/lib/loghandler.py +++ b/resources/lib/loghandler.py @@ -38,7 +38,7 @@ class LogHandler(logging.StreamHandler): logging.ERROR: 0, logging.WARNING: 0, logging.INFO: 1, - logging.DEBUG: 2, + logging.DEBUG: 2 } try: logLevel = int(window('emby_logLevel')) diff --git a/service.py b/service.py index f88b5c32..d70dd2b4 100644 --- a/service.py +++ b/service.py @@ -298,8 +298,8 @@ class Service(): # Delay option delay = int(settings('startupDelay')) +log.warn("Delaying emby startup by: %s sec..." % delay) -log.info("Delaying emby startup by: %s sec..." % delay) if delay and xbmc.Monitor().waitForAbort(delay): # Start the service log.warn("Abort requested while waiting. Emby for kodi not started.") From e197208d3e05433db9e3e478f58af7b3e8262bbb Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Sun, 24 Jul 2016 22:39:40 -0500 Subject: [PATCH 076/103] 2.2.17 (#50) * Add temporary setting to disable external subs for direct stream * Fix crash when sleeping/waking up device * Use the logging module. Moved logging to it's own file. * Version bump 2.2.17 * Update README.md --- README.md | 90 ++++++++-------- addon.xml | 2 +- changelog.txt | 4 + contextmenu.py | 19 ++-- default.py | 21 ++-- resources/lib/api.py | 15 ++- resources/lib/artwork.py | 48 +++++---- resources/lib/clientinfo.py | 16 +-- resources/lib/connect.py | 50 ++++----- resources/lib/downloadutils.py | 70 ++++++------- resources/lib/embydb_functions.py | 14 +-- resources/lib/entrypoint.py | 36 ++++--- resources/lib/image_cache_thread.py | 14 +-- resources/lib/initialsetup.py | 58 ++++++----- resources/lib/itemtypes.py | 148 +++++++++++++------------- resources/lib/kodidb_functions.py | 43 ++++---- resources/lib/kodimonitor.py | 39 ++++--- resources/lib/librarysync.py | 154 ++++++++++++++-------------- resources/lib/loghandler.py | 73 +++++++++++++ resources/lib/musicutils.py | 26 +++-- resources/lib/playbackutils.py | 48 ++++----- resources/lib/player.py | 65 ++++++------ resources/lib/playlist.py | 40 ++++---- resources/lib/playutils.py | 53 +++++----- resources/lib/read_embyserver.py | 36 +++---- resources/lib/userclient.py | 85 +++++++-------- resources/lib/utils.py | 92 ++++++----------- resources/lib/videonodes.py | 18 ++-- resources/lib/websocket_client.py | 37 +++---- resources/settings.xml | 1 + service.py | 60 +++++++---- 31 files changed, 780 insertions(+), 695 deletions(-) create mode 100644 resources/lib/loghandler.py diff --git a/README.md b/README.md index 59b8de90..a9fdc034 100644 --- a/README.md +++ b/README.md @@ -1,52 +1,60 @@ -### Welcome to Emby for Kodi +# Welcome to Emby for Kodi **A whole new way to manage and view your media library.** The Emby addon for Kodi combines the best of Kodi - ultra smooth navigation, beautiful UIs and playback of any file under the sun, and Emby - the most powerful fully open source multi-client media metadata indexer and server. -**What does it do?** +### Download and installation -With the old MediaBrowser addon for Kodi we have a couple of issues because you browse your media as a "video addon": -- 3rd party addons such as NextAired, remote apps etc. won't work -- Speed: when browsing the data has to be retrieved from the server. Especially on slower devices this can take too much time. -- All kinds of workaround were needed to get the best experience on Kodi clients +View this short [Youtube video](https://youtu.be/IaecDPcXI3I?t=119) to give you a better idea of the general process. -The new Emby addon synchronizes your media on your Emby server to the native Kodi database. Because we use the native Kodi database with this new approach the above limitations are gone! You can browse your media full speed and all other Kodi addons will be able to "see" your media. +1. Install the Emby repository for Kodi, from the repo install the Emby addon. +2. Within a few seconds you should be prompted for your server-details (or auto discovered). If not, try to restart Kodi +3. Once you're succesfully authenticated to your Emby server, the initial sync will start. +4. The first sync of the Emby server to local Kodi database may take some time depending on your device and library size. +5. Once the full sync is done, you can browse your media in Kodi, syncs will be automatically done in the background. -**What is currently supported ?** +### Our Wiki -We're still in beta stage of development. Currently these features are working: -- Movies -- Sets -- TV Shows -- MusicVideos -- Full sync at first run (import), background syncs configurable by the user in the addonsetting. The current default is that it will do a full sync on the background every couple of minutes. -- Deletions are supported: Items that are deleted on the Emby server will be deleted on the Kodi database. Deletions done from the Kodi client TO the Emby server is only supported if you enable file deletions in the Kodi settings. An additional warning will be diaplayed if you really want to remove the item from the Emby server. +If you need additional information on Emby for Kodi, check out our [wiki](https://github.com/MediaBrowser/plugin.video.emby/wiki). + +### What does Emby for Kodi do? + +The Emby addon synchronizes your media on your Emby server to the native Kodi database. Because we use the native Kodi database, you can browse your media full speed and all other Kodi addons will be able to "see" your media. You can also use any Kodi skin you'd like! + +### IMPORTANT NOTES + +- If you require help, post to our [Emby-Kodi forums](http://emby.media/community/index.php?/forum/99-kodi/) for faster replies. +- To achieve direct play, you will need to ensure your Emby library paths point to network paths (e.g: "\\\\server\Media\Movies"). See the [Emby wiki](https://github.com/MediaBrowser/Wiki/wiki/Path%20Substitution) for additional information. +- **The addon is not (and will not be) compatible with the MySQL database replacement in Kodi.** In fact, Emby takes over the point of having a MySQL database because it acts as a "man in the middle" for your entire media library. +- Emby for Kodi is currently not compatible with Kodi's Video Extras addon unless native playback mode is used. **Deactivate Video Extras if content start randomly playing.** + +### What is currently supported? + +Emby for Kodi is constantly being worked on. The following features are currently provided: + +- Library types available: + + Movies + + Sets + + TV Shows + + Music Videos + + Music +- Emby for Kodi context menu: + + Mark content as favorite + + Refresh content + + Delete content +- Direct play and transcode - Watched state/resume status sync: This is a 2-way synchronisation. Any watched state or resume status will be instantly (within seconds) reflected to or from Kodi and the server. -- Possibility to copy Theme Music locally for use with the TVTunes addon -- Possibility to copy ExtraFanart (rotating backgrounds) across for use with skins that support it +- Copy Theme Music locally for use with the TV Tunes addon +- Copy ExtraFanart (rotating backgrounds) across for use with skins that support it +- Offer to delete content after playback +- and more... -**To get started with the Emby addon for Kodi, first follow these guides to set up Emby and Kodi:** +### What is being worked on +Have a look at our [Trello board](https://trello.com/b/qBJ49ka4/emby-for-kodi) to follow our progress. -1. To prevent any conflicts, remove the "old" MediaBrowser addon from your Kodi setup. -2. If you were using a modded skin for the MediaBrowser addon, make sure to set it in "normal Kodi mode" or just install the unmodded version of the skin. -3. Install the MediaBrowser/Emby BETA repository for Kodi, from the repo install the Emby addon. -4. Within a few seconds you should be prompted for your server-details (or auto discovered). If not, try to restart Kodi -5. Once you're succesfully authenticated to your Emby server, the initial sync will start. -6. The first sync of the Emby server to local Kodi database may take some time. On a powerful machine and fast network, expect around 15-45 minutes. On a slow machine (such as a Raspberry Pi) the first sync may take up to two hours. -7. Once the full sync is done, you can browse your media in Kodi, syncs will be automatically done in the background. - - -**Known Issues:** -- Windows users: Kodi Helix 14.2 RC1 required - other versions will result in errors with recently added items etc. - -**Important note about MySQL database in kodi** - -The addon is not (and will not be) compatible with the MySQL database replacement in Kodi. In fact, Emby takes over the point of having a MySQL database because it acts as a "man in the middle" for your entire media library. - -**Important note about user collections/nodes** - -Emby has the ability to create custom nodes/folders for your Media, such as having a seperate folder for your "Kids Movies" etc. In Kodi this isn't supported, you just have "movies" or "tvshows". But... Kodi let's you create your own playlists and tags to get this same experience. During the sync the foldernode from the Emby server is added to the movies that are imported. In Kodi you can browse to Movie library --> tags and you will have a filtered result that points at your custom node. If you have a skin that let's you create any kind of shortcut on the homescreen you can simply create a shortcut to that tag. Another possibility is to create a "smart playlist" with Kodi and set it to show the content of a certain tag. - -At this point, please hold on to your feature requests and report bugs only. - -Report bugs or any usefull feedback on the forums +### Known Issues +Solutions to the following issues are unlikely due to Kodi limitations. +- Chapter images are missing unless native playback mode is used. +- Certain add-ons that depend on seeing where your content is located will not work unless native playback mode is selected. +- External subtitles (in separate files, e.g. mymovie.srt) can be used, but it is impossible to label them correctly unless direct playing +- Kodi only accepts direct paths for music content unlike the video library. Your Emby music library path will need to be formatted appropriately to work in Kodi (e.g: "\\\\server\Music\Album\song.ext"). See the [Emby wiki](https://github.com/MediaBrowser/Wiki/wiki/Path%20Substitution) for additional information. diff --git a/addon.xml b/addon.xml index 08000cf0..746cc7b4 100644 --- a/addon.xml +++ b/addon.xml @@ -1,7 +1,7 @@ diff --git a/changelog.txt b/changelog.txt index a9282f60..70d0235c 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,7 @@ +version 2.2.17 +- Fix crash when device wakes up +- Add option to disable external subs for direct stream - under add-on settings > playback + version 2.2.16 - Fix strptime error - Temporary fix for database being locked diff --git a/contextmenu.py b/contextmenu.py index b690fd8f..454c7387 100644 --- a/contextmenu.py +++ b/contextmenu.py @@ -2,6 +2,7 @@ ################################################################################################# +import logging import os import sys import urlparse @@ -27,8 +28,14 @@ import read_embyserver as embyserver import embydb_functions as embydb import kodidb_functions as kodidb import musicutils as musicutils -from utils import Logging, settings, language as lang, kodiSQL -log = Logging('ContextMenu').log +from utils import settings, language as lang, kodiSQL + +################################################################################################# + +import loghandler + +loghandler.config() +log = logging.getLogger("EMBY.contextmenu") ################################################################################################# @@ -50,7 +57,7 @@ if __name__ == '__main__': elif xbmc.getCondVisibility("Container.Content(pictures)"): itemType = "picture" else: - log("ItemType is unknown.") + log.info("ItemType is unknown.") if (not kodiId or kodiId == "-1") and xbmc.getInfoLabel("ListItem.Property(embyid)"): itemId = xbmc.getInfoLabel("ListItem.Property(embyid)") @@ -67,7 +74,7 @@ if __name__ == '__main__': pass - log("Found ItemId: %s ItemType: %s" % (itemId, itemType), 1) + log.info("Found ItemId: %s ItemType: %s" % (itemId, itemType)) if itemId: dialog = xbmcgui.Dialog() @@ -157,11 +164,11 @@ if __name__ == '__main__': heading=lang(29999), line1=lang(33041)) if not resp: - log("User skipped deletion for: %s." % itemId, 1) + log.info("User skipped deletion for: %s." % itemId) delete = False if delete: - log("Deleting request: %s" % itemId, 0) + log.info("Deleting request: %s" % itemId) emby.deleteItem(itemId) xbmc.sleep(500) diff --git a/default.py b/default.py index 03a338fc..9d9c098b 100644 --- a/default.py +++ b/default.py @@ -2,6 +2,7 @@ ################################################################################################# +import logging import os import sys import urlparse @@ -21,8 +22,14 @@ sys.path.append(_base_resource) import entrypoint import utils -from utils import Logging, window, language as lang -log = Logging('Default').log +from utils import window, language as lang + +################################################################################################# + +import loghandler + +loghandler.config() +log = logging.getLogger("EMBY.default") ################################################################################################# @@ -37,7 +44,7 @@ class Main(): # Parse parameters base_url = sys.argv[0] params = urlparse.parse_qs(sys.argv[2][1:]) - log("Parameter string: %s" % sys.argv[2], 0) + log.warn("Parameter string: %s" % sys.argv[2]) try: mode = params['mode'][0] itemid = params.get('id') @@ -113,7 +120,7 @@ class Main(): # Server is not online, do not run the sync xbmcgui.Dialog().ok(heading=lang(29999), line1=lang(33034)) - log("Not connected to the emby server.", 1) + log.warn("Not connected to the emby server.") return if window('emby_dbScan') != "true": @@ -126,7 +133,7 @@ class Main(): else: lib.fullSync(repair=True) else: - log("Database scan is already running.", 1) + log.warn("Database scan is already running.") elif mode == "texturecache": import artwork @@ -137,6 +144,6 @@ class Main(): if __name__ == "__main__": - log('plugin.video.emby started', 1) + log.info('plugin.video.emby started') Main() - log('plugin.video.emby stopped', 1) \ No newline at end of file + log.info('plugin.video.emby stopped') \ No newline at end of file diff --git a/resources/lib/api.py b/resources/lib/api.py index e5641e52..0d7332b5 100644 --- a/resources/lib/api.py +++ b/resources/lib/api.py @@ -4,8 +4,12 @@ ################################################################################################## -import clientinfo -from utils import Logging, settings +import logging +from utils import settings + +################################################################################################## + +log = logging.getLogger("EMBY."+__name__) ################################################################################################## @@ -13,17 +17,10 @@ from utils import Logging, settings class API(): def __init__(self, item): - - global log - log = Logging(self.__class__.__name__).log # item is the api response self.item = item - self.clientinfo = clientinfo.ClientInfo() - self.addonName = self.clientinfo.getAddonName() - - def getUserData(self): # Default favorite = False diff --git a/resources/lib/artwork.py b/resources/lib/artwork.py index f2cd0a32..4b0124f7 100644 --- a/resources/lib/artwork.py +++ b/resources/lib/artwork.py @@ -3,6 +3,7 @@ ################################################################################################# import json +import logging import requests import os import urllib @@ -12,11 +13,14 @@ import xbmc import xbmcgui import xbmcvfs -import clientinfo import image_cache_thread -from utils import Logging, window, settings, language as lang, kodiSQL +from utils import window, settings, language as lang, kodiSQL -################################################################################################# +################################################################################################## + +log = logging.getLogger("EMBY."+__name__) + +################################################################################################## class Artwork(): @@ -32,16 +36,10 @@ class Artwork(): def __init__(self): - global log - log = Logging(self.__class__.__name__).log - - self.clientinfo = clientinfo.ClientInfo() - self.addonName = self.clientinfo.getAddonName() - self.enableTextureCache = settings('enableTextureCache') == "true" self.imageCacheLimitThreads = int(settings('imageCacheLimit')) self.imageCacheLimitThreads = int(self.imageCacheLimitThreads * 5) - log("Using Image Cache Thread Count: %s" % self.imageCacheLimitThreads, 1) + log.info("Using Image Cache Thread Count: %s" % self.imageCacheLimitThreads) if not self.xbmc_port and self.enableTextureCache: self.setKodiWebServerDetails() @@ -175,14 +173,14 @@ class Artwork(): line1=lang(33042)): return - log("Doing Image Cache Sync", 1) + log.info("Doing Image Cache Sync") pdialog = xbmcgui.DialogProgress() pdialog.create(lang(29999), lang(33043)) # ask to rest all existing or not if dialog.yesno(lang(29999), lang(33044)): - log("Resetting all cache data first.", 1) + log.info("Resetting all cache data first.") # Remove all existing textures first path = xbmc.translatePath('special://thumbnails/').decode('utf-8') @@ -215,7 +213,7 @@ class Artwork(): cursor.execute("SELECT url FROM art WHERE media_type != 'actor'") # dont include actors result = cursor.fetchall() total = len(result) - log("Image cache sync about to process %s images" % total, 1) + log.info("Image cache sync about to process %s images" % total) cursor.close() count = 0 @@ -236,7 +234,7 @@ class Artwork(): cursor.execute("SELECT url FROM art") result = cursor.fetchall() total = len(result) - log("Image cache sync about to process %s images" % total, 1) + log.info("Image cache sync about to process %s images" % total) cursor.close() count = 0 @@ -252,14 +250,14 @@ class Artwork(): count += 1 pdialog.update(100, "%s %s" % (lang(33046), len(self.imageCacheThreads))) - log("Waiting for all threads to exit", 1) + log.info("Waiting for all threads to exit") while len(self.imageCacheThreads): for thread in self.imageCacheThreads: if thread.isFinished: self.imageCacheThreads.remove(thread) pdialog.update(100, "%s %s" % (lang(33046), len(self.imageCacheThreads))) - log("Waiting for all threads to exit: %s" % len(self.imageCacheThreads), 1) + log.info("Waiting for all threads to exit: %s" % len(self.imageCacheThreads)) xbmc.sleep(500) pdialog.close() @@ -282,13 +280,13 @@ class Artwork(): self.imageCacheThreads.append(newThread) return else: - log("Waiting for empty queue spot: %s" % len(self.imageCacheThreads), 2) + log.info("Waiting for empty queue spot: %s" % len(self.imageCacheThreads)) xbmc.sleep(50) def cacheTexture(self, url): # Cache a single image url to the texture cache if url and self.enableTextureCache: - log("Processing: %s" % url, 2) + log.debug("Processing: %s" % url) if not self.imageCacheLimitThreads: # Add image to texture cache by simply calling it at the http endpoint @@ -406,7 +404,7 @@ class Artwork(): except TypeError: # Add the artwork cacheimage = True - log("Adding Art Link for kodiId: %s (%s)" % (kodiId, imageUrl), 2) + log.debug("Adding Art Link for kodiId: %s (%s)" % (kodiId, imageUrl)) query = ( ''' @@ -427,8 +425,8 @@ class Artwork(): # Delete current entry before updating with the new one self.deleteCachedArtwork(url) - log("Updating Art url for %s kodiId: %s (%s) -> (%s)" - % (imageType, kodiId, url, imageUrl), 1) + log.info("Updating Art url for %s kodiId: %s (%s) -> (%s)" + % (imageType, kodiId, url, imageUrl)) query = ' '.join(( @@ -472,21 +470,21 @@ class Artwork(): cachedurl = cursor.fetchone()[0] except TypeError: - log("Could not find cached url.", 1) + log.info("Could not find cached url.") except OperationalError: - log("Database is locked. Skip deletion process.", 1) + log.info("Database is locked. Skip deletion process.") else: # Delete thumbnail as well as the entry thumbnails = xbmc.translatePath("special://thumbnails/%s" % cachedurl).decode('utf-8') - log("Deleting cached thumbnail: %s" % thumbnails, 1) + log.info("Deleting cached thumbnail: %s" % thumbnails) xbmcvfs.delete(thumbnails) try: cursor.execute("DELETE FROM texture WHERE url = ?", (url,)) connection.commit() except OperationalError: - log("Issue deleting url from cache. Skipping.", 2) + log.debug("Issue deleting url from cache. Skipping.") finally: cursor.close() diff --git a/resources/lib/clientinfo.py b/resources/lib/clientinfo.py index d5550388..7fd485e3 100644 --- a/resources/lib/clientinfo.py +++ b/resources/lib/clientinfo.py @@ -2,6 +2,7 @@ ################################################################################################# +import logging import os from uuid import uuid4 @@ -9,9 +10,13 @@ import xbmc import xbmcaddon import xbmcvfs -from utils import Logging, window, settings +from utils import window, settings -################################################################################################# +################################################################################################## + +log = logging.getLogger("EMBY."+__name__) + +################################################################################################## class ClientInfo(): @@ -19,9 +24,6 @@ class ClientInfo(): def __init__(self): - global log - log = Logging(self.__class__.__name__).log - self.addon = xbmcaddon.Addon() self.addonName = self.getAddonName() @@ -88,14 +90,14 @@ class ClientInfo(): GUID = xbmcvfs.File(GUID_file) clientId = GUID.read() if not clientId: - log("Generating a new deviceid...", 1) + log.info("Generating a new deviceid...") clientId = str("%012X" % uuid4()) GUID = xbmcvfs.File(GUID_file, 'w') GUID.write(clientId) GUID.close() - log("DeviceId loaded: %s" % clientId, 1) + log.info("DeviceId loaded: %s" % clientId) window('emby_deviceId', value=clientId) return clientId \ No newline at end of file diff --git a/resources/lib/connect.py b/resources/lib/connect.py index 87dbc2e1..aa3082a7 100644 --- a/resources/lib/connect.py +++ b/resources/lib/connect.py @@ -7,7 +7,7 @@ import requests import logging import clientinfo -from utils import Logging, window +from utils import window ################################################################################################## @@ -15,7 +15,8 @@ from utils import Logging, window from requests.packages.urllib3.exceptions import InsecureRequestWarning, InsecurePlatformWarning requests.packages.urllib3.disable_warnings(InsecureRequestWarning) requests.packages.urllib3.disable_warnings(InsecurePlatformWarning) -#logging.getLogger('requests').setLevel(logging.WARNING) + +log = logging.getLogger("EMBY."+__name__) ################################################################################################## @@ -25,7 +26,6 @@ class ConnectUtils(): # Borg - multiple instances, shared state _shared_state = {} clientInfo = clientinfo.ClientInfo() - addonName = clientInfo.getAddonName() # Requests session c = None @@ -34,26 +34,23 @@ class ConnectUtils(): def __init__(self): - global log - log = Logging(self.__class__.__name__).log - self.__dict__ = self._shared_state def setUserId(self, userId): # Reserved for userclient only self.userId = userId - log("Set connect userId: %s" % userId, 2) + log.debug("Set connect userId: %s" % userId) def setServer(self, server): # Reserved for userclient only self.server = server - log("Set connect server: %s" % server, 2) + log.debug("Set connect server: %s" % server) def setToken(self, token): # Reserved for userclient only self.token = token - log("Set connect token: %s" % token, 2) + log.debug("Set connect token: %s" % token) def startSession(self): @@ -71,7 +68,7 @@ class ConnectUtils(): if self.sslclient is not None: verify = self.sslclient except: - log("Could not load SSL settings.", 1) + log.info("Could not load SSL settings.") # Start session self.c = requests.Session() @@ -81,13 +78,13 @@ 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) + log.info("Requests session started on: %s" % self.server) def stopSession(self): try: self.c.close() - except Exception as e: - log("Requests session could not be terminated: %s" % e, 1) + except Exception: + log.warn("Requests session could not be terminated") def getHeader(self, authenticate=True): @@ -101,7 +98,7 @@ class ConnectUtils(): 'Content-type': 'application/x-www-form-urlencoded; charset=UTF-8', 'Accept': "application/json" } - log("Header: %s" % header, 1) + log.info("Header: %s" % header) else: token = self.token @@ -113,14 +110,14 @@ class ConnectUtils(): 'X-Application': "Kodi/%s" % version, 'X-Connect-UserToken': token } - log("Header: %s" % header, 1) + log.info("Header: %s" % header) return header def doUrl(self, url, data=None, postBody=None, rtype="GET", parameters=None, authenticate=True, timeout=None): - log("=== ENTER connectUrl ===", 2) + log.debug("=== ENTER connectUrl ===") default_link = "" @@ -207,25 +204,25 @@ class ConnectUtils(): verify=verifyssl) ##### THE RESPONSE ##### - log(r.url, 1) - log(r, 1) + log.info(r.url) + log.info(r) if r.status_code == 204: # No body in the response - log("====== 204 Success ======", 1) + log.info("====== 204 Success ======") elif r.status_code == requests.codes.ok: try: # UNICODE - JSON object r = r.json() - log("====== 200 Success ======", 1) - log("Response: %s" % r, 1) + log.info("====== 200 Success ======") + log.info("Response: %s" % r) return r except: if r.headers.get('content-type') != "text/html": - log("Unable to convert the response for: %s" % url, 1) + log.info("Unable to convert the response for: %s" % url) else: r.raise_for_status() @@ -236,8 +233,7 @@ class ConnectUtils(): pass except requests.exceptions.ConnectTimeout as e: - log("Server timeout at: %s" % url, 0) - log(e, 1) + log.warn("Server timeout at: %s" % url) except requests.exceptions.HTTPError as e: @@ -253,11 +249,9 @@ class ConnectUtils(): pass except requests.exceptions.SSLError as e: - log("Invalid SSL certificate for: %s" % url, 0) - log(e, 1) + log.warn("Invalid SSL certificate for: %s" % url) except requests.exceptions.RequestException as e: - log("Unknown error connecting to: %s" % url, 0) - log(e, 1) + log.warn("Unknown error connecting to: %s" % url) return default_link \ No newline at end of file diff --git a/resources/lib/downloadutils.py b/resources/lib/downloadutils.py index 34596c2f..47a5db59 100644 --- a/resources/lib/downloadutils.py +++ b/resources/lib/downloadutils.py @@ -10,7 +10,7 @@ import xbmc import xbmcgui import clientinfo -from utils import Logging, window, settings +from utils import window, settings ################################################################################################## @@ -18,7 +18,8 @@ from utils import Logging, window, settings from requests.packages.urllib3.exceptions import InsecureRequestWarning, InsecurePlatformWarning requests.packages.urllib3.disable_warnings(InsecureRequestWarning) requests.packages.urllib3.disable_warnings(InsecurePlatformWarning) -#logging.getLogger('requests').setLevel(logging.WARNING) + +log = logging.getLogger("EMBY."+__name__) ################################################################################################## @@ -36,38 +37,35 @@ class DownloadUtils(): def __init__(self): - global log - log = Logging(self.__class__.__name__).log - self.__dict__ = self._shared_state def setUsername(self, username): # Reserved for userclient only self.username = username - log("Set username: %s" % username, 2) + log.debug("Set username: %s" % username) def setUserId(self, userId): # Reserved for userclient only self.userId = userId - log("Set userId: %s" % userId, 2) + log.debug("Set userId: %s" % userId) def setServer(self, server): # Reserved for userclient only self.server = server - log("Set server: %s" % server, 2) + log.debug("Set server: %s" % server) def setToken(self, token): # Reserved for userclient only self.token = token - log("Set token: %s" % token, 2) + log.debug("Set token: %s" % token) def setSSL(self, ssl, sslclient): # Reserved for userclient only self.sslverify = ssl self.sslclient = sslclient - log("Verify SSL host certificate: %s" % ssl, 2) - log("SSL client side certificate: %s" % sslclient, 2) + log.debug("Verify SSL host certificate: %s" % ssl) + log.debug("SSL client side certificate: %s" % sslclient) def postCapabilities(self, deviceId): @@ -92,11 +90,11 @@ class DownloadUtils(): ) } - log("Capabilities URL: %s" % url, 2) - log("Postdata: %s" % data, 2) + log.debug("Capabilities URL: %s" % url) + log.debug("Postdata: %s" % data) self.downloadUrl(url, postBody=data, action_type="POST") - log("Posted capabilities to %s" % self.server, 2) + log.debug("Posted capabilities to %s" % self.server) # Attempt at getting sessionId url = "{server}/emby/Sessions?DeviceId=%s&format=json" % deviceId @@ -105,11 +103,11 @@ class DownloadUtils(): sessionId = result[0]['Id'] except (KeyError, TypeError): - log("Failed to retrieve sessionId.", 1) + log.info("Failed to retrieve sessionId.") else: - log("Session: %s" % result, 2) - log("SessionId: %s" % sessionId, 1) + log.debug("Session: %s" % result) + log.info("SessionId: %s" % sessionId) window('emby_sessionId', value=sessionId) # Post any permanent additional users @@ -117,8 +115,7 @@ class DownloadUtils(): if additionalUsers: additionalUsers = additionalUsers.split(',') - log("List of permanent users added to the session: %s" - % additionalUsers, 1) + log.info("List of permanent users added to the session: %s" % additionalUsers) # Get the user list from server to get the userId url = "{server}/emby/Users?format=json" @@ -155,7 +152,7 @@ class DownloadUtils(): if self.sslclient is not None: verify = self.sslclient except: - log("Could not load SSL settings.", 1) + log.info("Could not load SSL settings.") # Start session self.s = requests.Session() @@ -165,13 +162,13 @@ 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) + log.info("Requests session started on: %s" % self.server) def stopSession(self): try: self.s.close() - except: - log("Requests session could not be terminated.", 1) + except Exception: + log.warn("Requests session could not be terminated.") def getHeader(self, authenticate=True): @@ -210,7 +207,7 @@ class DownloadUtils(): def downloadUrl(self, url, postBody=None, action_type="GET", parameters=None, authenticate=True): - log("===== ENTER downloadUrl =====", 2) + log.debug("===== ENTER downloadUrl =====") session = requests kwargs = {} @@ -267,12 +264,12 @@ class DownloadUtils(): }) ##### THE RESPONSE ##### - log(kwargs, 2) + log.debug(kwargs) r = self._requests(action_type, session, **kwargs) if r.status_code == 204: # No body in the response - log("====== 204 Success ======", 2) + log.debug("====== 204 Success ======") # Read response to release connection r.content @@ -280,15 +277,16 @@ class DownloadUtils(): try: # UNICODE - JSON object r = r.json() - log("====== 200 Success ======", 2) - log("Response: %s" % r, 2) + log.debug("====== 200 Success ======") + log.debug("Response: %s" % r) return r except: if r.headers.get('content-type') != "text/html": - log("Unable to convert the response for: %s" % url, 1) + log.info("Unable to convert the response for: %s" % url) else: # Bad status code + log.error("=== Bad status response: %s ===" % r.status_code) r.raise_for_status() ##### EXCEPTIONS ##### @@ -296,13 +294,11 @@ class DownloadUtils(): except requests.exceptions.ConnectionError as e: # Make the addon aware of status if window('emby_online') != "false": - log("Server unreachable at: %s" % url, 0) - log(e, 2) + log.warn("Server unreachable at: %s" % url) window('emby_online', value="false") except requests.exceptions.ConnectTimeout as e: - log("Server timeout at: %s" % url, 0) - log(e, 1) + log.warn("Server timeout at: %s" % url) except requests.exceptions.HTTPError as e: @@ -329,7 +325,7 @@ class DownloadUtils(): elif status not in ("401", "Auth"): # Tell userclient token has been revoked. window('emby_serverStatus', value="401") - log("HTTP Error: %s" % e, 0) + log.warn("HTTP Error: %s" % e) xbmcgui.Dialog().notification( heading="Error connecting", message="Unauthorized.", @@ -344,12 +340,10 @@ class DownloadUtils(): pass except requests.exceptions.SSLError as e: - log("Invalid SSL certificate for: %s" % url, 0) - log(e, 1) + log.warn("Invalid SSL certificate for: %s" % url) except requests.exceptions.RequestException as e: - log("Unknown error connecting to: %s" % url, 0) - log(e, 1) + log.warn("Unknown error connecting to: %s" % url) return default_link diff --git a/resources/lib/embydb_functions.py b/resources/lib/embydb_functions.py index 73e808d8..9220fa5d 100644 --- a/resources/lib/embydb_functions.py +++ b/resources/lib/embydb_functions.py @@ -2,12 +2,14 @@ ################################################################################################# +import logging from sqlite3 import OperationalError -import clientinfo -from utils import Logging +################################################################################################## -################################################################################################# +log = logging.getLogger("EMBY."+__name__) + +################################################################################################## class Embydb_Functions(): @@ -15,14 +17,8 @@ class Embydb_Functions(): def __init__(self, embycursor): - global log - log = Logging(self.__class__.__name__).log - self.embycursor = embycursor - self.clientInfo = clientinfo.ClientInfo() - self.addonName = self.clientInfo.getAddonName() - def getViews(self): diff --git a/resources/lib/entrypoint.py b/resources/lib/entrypoint.py index ba4ffe22..e532e5e2 100644 --- a/resources/lib/entrypoint.py +++ b/resources/lib/entrypoint.py @@ -3,6 +3,7 @@ ################################################################################################# import json +import logging import os import sys import urlparse @@ -24,8 +25,11 @@ import playlist import playbackutils as pbutils import playutils import api -from utils import Logging, window, settings, language as lang -log = Logging('Entrypoint').log +from utils import window, settings, language as lang + +################################################################################################# + +log = logging.getLogger("EMBY."+__name__) ################################################################################################# @@ -43,7 +47,7 @@ def resetAuth(): heading=lang(30132), line1=lang(33050)) if resp: - log("Reset login attempts.", 1) + log.info("Reset login attempts.") window('emby_serverStatus', value="Auth") else: xbmc.executebuiltin('Addon.OpenSettings(plugin.video.emby)') @@ -121,12 +125,12 @@ def resetDeviceId(): window('emby_deviceId', clear=True) deviceId = clientinfo.ClientInfo().getDeviceId(reset=True) except Exception as e: - log("Failed to generate a new device Id: %s" % e, 1) + log.error("Failed to generate a new device Id: %s" % e) dialog.ok( heading=lang(29999), line1=lang(33032)) else: - log("Successfully removed old deviceId: %s New deviceId: %s" % (deviceId_old, deviceId), 1) + log.info("Successfully removed old deviceId: %s New deviceId: %s" % (deviceId_old, deviceId)) dialog.ok( heading=lang(29999), line1=lang(33033)) @@ -153,7 +157,7 @@ def deleteItem(): elif xbmc.getCondVisibility('Container.Content(pictures)'): itemType = "picture" else: - log("Unknown type, unable to proceed.", 1) + log.info("Unknown type, unable to proceed.") return embyconn = utils.kodiSQL('emby') @@ -165,7 +169,7 @@ def deleteItem(): try: itemId = item[0] except TypeError: - log("Unknown itemId, unable to proceed.", 1) + log.error("Unknown itemId, unable to proceed.") return if settings('skipContextMenu') != "true": @@ -173,7 +177,7 @@ def deleteItem(): heading=lang(29999), line1=lang(33041)) if not resp: - log("User skipped deletion for: %s." % itemId, 1) + log.info("User skipped deletion for: %s." % itemId) return embyserver.Read_EmbyServer().deleteItem(itemId) @@ -257,7 +261,7 @@ def addUser(): return # Subtract any additional users - log("Displaying list of users: %s" % users) + log.info("Displaying list of users: %s" % users) resp = dialog.select("Add user to the session", users) # post additional user if resp > -1: @@ -272,7 +276,7 @@ def addUser(): time=1000) except: - log("Failed to add user to session.") + log.error("Failed to add user to session.") dialog.notification( heading=lang(29999), message=lang(33068), @@ -328,7 +332,7 @@ def getThemeMedia(): tvtunes = xbmcaddon.Addon(id="script.tvtunes") tvtunes.setSetting('custom_path_enable', "true") tvtunes.setSetting('custom_path', library) - log("TV Tunes custom path is enabled and set.", 1) + log.info("TV Tunes custom path is enabled and set.") 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. @@ -474,7 +478,7 @@ def refreshPlaylist(): sound=False) except Exception as e: - log("Refresh playlists/nodes failed: %s" % e, 1) + log.error("Refresh playlists/nodes failed: %s" % e) dialog.notification( heading=lang(29999), message=lang(33070), @@ -516,7 +520,7 @@ def BrowseContent(viewname, browse_type="", folderid=""): break if viewname is not None: - log("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'))) + log.info("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: @@ -1049,7 +1053,7 @@ def getExtraFanArt(embyId,embyPath): if embyId: #only proceed if we actually have a emby id - log("Requesting extrafanart for Id: %s" % embyId, 0) + log.info("Requesting extrafanart for Id: %s" % embyId) # We need to store the images locally for this to work # because of the caching system in xbmc @@ -1078,7 +1082,7 @@ def getExtraFanArt(embyId,embyPath): xbmcvfs.copy(backdrop, fanartFile) count += 1 else: - log("Found cached backdrop.", 2) + log.debug("Found cached backdrop.") # Use existing cached images dirs, files = xbmcvfs.listdir(fanartDir) for file in files: @@ -1089,7 +1093,7 @@ def getExtraFanArt(embyId,embyPath): url=fanartFile, listitem=li) except Exception as e: - log("Error getting extrafanart: %s" % e, 0) + log.error("Error getting extrafanart: %s" % e) # 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/image_cache_thread.py b/resources/lib/image_cache_thread.py index fdf63d63..ffed967b 100644 --- a/resources/lib/image_cache_thread.py +++ b/resources/lib/image_cache_thread.py @@ -2,10 +2,13 @@ ################################################################################################# -import threading +import logging import requests +import threading -from utils import Logging +################################################################################################# + +log = logging.getLogger("EMBY."+__name__) ################################################################################################# @@ -22,9 +25,6 @@ class image_cache_thread(threading.Thread): def __init__(self): - global log - log = Logging(self.__class__.__name__).log - threading.Thread.__init__(self) @@ -44,7 +44,7 @@ class image_cache_thread(threading.Thread): def run(self): - log("Image Caching Thread Processing: %s" % self.urlToProcess, 2) + log.debug("Image Caching Thread Processing: %s" % self.urlToProcess) try: response = requests.head( @@ -56,5 +56,5 @@ class image_cache_thread(threading.Thread): # We don't need the result except: pass - log("Image Caching Thread Exited", 2) + log.debug("Image Caching Thread Exited") self.isFinished = True \ No newline at end of file diff --git a/resources/lib/initialsetup.py b/resources/lib/initialsetup.py index da0a5108..032affb1 100644 --- a/resources/lib/initialsetup.py +++ b/resources/lib/initialsetup.py @@ -3,6 +3,7 @@ ################################################################################################# import json +import logging import socket import xbmc @@ -12,7 +13,11 @@ import xbmcaddon import clientinfo import downloadutils import userclient -from utils import Logging, settings, language as lang, passwordsXML +from utils import settings, language as lang, passwordsXML + +################################################################################################# + +log = logging.getLogger("EMBY."+__name__) ################################################################################################# @@ -22,9 +27,6 @@ class InitialSetup(): def __init__(self): - global log - log = Logging(self.__class__.__name__).log - self.addonId = clientinfo.ClientInfo().getAddonId() self.doUtils = downloadutils.DownloadUtils().downloadUrl self.userClient = userclient.UserClient() @@ -37,20 +39,20 @@ class InitialSetup(): ##### SERVER INFO ##### - log("Initial setup called.", 2) + log.debug("Initial setup called.") server = self.userClient.getServer() if server: - log("Server is already set.", 2) + log.debug("Server is already set.") return - log("Looking for server...", 2) + log.debug("Looking for server...") server = self.getServerDetails() - log("Found: %s" % server, 2) + log.debug("Found: %s" % server) try: prefix, ip, port = server.replace("/", "").split(":") - except: # Failed to retrieve server information - log("getServerDetails failed.", 1) + except Exception: # Failed to retrieve server information + log.error("getServerDetails failed.") xbmc.executebuiltin('Addon.OpenSettings(%s)' % addonId) return else: @@ -60,7 +62,7 @@ class InitialSetup(): line2="%s %s" % (lang(30169), server)) if server_confirm: # Correct server found - log("Server is selected. Saving the information.", 1) + log.info("Server is selected. Saving the information.") settings('ipaddress', value=ip) settings('port', value=port) @@ -68,20 +70,20 @@ class InitialSetup(): settings('https', value="true") else: # User selected no or cancelled the dialog - log("No server selected.", 1) + log.info("No server selected.") xbmc.executebuiltin('Addon.OpenSettings(%s)' % addonId) return ##### USER INFO ##### - log("Getting user list.", 1) + log.info("Getting user list.") result = self.doUtils("%s/emby/Users/Public?format=json" % server, authenticate=False) if result == "": - log("Unable to connect to %s" % server, 1) + log.info("Unable to connect to %s" % server) return - log("Response: %s" % result, 2) + log.debug("Response: %s" % result) # Process the list of users usernames = [] users_hasPassword = [] @@ -95,14 +97,14 @@ class InitialSetup(): name = "%s (secure)" % name users_hasPassword.append(name) - log("Presenting user list: %s" % users_hasPassword, 1) + log.info("Presenting user list: %s" % users_hasPassword) user_select = dialog.select(lang(30200), users_hasPassword) if user_select > -1: selected_user = usernames[user_select] - log("Selected user: %s" % selected_user, 1) + log.info("Selected user: %s" % selected_user) settings('username', value=selected_user) else: - log("No user selected.", 1) + log.info("No user selected.") xbmc.executebuiltin('Addon.OpenSettings(%s)' % addonId) return @@ -114,7 +116,7 @@ class InitialSetup(): nolabel=lang(33036), yeslabel=lang(33037)) if directPaths: - log("User opted to use direct paths.", 1) + log.info("User opted to use direct paths.") settings('useDirectPaths', value="1") # ask for credentials @@ -122,14 +124,14 @@ class InitialSetup(): heading=lang(30517), line1= lang(33038)) if credentials: - log("Presenting network credentials dialog.", 1) + log.info("Presenting network credentials dialog.") passwordsXML() musicDisabled = dialog.yesno( heading=lang(29999), line1=lang(33039)) if musicDisabled: - log("User opted to disable Emby music library.", 1) + log.info("User opted to disable Emby music library.") settings('enableMusic', value="false") else: # Only prompt if the user didn't select direct paths for videos @@ -138,12 +140,12 @@ class InitialSetup(): heading=lang(29999), line1=lang(33040)) if musicAccess: - log("User opted to direct stream music.", 1) + log.info("User opted to direct stream music.") settings('streamMusic', value="true") def getServerDetails(self): - log("Getting Server Details from Network", 1) + log.info("Getting Server Details from Network") MULTI_GROUP = ("", 7359) MESSAGE = "who is EmbyServer?" @@ -157,15 +159,15 @@ class InitialSetup(): sock.setsockopt(socket.SOL_IP, socket.IP_MULTICAST_LOOP, 1) sock.setsockopt(socket.IPPROTO_IP, socket.SO_REUSEADDR, 1) - log("MultiGroup : %s" % str(MULTI_GROUP), 2) - log("Sending UDP Data: %s" % MESSAGE, 2) + log.debug("MultiGroup : %s" % str(MULTI_GROUP)) + log.debug("Sending UDP Data: %s" % MESSAGE) sock.sendto(MESSAGE, MULTI_GROUP) try: data, addr = sock.recvfrom(1024) # buffer size is 1024 bytes - log("Received Response: %s" % data) - except: - log("No UDP Response") + log.info("Received Response: %s" % data) + except Exception: + log.error("No UDP Response") return None else: # Get the address diff --git a/resources/lib/itemtypes.py b/resources/lib/itemtypes.py index 934efcee..7438c83f 100644 --- a/resources/lib/itemtypes.py +++ b/resources/lib/itemtypes.py @@ -2,6 +2,7 @@ ################################################################################################## +import logging import urllib from ntpath import dirname from datetime import datetime @@ -18,9 +19,13 @@ import embydb_functions as embydb import kodidb_functions as kodidb import read_embyserver as embyserver import musicutils -from utils import Logging, window, settings, language as lang, kodiSQL +from utils import window, settings, language as lang, kodiSQL -################################################################################################## +################################################################################################# + +log = logging.getLogger("EMBY."+__name__) + +################################################################################################# class Items(object): @@ -28,9 +33,6 @@ class Items(object): def __init__(self, embycursor, kodicursor): - global log - log = Logging(self.__class__.__name__).log - self.embycursor = embycursor self.kodicursor = kodicursor @@ -79,7 +81,7 @@ class Items(object): if total == 0: return False - log("Processing %s: %s" % (process, items), 1) + log.info("Processing %s: %s" % (process, items)) if pdialog: pdialog.update(heading="Processing %s: %s items" % (process, total)) @@ -171,7 +173,7 @@ class Items(object): 'remove': items_process.remove } else: - log("Unsupported itemtype: %s." % itemtype, 1) + log.info("Unsupported itemtype: %s." % itemtype) actions = {} if actions.get(process): @@ -202,7 +204,7 @@ class Items(object): if musicconn is not None: # close connection for special types - log("Updating music database.", 1) + log.info("Updating music database.") musicconn.commit() musiccursor.close() @@ -282,11 +284,11 @@ class Movies(Items): movieid = emby_dbitem[0] fileid = emby_dbitem[1] pathid = emby_dbitem[2] - log("movieid: %s fileid: %s pathid: %s" % (movieid, fileid, pathid), 1) + log.info("movieid: %s fileid: %s pathid: %s" % (movieid, fileid, pathid)) except TypeError: update_item = False - log("movieid: %s not found." % itemid, 2) + log.info("movieid: %s not found." % itemid) # movieid kodicursor.execute("select coalesce(max(idMovie),0) from movie") movieid = kodicursor.fetchone()[0] + 1 @@ -300,12 +302,12 @@ class Movies(Items): except TypeError: # item is not found, let's recreate it. update_item = False - log("movieid: %s missing from Kodi, repairing the entry." % movieid, 1) + log.info("movieid: %s missing from Kodi, repairing the entry." % movieid) if not viewtag or not viewid: # Get view tag from emby viewtag, viewid, mediatype = self.emby.getView_embyId(itemid) - log("View tag found: %s" % viewtag, 2) + log.debug("View tag found: %s" % viewtag) # fileId information checksum = API.getChecksum() @@ -348,7 +350,7 @@ class Movies(Items): try: trailer = "plugin://plugin.video.emby/trailer/?id=%s&mode=play" % result[0]['Id'] except IndexError: - log("Failed to process local trailer.", 1) + log.info("Failed to process local trailer.") trailer = None else: # Try to get the youtube trailer @@ -360,7 +362,7 @@ class Movies(Items): try: trailerId = trailer.rsplit('=', 1)[1] except IndexError: - log("Failed to process trailer: %s" % trailer, 1) + log.info("Failed to process trailer: %s" % trailer) trailer = None else: trailer = "plugin://plugin.video.youtube/play/?video_id=%s" % trailerId @@ -397,7 +399,7 @@ class Movies(Items): ##### UPDATE THE MOVIE ##### if update_item: - log("UPDATE movie itemid: %s - Title: %s" % (itemid, title), 1) + log.info("UPDATE movie itemid: %s - Title: %s" % (itemid, title)) # Update the movie entry query = ' '.join(( @@ -417,7 +419,7 @@ class Movies(Items): ##### OR ADD THE MOVIE ##### else: - log("ADD movie itemid: %s - Title: %s" % (itemid, title), 1) + log.info("ADD movie itemid: %s - Title: %s" % (itemid, title)) # Add path pathid = self.kodi_db.addPath(path) @@ -527,10 +529,10 @@ class Movies(Items): try: movieid = emby_dbitem[0] except TypeError: - log("Failed to add: %s to boxset." % movie['Name'], 1) + log.info("Failed to add: %s to boxset." % movie['Name']) continue - log("New addition to boxset %s: %s" % (title, movie['Name']), 1) + log.info("New addition to boxset %s: %s" % (title, movie['Name'])) self.kodi_db.assignBoxset(setid, movieid) # Update emby reference emby_db.updateParentId(itemid, setid) @@ -541,7 +543,7 @@ class Movies(Items): # Process removals from boxset for movie in process: movieid = current[movie] - log("Remove from boxset %s: %s" % (title, movieid)) + log.info("Remove from boxset %s: %s" % (title, movieid)) self.kodi_db.removefromBoxset(movieid) # Update emby reference emby_db.updateParentId(movie, None) @@ -566,7 +568,7 @@ class Movies(Items): try: movieid = emby_dbitem[0] fileid = emby_dbitem[1] - log("Update playstate for movie: %s fileid: %s" % (item['Name'], fileid), 1) + log.info("Update playstate for movie: %s fileid: %s" % (item['Name'], fileid)) except TypeError: return @@ -582,7 +584,7 @@ class Movies(Items): resume = API.adjustResume(userdata['Resume']) total = round(float(runtime), 6) - log("%s New resume point: %s" % (itemid, resume)) + log.debug("%s New resume point: %s" % (itemid, resume)) self.kodi_db.addPlaystate(fileid, resume, total, playcount, dateplayed) emby_db.updateReference(itemid, checksum) @@ -598,7 +600,7 @@ class Movies(Items): kodiid = emby_dbitem[0] fileid = emby_dbitem[1] mediatype = emby_dbitem[4] - log("Removing %sid: %s fileid: %s" % (mediatype, kodiid, fileid), 1) + log.info("Removing %sid: %s fileid: %s" % (mediatype, kodiid, fileid)) except TypeError: return @@ -624,7 +626,7 @@ class Movies(Items): kodicursor.execute("DELETE FROM sets WHERE idSet = ?", (kodiid,)) - log("Deleted %s %s from kodi database" % (mediatype, itemid), 1) + log.info("Deleted %s %s from kodi database" % (mediatype, itemid)) class MusicVideos(Items): @@ -664,11 +666,11 @@ class MusicVideos(Items): mvideoid = emby_dbitem[0] fileid = emby_dbitem[1] pathid = emby_dbitem[2] - log("mvideoid: %s fileid: %s pathid: %s" % (mvideoid, fileid, pathid), 1) + log.info("mvideoid: %s fileid: %s pathid: %s" % (mvideoid, fileid, pathid)) except TypeError: update_item = False - log("mvideoid: %s not found." % itemid, 2) + log.info("mvideoid: %s not found." % itemid) # mvideoid kodicursor.execute("select coalesce(max(idMVideo),0) from musicvideo") mvideoid = kodicursor.fetchone()[0] + 1 @@ -682,12 +684,12 @@ class MusicVideos(Items): except TypeError: # item is not found, let's recreate it. update_item = False - log("mvideoid: %s missing from Kodi, repairing the entry." % mvideoid, 1) + log.info("mvideoid: %s missing from Kodi, repairing the entry." % mvideoid) if not viewtag or not viewid: # Get view tag from emby viewtag, viewid, mediatype = self.emby.getView_embyId(itemid) - log("View tag found: %s" % viewtag, 2) + log.debug("View tag found: %s" % viewtag) # fileId information checksum = API.getChecksum() @@ -743,7 +745,7 @@ class MusicVideos(Items): ##### UPDATE THE MUSIC VIDEO ##### if update_item: - log("UPDATE mvideo itemid: %s - Title: %s" % (itemid, title), 1) + log.info("UPDATE mvideo itemid: %s - Title: %s" % (itemid, title)) # Update path query = "UPDATE path SET strPath = ? WHERE idPath = ?" @@ -769,7 +771,7 @@ class MusicVideos(Items): ##### OR ADD THE MUSIC VIDEO ##### else: - log("ADD mvideo itemid: %s - Title: %s" % (itemid, title), 1) + log.info("ADD mvideo itemid: %s - Title: %s" % (itemid, title)) # Add path query = ' '.join(( @@ -869,9 +871,9 @@ class MusicVideos(Items): try: mvideoid = emby_dbitem[0] fileid = emby_dbitem[1] - log( + log.info( "Update playstate for musicvideo: %s fileid: %s" - % (item['Name'], fileid), 1) + % (item['Name'], fileid)) except TypeError: return @@ -901,7 +903,7 @@ class MusicVideos(Items): mvideoid = emby_dbitem[0] fileid = emby_dbitem[1] pathid = emby_dbitem[2] - log("Removing mvideoid: %s fileid: %s" % (mvideoid, fileid, pathid), 1) + log.info("Removing mvideoid: %s fileid: %s" % (mvideoid, fileid, pathid)) except TypeError: return @@ -927,7 +929,7 @@ class MusicVideos(Items): kodicursor.execute("DELETE FROM path WHERE idPath = ?", (pathid,)) self.embycursor.execute("DELETE FROM emby WHERE emby_id = ?", (itemid,)) - log("Deleted musicvideo %s from kodi database" % itemid, 1) + log.info("Deleted musicvideo %s from kodi database" % itemid) class TVShows(Items): @@ -991,7 +993,7 @@ class TVShows(Items): API = api.API(item) if settings('syncEmptyShows') == "false" and not item.get('RecursiveItemCount'): - log("Skipping empty show: %s" % item['Name'], 1) + log.info("Skipping empty show: %s" % item['Name']) 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 @@ -1002,11 +1004,11 @@ class TVShows(Items): try: showid = emby_dbitem[0] pathid = emby_dbitem[2] - log("showid: %s pathid: %s" % (showid, pathid), 1) + log.info("showid: %s pathid: %s" % (showid, pathid)) except TypeError: update_item = False - log("showid: %s not found." % itemid, 2) + log.info("showid: %s not found." % itemid) kodicursor.execute("select coalesce(max(idShow),0) from tvshow") showid = kodicursor.fetchone()[0] + 1 @@ -1019,7 +1021,7 @@ class TVShows(Items): except TypeError: # item is not found, let's recreate it. update_item = False - log("showid: %s missing from Kodi, repairing the entry." % showid, 1) + log.info("showid: %s missing from Kodi, repairing the entry." % showid) # Force re-add episodes after the show is re-created. force_episodes = True @@ -1027,7 +1029,7 @@ class TVShows(Items): if viewtag is None or viewid is None: # Get view tag from emby viewtag, viewid, mediatype = emby.getView_embyId(itemid) - log("View tag found: %s" % viewtag, 2) + log.debug("View tag found: %s" % viewtag) # fileId information checksum = API.getChecksum() @@ -1076,7 +1078,7 @@ class TVShows(Items): ##### UPDATE THE TVSHOW ##### if update_item: - log("UPDATE tvshow itemid: %s - Title: %s" % (itemid, title), 1) + log.info("UPDATE tvshow itemid: %s - Title: %s" % (itemid, title)) # Update the tvshow entry query = ' '.join(( @@ -1094,7 +1096,7 @@ class TVShows(Items): ##### OR ADD THE TVSHOW ##### else: - log("ADD tvshow itemid: %s - Title: %s" % (itemid, title), 1) + log.info("ADD tvshow itemid: %s - Title: %s" % (itemid, title)) # Add top path toppathid = self.kodi_db.addPath(toplevelpath) @@ -1165,7 +1167,7 @@ class TVShows(Items): if force_episodes: # We needed to recreate the show entry. Re-add episodes now. - log("Repairing episodes for showid: %s %s" % (showid, title), 1) + log.info("Repairing episodes for showid: %s %s" % (showid, title)) all_episodes = emby.getEpisodesbyShow(itemid) self.added_episode(all_episodes['Items'], None) @@ -1214,11 +1216,11 @@ class TVShows(Items): episodeid = emby_dbitem[0] fileid = emby_dbitem[1] pathid = emby_dbitem[2] - log("episodeid: %s fileid: %s pathid: %s" % (episodeid, fileid, pathid), 1) + log.info("episodeid: %s fileid: %s pathid: %s" % (episodeid, fileid, pathid)) except TypeError: update_item = False - log("episodeid: %s not found." % itemid, 2) + log.info("episodeid: %s not found." % itemid) # episodeid kodicursor.execute("select coalesce(max(idEpisode),0) from episode") episodeid = kodicursor.fetchone()[0] + 1 @@ -1232,7 +1234,7 @@ class TVShows(Items): except TypeError: # item is not found, let's recreate it. update_item = False - log("episodeid: %s missing from Kodi, repairing the entry." % episodeid, 1) + log.info("episodeid: %s missing from Kodi, repairing the entry." % episodeid) # fileId information checksum = API.getChecksum() @@ -1256,7 +1258,7 @@ class TVShows(Items): seriesId = item['SeriesId'] except KeyError: # Missing seriesId, skip - log("Skipping: %s. SeriesId is missing." % itemid, 1) + log.error("Skipping: %s. SeriesId is missing." % itemid) return False season = item.get('ParentIndexNumber') @@ -1294,7 +1296,7 @@ class TVShows(Items): try: showid = show[0] except TypeError: - log("Skipping: %s. Unable to add series: %s." % (itemid, seriesId)) + log.error("Skipping: %s. Unable to add series: %s." % (itemid, seriesId)) return False seasonid = self.kodi_db.addSeason(showid, season) @@ -1331,7 +1333,7 @@ class TVShows(Items): ##### UPDATE THE EPISODE ##### if update_item: - log("UPDATE episode itemid: %s - Title: %s" % (itemid, title), 1) + log.info("UPDATE episode itemid: %s - Title: %s" % (itemid, title)) # Update the movie entry if self.kodiversion in (16, 17): @@ -1365,7 +1367,7 @@ class TVShows(Items): ##### OR ADD THE EPISODE ##### else: - log("ADD episode itemid: %s - Title: %s" % (itemid, title), 1) + log.info("ADD episode itemid: %s - Title: %s" % (itemid, title)) # Add path pathid = self.kodi_db.addPath(path) @@ -1468,9 +1470,9 @@ class TVShows(Items): kodiid = emby_dbitem[0] fileid = emby_dbitem[1] mediatype = emby_dbitem[4] - log( + log.info( "Update playstate for %s: %s fileid: %s" - % (mediatype, item['Name'], fileid), 1) + % (mediatype, item['Name'], fileid)) except TypeError: return @@ -1487,7 +1489,7 @@ class TVShows(Items): resume = API.adjustResume(userdata['Resume']) total = round(float(runtime), 6) - log("%s New resume point: %s" % (itemid, resume)) + log.debug("%s New resume point: %s" % (itemid, resume)) self.kodi_db.addPlaystate(fileid, resume, total, playcount, dateplayed) if not self.directpath and not resume: @@ -1525,7 +1527,7 @@ class TVShows(Items): pathid = emby_dbitem[2] parentid = emby_dbitem[3] mediatype = emby_dbitem[4] - log("Removing %s kodiid: %s fileid: %s" % (mediatype, kodiid, fileid), 1) + log.info("Removing %s kodiid: %s fileid: %s" % (mediatype, kodiid, fileid)) except TypeError: return @@ -1615,14 +1617,14 @@ class TVShows(Items): self.removeShow(parentid) emby_db.removeItem_byKodiId(parentid, "tvshow") - log("Deleted %s: %s from kodi database" % (mediatype, itemid), 1) + log.info("Deleted %s: %s from kodi database" % (mediatype, itemid)) def removeShow(self, kodiid): kodicursor = self.kodicursor self.artwork.deleteArtwork(kodiid, "tvshow", kodicursor) kodicursor.execute("DELETE FROM tvshow WHERE idShow = ?", (kodiid,)) - log("Removed tvshow: %s." % kodiid, 2) + log.debug("Removed tvshow: %s." % kodiid) def removeSeason(self, kodiid): @@ -1630,7 +1632,7 @@ class TVShows(Items): self.artwork.deleteArtwork(kodiid, "season", kodicursor) kodicursor.execute("DELETE FROM seasons WHERE idSeason = ?", (kodiid,)) - log("Removed season: %s." % kodiid, 2) + log.debug("Removed season: %s." % kodiid) def removeEpisode(self, kodiid, fileid): @@ -1639,7 +1641,7 @@ class TVShows(Items): self.artwork.deleteArtwork(kodiid, "episode", kodicursor) kodicursor.execute("DELETE FROM episode WHERE idEpisode = ?", (kodiid,)) kodicursor.execute("DELETE FROM files WHERE idFile = ?", (fileid,)) - log("Removed episode: %s." % kodiid, 2) + log.debug("Removed episode: %s." % kodiid) class Music(Items): @@ -1713,7 +1715,7 @@ class Music(Items): artistid = emby_dbitem[0] except TypeError: update_item = False - log("artistid: %s not found." % itemid, 2) + log.debug("artistid: %s not found." % itemid) ##### The artist details ##### lastScraped = datetime.now().strftime('%Y-%m-%d %H:%M:%S') @@ -1740,13 +1742,13 @@ class Music(Items): ##### UPDATE THE ARTIST ##### if update_item: - log("UPDATE artist itemid: %s - Name: %s" % (itemid, name), 1) + log.info("UPDATE artist itemid: %s - Name: %s" % (itemid, name)) # Update the checksum in emby table emby_db.updateReference(itemid, checksum) ##### OR ADD THE ARTIST ##### else: - log("ADD artist itemid: %s - Name: %s" % (itemid, name), 1) + log.info("ADD artist itemid: %s - Name: %s" % (itemid, name)) # 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) @@ -1794,7 +1796,7 @@ class Music(Items): albumid = emby_dbitem[0] except TypeError: update_item = False - log("albumid: %s not found." % itemid, 2) + log.debug("albumid: %s not found." % itemid) ##### The album details ##### lastScraped = datetime.now().strftime('%Y-%m-%d %H:%M:%S') @@ -1825,13 +1827,13 @@ class Music(Items): ##### UPDATE THE ALBUM ##### if update_item: - log("UPDATE album itemid: %s - Name: %s" % (itemid, name), 1) + log.info("UPDATE album itemid: %s - Name: %s" % (itemid, name)) # Update the checksum in emby table emby_db.updateReference(itemid, checksum) ##### OR ADD THE ALBUM ##### else: - log("ADD album itemid: %s - Name: %s" % (itemid, name), 1) + log.info("ADD album itemid: %s - Name: %s" % (itemid, name)) # 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) @@ -1964,7 +1966,7 @@ class Music(Items): albumid = emby_dbitem[3] except TypeError: update_item = False - log("songid: %s not found." % itemid, 2) + log.debug("songid: %s not found." % itemid) ##### The song details ##### checksum = API.getChecksum() @@ -2019,7 +2021,7 @@ class Music(Items): ##### UPDATE THE SONG ##### if update_item: - log("UPDATE song itemid: %s - Title: %s" % (itemid, title), 1) + log.info("UPDATE song itemid: %s - Title: %s" % (itemid, title)) # Update path query = "UPDATE path SET strPath = ? WHERE idPath = ?" @@ -2042,7 +2044,7 @@ class Music(Items): ##### OR ADD THE SONG ##### else: - log("ADD song itemid: %s - Title: %s" % (itemid, title), 1) + log.info("ADD song itemid: %s - Title: %s" % (itemid, title)) # Add path pathid = self.kodi_db.addPath(path) @@ -2055,27 +2057,27 @@ class Music(Items): # Verify if there's an album associated. album_name = item.get('Album') if album_name: - log("Creating virtual music album for song: %s." % itemid, 1) + log.info("Creating virtual music album for song: %s." % itemid) 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. - log("Song itemid: %s has no albumId associated." % itemid, 1) + log.error("Song itemid: %s has no albumId associated." % itemid) return False except TypeError: # No album found. Let's create it - log("Album database entry missing.", 1) + log.info("Album database entry missing.") 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] - log("Found albumid: %s" % albumid, 1) + log.info("Found albumid: %s" % albumid) except TypeError: # No album found, create a single's album - log("Failed to add album. Creating singles.", 1) + log.info("Failed to add album. Creating singles.") kodicursor.execute("select coalesce(max(idAlbum),0) from album") albumid = kodicursor.fetchone()[0] + 1 if self.kodiversion == 16: @@ -2257,7 +2259,7 @@ class Music(Items): try: kodiid = emby_dbitem[0] mediatype = emby_dbitem[4] - log("Update playstate for %s: %s" % (mediatype, item['Name']), 1) + log.info("Update playstate for %s: %s" % (mediatype, item['Name'])) except TypeError: return @@ -2296,7 +2298,7 @@ class Music(Items): try: kodiid = emby_dbitem[0] mediatype = emby_dbitem[4] - log("Removing %s kodiid: %s" % (mediatype, kodiid), 1) + log.info("Removing %s kodiid: %s" % (mediatype, kodiid)) except TypeError: return @@ -2364,7 +2366,7 @@ class Music(Items): # Remove artist self.removeArtist(kodiid) - log("Deleted %s: %s from kodi database" % (mediatype, itemid), 1) + log.info("Deleted %s: %s from kodi database" % (mediatype, itemid)) def removeSong(self, kodiId): diff --git a/resources/lib/kodidb_functions.py b/resources/lib/kodidb_functions.py index 86981e7b..9b7aca14 100644 --- a/resources/lib/kodidb_functions.py +++ b/resources/lib/kodidb_functions.py @@ -2,15 +2,19 @@ ################################################################################################## +import logging + import xbmc import api import artwork import clientinfo -from utils import Logging -################################################################################################## +################################################################################################# +log = logging.getLogger("EMBY."+__name__) + +################################################################################################# class Kodidb_Functions(): @@ -18,9 +22,6 @@ class Kodidb_Functions(): def __init__(self, cursor): - - global log - log = Logging(self.__class__.__name__).log self.cursor = cursor @@ -151,7 +152,7 @@ class Kodidb_Functions(): query = "INSERT INTO country(country_id, name) values(?, ?)" self.cursor.execute(query, (country_id, country)) - log("Add country to media, processing: %s" % country, 2) + log.debug("Add country to media, processing: %s" % country) finally: # Assign country to content query = ( @@ -185,7 +186,7 @@ class Kodidb_Functions(): query = "INSERT INTO country(idCountry, strCountry) values(?, ?)" self.cursor.execute(query, (idCountry, country)) - log("Add country to media, processing: %s" % country, 2) + log.debug("Add country to media, processing: %s" % country) finally: # Only movies have a country field @@ -230,7 +231,7 @@ class Kodidb_Functions(): query = "INSERT INTO actor(actor_id, name) values(?, ?)" self.cursor.execute(query, (actorid, name)) - log("Add people to media, processing: %s" % name, 2) + log.debug("Add people to media, processing: %s" % name) finally: # Link person to content @@ -300,7 +301,7 @@ class Kodidb_Functions(): query = "INSERT INTO actors(idActor, strActor) values(?, ?)" self.cursor.execute(query, (actorid, name)) - log("Add people to media, processing: %s" % name, 2) + log.debug("Add people to media, processing: %s" % name) finally: # Link person to content @@ -460,7 +461,7 @@ class Kodidb_Functions(): query = "INSERT INTO genre(genre_id, name) values(?, ?)" self.cursor.execute(query, (genre_id, genre)) - log("Add Genres to media, processing: %s" % genre, 2) + log.debug("Add Genres to media, processing: %s" % genre) finally: # Assign genre to item @@ -505,7 +506,7 @@ class Kodidb_Functions(): query = "INSERT INTO genre(idGenre, strGenre) values(?, ?)" self.cursor.execute(query, (idGenre, genre)) - log("Add Genres to media, processing: %s" % genre, 2) + log.debug("Add Genres to media, processing: %s" % genre) finally: # Assign genre to item @@ -564,7 +565,7 @@ class Kodidb_Functions(): query = "INSERT INTO studio(studio_id, name) values(?, ?)" self.cursor.execute(query, (studioid, studio)) - log("Add Studios to media, processing: %s" % studio, 2) + log.debug("Add Studios to media, processing: %s" % studio) finally: # Assign studio to item query = ( @@ -595,7 +596,7 @@ class Kodidb_Functions(): query = "INSERT INTO studio(idstudio, strstudio) values(?, ?)" self.cursor.execute(query, (studioid, studio)) - log("Add Studios to media, processing: %s" % studio, 2) + log.debug("Add Studios to media, processing: %s" % studio) finally: # Assign studio to item if "movie" in mediatype: @@ -726,7 +727,7 @@ class Kodidb_Functions(): self.cursor.execute(query, (kodiid, mediatype)) # Add tags - log("Adding Tags: %s" % tags, 2) + log.debug("Adding Tags: %s" % tags) for tag in tags: self.addTag(kodiid, tag, mediatype) @@ -748,7 +749,7 @@ class Kodidb_Functions(): except TypeError: # Create the tag, because it does not exist tag_id = self.createTag(tag) - log("Adding tag: %s" % tag, 2) + log.debug("Adding tag: %s" % tag) finally: # Assign tag to item @@ -777,7 +778,7 @@ class Kodidb_Functions(): except TypeError: # Create the tag tag_id = self.createTag(tag) - log("Adding tag: %s" % tag, 2) + log.debug("Adding tag: %s" % tag) finally: # Assign tag to item @@ -813,7 +814,7 @@ class Kodidb_Functions(): query = "INSERT INTO tag(tag_id, name) values(?, ?)" self.cursor.execute(query, (tag_id, name)) - log("Create tag_id: %s name: %s" % (tag_id, name), 2) + log.debug("Create tag_id: %s name: %s" % (tag_id, name)) else: # Kodi Helix query = ' '.join(( @@ -833,13 +834,13 @@ class Kodidb_Functions(): query = "INSERT INTO tag(idTag, strTag) values(?, ?)" self.cursor.execute(query, (tag_id, name)) - log("Create idTag: %s name: %s" % (tag_id, name), 2) + log.debug("Create idTag: %s name: %s" % (tag_id, name)) return tag_id def updateTag(self, oldtag, newtag, kodiid, mediatype): - log("Updating: %s with %s for %s: %s" % (oldtag, newtag, mediatype, kodiid), 2) + log.debug("Updating: %s with %s for %s: %s" % (oldtag, newtag, mediatype, kodiid)) if self.kodiversion in (15, 16, 17): # Kodi Isengard, Jarvis, Krypton @@ -856,7 +857,6 @@ class Kodidb_Functions(): except Exception as e: # The new tag we are going to apply already exists for this item # delete current tag instead - log("Exception: %s" % e, 1) query = ' '.join(( "DELETE FROM tag_link", @@ -880,7 +880,6 @@ class Kodidb_Functions(): except Exception as e: # The new tag we are going to apply already exists for this item # delete current tag instead - log("Exception: %s" % e, 1) query = ' '.join(( "DELETE FROM taglinks", @@ -941,7 +940,7 @@ class Kodidb_Functions(): def createBoxset(self, boxsetname): - log("Adding boxset: %s" % boxsetname, 2) + log.debug("Adding boxset: %s" % boxsetname) query = ' '.join(( "SELECT idSet", diff --git a/resources/lib/kodimonitor.py b/resources/lib/kodimonitor.py index 16baa487..66e0b661 100644 --- a/resources/lib/kodimonitor.py +++ b/resources/lib/kodimonitor.py @@ -3,6 +3,7 @@ ################################################################################################# import json +import logging import xbmc import xbmcgui @@ -11,7 +12,11 @@ import clientinfo import downloadutils import embydb_functions as embydb import playbackutils as pbutils -from utils import Logging, window, settings, kodiSQL +from utils import window, settings, kodiSQL + +################################################################################################# + +log = logging.getLogger("EMBY."+__name__) ################################################################################################# @@ -21,23 +26,20 @@ class KodiMonitor(xbmc.Monitor): def __init__(self): - global log - log = Logging(self.__class__.__name__).log - self.clientInfo = clientinfo.ClientInfo() self.addonName = self.clientInfo.getAddonName() self.doUtils = downloadutils.DownloadUtils() - log("Kodi monitor started.", 1) + log.info("Kodi monitor started.") def onScanStarted(self, library): - log("Kodi library scan %s running." % library, 2) + log.debug("Kodi library scan %s running." % library) if library == "video": window('emby_kodiScan', value="true") def onScanFinished(self, library): - log("Kodi library scan %s finished." % library, 2) + log.debug("Kodi library scan %s finished." % library) if library == "video": window('emby_kodiScan', clear=True) @@ -62,14 +64,14 @@ class KodiMonitor(xbmc.Monitor): currentLog = settings('logLevel') if window('emby_logLevel') != currentLog: # The log level changed, set new prop - log("New log level: %s" % currentLog, 1) + log.info("New log level: %s" % currentLog) window('emby_logLevel', value=currentLog) def onNotification(self, sender, method, data): doUtils = self.doUtils if method not in ("Playlist.OnAdd"): - log("Method: %s Data: %s" % (method, data), 1) + log.info("Method: %s Data: %s" % (method, data)) if data: data = json.loads(data,'utf-8') @@ -82,7 +84,7 @@ class KodiMonitor(xbmc.Monitor): kodiid = item['id'] item_type = item['type'] except (KeyError, TypeError): - log("Item is invalid for playstate update.", 1) + log.info("Item is invalid for playstate update.") else: if ((settings('useDirectPaths') == "1" and not item_type == "song") or (item_type == "song" and settings('enableMusic') == "true")): @@ -94,11 +96,11 @@ class KodiMonitor(xbmc.Monitor): try: itemid = emby_dbitem[0] except TypeError: - log("No kodiId returned.", 1) + log.info("No kodiId returned.") else: url = "{server}/emby/Users/{UserId}/Items/%s?format=json" % itemid result = doUtils.downloadUrl(url) - log("Item: %s" % result, 2) + log.debug("Item: %s" % result) playurl = None count = 0 @@ -130,7 +132,7 @@ class KodiMonitor(xbmc.Monitor): kodiid = item['id'] item_type = item['type'] except (KeyError, TypeError): - log("Item is invalid for playstate update.", 1) + log.info("Item is invalid for playstate update.") else: # Send notification to the server. embyconn = kodiSQL('emby') @@ -140,7 +142,7 @@ class KodiMonitor(xbmc.Monitor): try: itemid = emby_dbitem[0] except TypeError: - log("Could not find itemid in emby database.", 1) + log.info("Could not find itemid in emby database.") else: # Stop from manually marking as watched unwatched, with actual playback. if window('emby_skipWatched%s' % itemid) == "true": @@ -151,10 +153,10 @@ class KodiMonitor(xbmc.Monitor): url = "{server}/emby/Users/{UserId}/PlayedItems/%s?format=json" % itemid if playcount != 0: doUtils.downloadUrl(url, action_type="POST") - log("Mark as watched for itemid: %s" % itemid, 1) + log.info("Mark as watched for itemid: %s" % itemid) else: doUtils.downloadUrl(url, action_type="DELETE") - log("Mark as unwatched for itemid: %s" % itemid, 1) + log.info("Mark as unwatched for itemid: %s" % itemid) finally: embycursor.close() @@ -195,10 +197,15 @@ class KodiMonitor(xbmc.Monitor): finally: embycursor.close()''' + elif method == "System.OnSleep": + # Connection is going to sleep + log.info("Marking the server as offline. System.OnSleep activated.") + window('emby_online', value="sleep") elif method == "System.OnWake": # Allow network to wake up xbmc.sleep(10000) + window('emby_online', value="false") window('emby_onWake', value="true") diff --git a/resources/lib/librarysync.py b/resources/lib/librarysync.py index d5608a19..4c7ff088 100644 --- a/resources/lib/librarysync.py +++ b/resources/lib/librarysync.py @@ -2,6 +2,7 @@ ################################################################################################## +import logging import sqlite3 import threading from datetime import datetime, timedelta, time @@ -20,10 +21,13 @@ import kodidb_functions as kodidb import read_embyserver as embyserver import userclient import videonodes -from utils import Logging, window, settings, language as lang +from utils import window, settings, language as lang ################################################################################################## +log = logging.getLogger("EMBY."+__name__) + +################################################################################################## class LibrarySync(threading.Thread): @@ -43,9 +47,6 @@ class LibrarySync(threading.Thread): def __init__(self): - global log - log = Logging(self.__class__.__name__).log - self.__dict__ = self._shared_state self.monitor = xbmc.Monitor() @@ -65,7 +66,7 @@ class LibrarySync(threading.Thread): dialog = xbmcgui.DialogProgressBG() dialog.create("Emby for Kodi", title) - log("Show progress dialog: %s" % title, 2) + log.debug("Show progress dialog: %s" % title) return dialog @@ -85,7 +86,7 @@ class LibrarySync(threading.Thread): for plugin in result: if plugin['Name'] == "Emby.Kodi Sync Queue": - log("Found server plugin.", 2) + log.debug("Found server plugin.") completed = self.fastSync() break @@ -105,7 +106,7 @@ class LibrarySync(threading.Thread): lastSync = "2010-01-01T00:00:00Z" lastSyncTime = utils.convertDate(lastSync) - log("Last sync run: %s" % lastSyncTime, 1) + log.info("Last sync run: %s" % lastSyncTime) # get server RetentionDateTime result = self.doUtils("{server}/emby/Emby.Kodi.SyncQueue/GetServerDateTime?format=json") @@ -115,11 +116,11 @@ class LibrarySync(threading.Thread): retention_time = "2010-01-01T00:00:00Z" retention_time = utils.convertDate(retention_time) - log("RetentionDateTime: %s" % retention_time, 1) + log.info("RetentionDateTime: %s" % retention_time) # 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) + log.info("Fast sync server retention insufficient, fall back to full sync") return False params = {'LastUpdateDT': lastSync} @@ -136,11 +137,11 @@ class LibrarySync(threading.Thread): } except (KeyError, TypeError): - log("Failed to retrieve latest updates using fast sync.", 1) + log.error("Failed to retrieve latest updates using fast sync.") return False else: - log("Fast sync changes: %s" % result, 1) + log.info("Fast sync changes: %s" % result) for action in processlist: self.triage_items(action, processlist[action]) @@ -158,14 +159,14 @@ class LibrarySync(threading.Thread): except Exception as e: # If the server plugin is not installed or an error happened. - log("An exception occurred: %s" % e, 1) + log.error("An exception occurred: %s" % e) 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) + log.info("New sync time: client time -%s min: %s" % (overlap, lastSync)) 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) + log.info("New sync time: server time -%s min: %s" % (overlap, lastSync)) finally: settings('LastIncrementalSync', value=lastSync) @@ -185,20 +186,20 @@ class LibrarySync(threading.Thread): while kodidb_scan: - log("Kodi scan is running. Waiting...", 1) + log.info("Kodi scan is running. Waiting...") kodidb_scan = window('emby_kodiScan') == "true" if self.shouldStop(): - log("Commit unsuccessful. Sync terminated.", 1) + log.info("Commit unsuccessful. Sync terminated.") break if self.monitor.waitForAbort(1): # Abort was requested while waiting. We should exit - log("Commit unsuccessful.", 1) + log.info("Commit unsuccessful.") break else: connection.commit() - log("Commit successful.", 1) + log.info("Commit successful.") def fullSync(self, manualrun=False, repair=False): # Only run once when first setting up. Can be run manually. @@ -268,8 +269,8 @@ class LibrarySync(threading.Thread): self.dbCommit(kodiconn) embyconn.commit() elapsedTime = datetime.now() - startTime - log("SyncDatabase (finished %s in: %s)" - % (itemtype, str(elapsedTime).split('.')[0]), 1) + log.info("SyncDatabase (finished %s in: %s)" + % (itemtype, str(elapsedTime).split('.')[0])) else: # Close the Kodi cursor kodicursor.close() @@ -296,8 +297,8 @@ class LibrarySync(threading.Thread): musicconn.commit() embyconn.commit() elapsedTime = datetime.now() - startTime - log("SyncDatabase (finished music in: %s)" - % (str(elapsedTime).split('.')[0]), 1) + log.info("SyncDatabase (finished music in: %s)" + % (str(elapsedTime).split('.')[0])) musiccursor.close() if pDialog: @@ -363,7 +364,7 @@ class LibrarySync(threading.Thread): if view['type'] == "mixed": sorted_views.append(view['name']) sorted_views.append(view['name']) - log("Sorted views: %s" % sorted_views, 1) + log.info("Sorted views: %s" % sorted_views) # total nodes for window properties self.vnodes.clearProperties() @@ -423,7 +424,7 @@ class LibrarySync(threading.Thread): 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) + log.info("Couldn't find corresponding grouped view: %s" % sorted_views) # Failsafe try: @@ -439,7 +440,7 @@ class LibrarySync(threading.Thread): current_tagid = view[2] except TypeError: - log("Creating viewid: %s in Emby database." % folderid, 1) + log.info("Creating viewid: %s in Emby database." % folderid) tagid = kodi_db.createTag(foldername) # Create playlist for the video library if (foldername not in playlists and @@ -458,12 +459,12 @@ class LibrarySync(threading.Thread): emby_db.addView(folderid, foldername, viewtype, tagid) else: - log(' '.join(( + log.debug(' '.join(( "Found viewid: %s" % folderid, "viewname: %s" % current_viewname, "viewtype: %s" % current_viewtype, - "tagid: %s" % current_tagid)), 2) + "tagid: %s" % current_tagid))) # View is still valid try: @@ -474,7 +475,7 @@ class LibrarySync(threading.Thread): # View was modified, update with latest info if current_viewname != foldername: - log("viewid: %s new viewname: %s" % (folderid, foldername), 1) + log.info("viewid: %s new viewname: %s" % (folderid, foldername)) tagid = kodi_db.createTag(foldername) # Update view with new info @@ -542,7 +543,7 @@ class LibrarySync(threading.Thread): window('Emby.nodes.total', str(totalnodes)) # Remove any old referenced views - log("Removing views: %s" % current_views, 1) + log.info("Removing views: %s" % current_views) for view in current_views: emby_db.removeView(view) @@ -554,7 +555,7 @@ class LibrarySync(threading.Thread): views = emby_db.getView_byType('movies') views += emby_db.getView_byType('mixed') - log("Media folders: %s" % views, 1) + log.info("Media folders: %s" % views) ##### PROCESS MOVIES ##### for view in views: @@ -589,7 +590,7 @@ class LibrarySync(threading.Thread): count += 1 movies.add_update(embymovie, view['name'], view['id']) else: - log("Movies finished.", 2) + log.debug("Movies finished.") ##### PROCESS BOXSETS ##### @@ -616,7 +617,7 @@ class LibrarySync(threading.Thread): count += 1 movies.add_updateBoxset(boxset) else: - log("Boxsets finished.", 2) + log.debug("Boxsets finished.") return True @@ -627,7 +628,7 @@ class LibrarySync(threading.Thread): mvideos = itemtypes.MusicVideos(embycursor, kodicursor) views = emby_db.getView_byType('musicvideos') - log("Media folders: %s" % views, 1) + log.info("Media folders: %s" % views) for view in views: @@ -664,7 +665,7 @@ class LibrarySync(threading.Thread): count += 1 mvideos.add_update(embymvideo, viewName, viewId) else: - log("MusicVideos finished.", 2) + log.debug("MusicVideos finished.") return True @@ -676,7 +677,7 @@ class LibrarySync(threading.Thread): views = emby_db.getView_byType('tvshows') views += emby_db.getView_byType('mixed') - log("Media folders: %s" % views, 1) + log.info("Media folders: %s" % views) for view in views: @@ -722,7 +723,7 @@ class LibrarySync(threading.Thread): pdialog.update(percentage, message="%s - %s" % (title, episodetitle)) tvshows.add_updateEpisode(episode) else: - log("TVShows finished.", 2) + log.debug("TVShows finished.") return True @@ -763,7 +764,7 @@ class LibrarySync(threading.Thread): process[itemtype][1](embyitem) else: - log("%s finished." % itemtype, 2) + log.debug("%s finished." % itemtype) return True @@ -784,7 +785,7 @@ class LibrarySync(threading.Thread): itemids.append(item['ItemId']) items = itemids - log("Queue %s: %s" % (process, items), 1) + log.info("Queue %s: %s" % (process, items)) processlist[process].extend(items) def incrementalSync(self): @@ -806,11 +807,11 @@ class LibrarySync(threading.Thread): incSyncIndicator = int(settings('incSyncIndicator')) totalUpdates = len(self.addedItems) + len(self.updateItems) + len(self.userdataItems) + len(self.removeItems) - log("incSyncIndicator=" + str(incSyncIndicator) + " totalUpdates=" + str(totalUpdates), 1) if incSyncIndicator != -1 and totalUpdates > incSyncIndicator: # Only present dialog if we are going to process items pDialog = self.progressDialog('Incremental sync') + log.info("incSyncIndicator=" + str(incSyncIndicator) + " totalUpdates=" + str(totalUpdates)) process = { @@ -859,7 +860,7 @@ class LibrarySync(threading.Thread): if update_embydb: update_embydb = False - log("Updating emby database.", 1) + log.info("Updating emby database.") embyconn.commit() self.saveLastSync() @@ -868,7 +869,7 @@ class LibrarySync(threading.Thread): self.forceLibraryUpdate = False self.dbCommit(kodiconn) - log("Updating video library.", 1) + log.info("Updating video library.") window('emby_kodiScan', value="true") xbmc.executebuiltin('UpdateLibrary(video)') @@ -881,7 +882,7 @@ class LibrarySync(threading.Thread): def compareDBVersion(self, current, minimum): # It returns True is database is up to date. False otherwise. - log("current: %s minimum: %s" % (current, minimum), 1) + log.info("current: %s minimum: %s" % (current, minimum)) currMajor, currMinor, currPatch = current.split(".") minMajor, minMinor, minPatch = minimum.split(".") @@ -906,6 +907,7 @@ class LibrarySync(threading.Thread): "Library sync thread has exited! " "You should restart Kodi now. " "Please report this on the forum.")) + log.exception(e) raise def run_internal(self): @@ -914,7 +916,7 @@ class LibrarySync(threading.Thread): startupComplete = False - log("---===### Starting LibrarySync ###===---", 0) + log.warn("---===### Starting LibrarySync ###===---") while not self.monitor.abortRequested(): @@ -932,12 +934,12 @@ class LibrarySync(threading.Thread): uptoDate = self.compareDBVersion(currentVersion, minVersion) if not uptoDate: - log("Database version out of date: %s minimum version required: %s" - % (currentVersion, minVersion), 0) + log.warn("Database version out of date: %s minimum version required: %s" + % (currentVersion, minVersion)) resp = dialog.yesno(lang(29999), lang(33022)) if not resp: - log("Database version is out of date! USER IGNORED!", 0) + log.warn("Database version is out of date! USER IGNORED!") dialog.ok(lang(29999), lang(33023)) else: utils.reset() @@ -952,11 +954,11 @@ class LibrarySync(threading.Thread): videoDb = utils.getKodiVideoDBPath() if not xbmcvfs.exists(videoDb): # Database does not exists - log( + log.error( "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) + "to know which Kodi versions are supported.") dialog.ok( heading=lang(29999), @@ -964,13 +966,13 @@ class LibrarySync(threading.Thread): break # Run start up sync - log("Database version: %s" % settings('dbCreatedWithVersion'), 0) - log("SyncDatabase (started)", 1) + log.warn("Database version: %s" % settings('dbCreatedWithVersion')) + log.info("SyncDatabase (started)") startTime = datetime.now() librarySync = self.startSync() elapsedTime = datetime.now() - startTime - log("SyncDatabase (finished in: %s) %s" - % (str(elapsedTime).split('.')[0], librarySync), 1) + log.info("SyncDatabase (finished in: %s) %s" + % (str(elapsedTime).split('.')[0], librarySync)) # Only try the initial sync once per kodi session regardless # This will prevent an infinite loop in case something goes wrong. startupComplete = True @@ -984,32 +986,32 @@ class LibrarySync(threading.Thread): # Set in kodimonitor.py window('emby_onWake', clear=True) if window('emby_syncRunning') != "true": - log("SyncDatabase onWake (started)", 0) + log.info("SyncDatabase onWake (started)") librarySync = self.startSync() - log("SyncDatabase onWake (finished) %s" % librarySync, 0) + log.info("SyncDatabase onWake (finished) %s" % librarySync) if self.stop_thread: # Set in service.py - log("Service terminated thread.", 2) + log.debug("Service terminated thread.") break if self.monitor.waitForAbort(1): # Abort was requested while waiting. We should exit break - log("###===--- LibrarySync Stopped ---===###", 0) + log.warn("###===--- LibrarySync Stopped ---===###") def stopThread(self): self.stop_thread = True - log("Ending thread...", 2) + log.debug("Ending thread...") def suspendThread(self): self.suspend_thread = True - log("Pausing thread...", 0) + log.debug("Pausing thread...") def resumeThread(self): self.suspend_thread = False - log("Resuming thread...", 0) + log.debug("Resuming thread...") class ManualSync(LibrarySync): @@ -1032,7 +1034,7 @@ class ManualSync(LibrarySync): views = emby_db.getView_byType('movies') views += emby_db.getView_byType('mixed') - log("Media folders: %s" % views, 1) + log.info("Media folders: %s" % views) # Pull the list of movies and boxsets in Kodi try: @@ -1079,7 +1081,7 @@ class ManualSync(LibrarySync): # 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) + log.info("Movies to update for %s: %s" % (viewName, updatelist)) embymovies = self.emby.getFullItems(updatelist) total = len(updatelist) del updatelist[:] @@ -1121,7 +1123,7 @@ class ManualSync(LibrarySync): updatelist.append(itemid) embyboxsets.append(boxset) - log("Boxsets to update: %s" % updatelist, 1) + log.info("Boxsets to update: %s" % updatelist) total = len(updatelist) if pdialog: @@ -1145,13 +1147,13 @@ class ManualSync(LibrarySync): if kodimovie not in all_embymoviesIds: movies.remove(kodimovie) else: - log("Movies compare finished.", 1) + log.info("Movies compare finished.") for boxset in all_kodisets: if boxset not in all_embyboxsetsIds: movies.remove(boxset) else: - log("Boxsets compare finished.", 1) + log.info("Boxsets compare finished.") return True @@ -1162,7 +1164,7 @@ class ManualSync(LibrarySync): mvideos = itemtypes.MusicVideos(embycursor, kodicursor) views = emby_db.getView_byType('musicvideos') - log("Media folders: %s" % views, 1) + log.info("Media folders: %s" % views) # Pull the list of musicvideos in Kodi try: @@ -1202,7 +1204,7 @@ class ManualSync(LibrarySync): # 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) + log.info("MusicVideos to update for %s: %s" % (viewName, updatelist)) embymvideos = self.emby.getFullItems(updatelist) total = len(updatelist) del updatelist[:] @@ -1229,7 +1231,7 @@ class ManualSync(LibrarySync): if kodimvideo not in all_embymvideosIds: mvideos.remove(kodimvideo) else: - log("MusicVideos compare finished.", 1) + log.info("MusicVideos compare finished.") return True @@ -1241,7 +1243,7 @@ class ManualSync(LibrarySync): views = emby_db.getView_byType('tvshows') views += emby_db.getView_byType('mixed') - log("Media folders: %s" % views, 1) + log.info("Media folders: %s" % views) # Pull the list of tvshows and episodes in Kodi try: @@ -1288,7 +1290,7 @@ class ManualSync(LibrarySync): # 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) + log.info("TVShows to update for %s: %s" % (viewName, updatelist)) embytvshows = self.emby.getFullItems(updatelist) total = len(updatelist) del updatelist[:] @@ -1332,7 +1334,7 @@ class ManualSync(LibrarySync): # 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) + log.info("Episodes to update for %s: %s" % (viewName, updatelist)) embyepisodes = self.emby.getFullItems(updatelist) total = len(updatelist) del updatelist[:] @@ -1357,13 +1359,13 @@ class ManualSync(LibrarySync): if koditvshow not in all_embytvshowsIds: tvshows.remove(koditvshow) else: - log("TVShows compare finished.", 1) + log.info("TVShows compare finished.") for kodiepisode in all_kodiepisodes: if kodiepisode not in all_embyepisodesIds: tvshows.remove(kodiepisode) else: - log("Episodes compare finished.", 1) + log.info("Episodes compare finished.") return True @@ -1429,7 +1431,7 @@ class ManualSync(LibrarySync): 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" % (data_type, updatelist), 1) + log.info("%s to update: %s" % (data_type, updatelist)) embyitems = self.emby.getFullItems(updatelist) total = len(updatelist) del updatelist[:] @@ -1450,15 +1452,15 @@ class ManualSync(LibrarySync): if kodiartist not in all_embyartistsIds and all_kodiartists[kodiartist] is not None: music.remove(kodiartist) else: - log("Artist compare finished.", 1) + log.info("Artist compare finished.") for kodialbum in all_kodialbums: if kodialbum not in all_embyalbumsIds: music.remove(kodialbum) else: - log("Albums compare finished.", 1) + log.info("Albums compare finished.") for kodisong in all_kodisongs: if kodisong not in all_embysongsIds: music.remove(kodisong) else: - log("Songs compare finished.", 1) + log.info("Songs compare finished.") return True \ No newline at end of file diff --git a/resources/lib/loghandler.py b/resources/lib/loghandler.py new file mode 100644 index 00000000..b576e078 --- /dev/null +++ b/resources/lib/loghandler.py @@ -0,0 +1,73 @@ +# -*- coding: utf-8 -*- + +################################################################################################## + +import logging +import xbmc + +from utils import window + +################################################################################################## + + +def config(): + + logger = logging.getLogger('EMBY') + logger.addHandler(LogHandler()) + logger.setLevel(logging.DEBUG) + + +class LogHandler(logging.StreamHandler): + + def __init__(self): + + logging.StreamHandler.__init__(self) + self.setFormatter(MyFormatter()) + + def emit(self, record): + + if self._getLogLevel(record.levelno): + try: + xbmc.log(self.format(record)) + except UnicodeEncodeError: + xbmc.log(self.format(record).encode('utf-8')) + + def _getLogLevel(self, level): + + levels = { + logging.ERROR: 0, + logging.WARNING: 0, + logging.INFO: 1, + logging.DEBUG: 2 + } + try: + logLevel = int(window('emby_logLevel')) + except ValueError: + logLevel = 0 + + return logLevel >= levels[level] + + +class MyFormatter(logging.Formatter): + + def __init__(self, fmt="%(name)s -> %(message)s"): + + logging.Formatter.__init__(self, fmt) + + def format(self, record): + + # Save the original format configured by the user + # when the logger formatter was instantiated + format_orig = self._fmt + + # Replace the original format with one customized by logging level + if record.levelno in (logging.DEBUG, logging.ERROR): + self._fmt = '%(name)s -> %(levelname)s:: %(message)s' + + # Call the original formatter class to do the grunt work + result = logging.Formatter.format(self, record) + + # Restore the original format configured by the user + self._fmt = format_orig + + return result \ No newline at end of file diff --git a/resources/lib/musicutils.py b/resources/lib/musicutils.py index b89c12da..1f5bd9e9 100644 --- a/resources/lib/musicutils.py +++ b/resources/lib/musicutils.py @@ -2,6 +2,7 @@ ################################################################################################# +import logging import os import xbmc @@ -14,8 +15,11 @@ from mutagen import id3 import base64 import read_embyserver as embyserver -from utils import Logging, window -log = Logging('MusicTools').log +from utils import window + +################################################################################################# + +log = logging.getLogger("EMBY."+__name__) ################################################################################################# @@ -25,7 +29,7 @@ def getRealFileName(filename, isTemp=False): #get the filename path accessible by python if possible... if not xbmcvfs.exists(filename): - log("File does not exist! %s" % filename, 0) + log.warn("File does not exist! %s" % filename) return (False, "") #if we use os.path method on older python versions (sunch as some android builds), we need to pass arguments as string @@ -102,7 +106,7 @@ def getAdditionalSongTags(embyid, emby_rating, API, kodicursor, emby_db, enablei elif file_rating is None and not currentvalue: return (emby_rating, comment, False) - log("getAdditionalSongTags --> embyid: %s - emby_rating: %s - file_rating: %s - current rating in kodidb: %s" %(embyid, emby_rating, file_rating, currentvalue)) + log.info("getAdditionalSongTags --> embyid: %s - emby_rating: %s - file_rating: %s - current rating in kodidb: %s" %(embyid, emby_rating, file_rating, currentvalue)) updateFileRating = False updateEmbyRating = False @@ -181,7 +185,7 @@ def getSongTags(file): hasEmbeddedCover = False isTemp,filename = getRealFileName(file) - log( "getting song ID3 tags for " + filename) + log.info( "getting song ID3 tags for " + filename) try: ###### FLAC FILES ############# @@ -215,14 +219,14 @@ def getSongTags(file): #POPM rating is 0-255 and needs to be converted to 0-5 range if rating > 5: rating = (rating / 255) * 5 else: - log( "Not supported fileformat or unable to access file: %s" %(filename)) + log.info( "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 ? - log("Exception in getSongTags %s" % e,0) + log.error("Exception in getSongTags %s" % e) rating = None #remove tempfile if needed.... @@ -244,7 +248,7 @@ def updateRatingToFile(rating, file): xbmcvfs.copy(file, tempfile) tempfile = xbmc.translatePath(tempfile).decode("utf-8") - log( "setting song rating: %s for filename: %s - using tempfile: %s" %(rating,file,tempfile)) + log.info( "setting song rating: %s for filename: %s - using tempfile: %s" %(rating,file,tempfile)) if not tempfile: return @@ -261,7 +265,7 @@ def updateRatingToFile(rating, file): audio.add(id3.POPM(email="Windows Media Player 9 Series", rating=calcrating, count=1)) audio.save() else: - log( "Not supported fileformat: %s" %(tempfile)) + log.info( "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 @@ -272,14 +276,14 @@ def updateRatingToFile(rating, file): xbmcvfs.delete(file) xbmcvfs.copy(tempfile,file) else: - log( "Checksum mismatch for filename: %s - using tempfile: %s - not proceeding with file overwite!" %(rating,file,tempfile)) + log.info( "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 ? - log("Exception in updateRatingToFile %s" %e,0) + log.error("Exception in updateRatingToFile %s" % e) \ No newline at end of file diff --git a/resources/lib/playbackutils.py b/resources/lib/playbackutils.py index 4da87f27..f089195a 100644 --- a/resources/lib/playbackutils.py +++ b/resources/lib/playbackutils.py @@ -3,6 +3,7 @@ ################################################################################################# import json +import logging import sys import xbmc @@ -11,12 +12,16 @@ import xbmcplugin import api import artwork -import clientinfo import downloadutils import playutils as putils import playlist import read_embyserver as embyserver -from utils import Logging, window, settings, language as lang +import shutil +from utils import window, settings, language as lang + +################################################################################################# + +log = logging.getLogger("EMBY."+__name__) ################################################################################################# @@ -26,14 +31,9 @@ class PlaybackUtils(): def __init__(self, item): - global log - log = Logging(self.__class__.__name__).log - 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 = window('emby_currUser') @@ -49,7 +49,7 @@ class PlaybackUtils(): listitem = xbmcgui.ListItem() playutils = putils.PlayUtils(self.item) - log("Play called.", 1) + log.info("Play called.") playurl = playutils.getPlayUrl() if not playurl: return xbmcplugin.setResolvedUrl(int(sys.argv[1]), False, listitem) @@ -72,9 +72,9 @@ class PlaybackUtils(): introsPlaylist = False dummyPlaylist = False - log("Playlist start position: %s" % startPos, 2) - log("Playlist plugin position: %s" % currentPosition, 2) - log("Playlist size: %s" % sizePlaylist, 2) + log.debug("Playlist start position: %s" % startPos) + log.debug("Playlist plugin position: %s" % currentPosition) + log.debug("Playlist size: %s" % sizePlaylist) ############### RESUME POINT ################ @@ -86,11 +86,11 @@ class PlaybackUtils(): if not propertiesPlayback: window('emby_playbackProps', value="true") - log("Setting up properties in playlist.", 1) + log.info("Setting up properties in playlist.") if not homeScreen and not seektime and window('emby_customPlaylist') != "true": - log("Adding dummy file to playlist.", 2) + log.debug("Adding dummy file to playlist.") dummyPlaylist = True playlist.add(playurl, listitem, index=startPos) # Remove the original item from playlist @@ -114,14 +114,14 @@ class PlaybackUtils(): if not resp: # User selected to not play trailers getTrailers = False - log("Skip trailers.", 1) + log.info("Skip trailers.") 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) + log.info("Adding Intro: %s" % introPlayurl) # Set listitem and properties for intros pbutils = PlaybackUtils(intro) @@ -137,7 +137,7 @@ class PlaybackUtils(): if homeScreen and not seektime and not sizePlaylist: # Extend our current playlist with the actual item to play # only if there's no playlist first - log("Adding main item to playlist.", 1) + log.info("Adding main item to playlist.") self.pl.addtoPlaylist(dbid, self.item['Type'].lower()) # Ensure that additional parts are played after the main item @@ -154,7 +154,7 @@ class PlaybackUtils(): additionalListItem = xbmcgui.ListItem() additionalPlayurl = putils.PlayUtils(part).getPlayUrl() - log("Adding additional part: %s" % partcount, 1) + log.info("Adding additional part: %s" % partcount) # Set listitem and properties for each additional parts pbutils = PlaybackUtils(part) @@ -168,13 +168,13 @@ class PlaybackUtils(): 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) + log.info("Processed as a playlist. First item is skipped.") 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) + log.debug("Resetting properties playback flag.") window('emby_playbackProps', clear=True) #self.pl.verifyPlaylist() @@ -184,7 +184,7 @@ class PlaybackUtils(): if window('emby_%s.playmethod' % playurl) == "Transcode": # Filter ISO since Emby does not probe anymore if self.item.get('VideoType') == "Iso": - log("Skipping audio/subs prompt, ISO detected.", 1) + log.info("Skipping audio/subs prompt, ISO detected.") else: playurl = playutils.audioSubsPref(playurl, listitem) window('emby_%s.playmethod' % playurl, value="Transcode") @@ -195,18 +195,18 @@ class PlaybackUtils(): ############### PLAYBACK ################ if homeScreen and seektime and window('emby_customPlaylist') != "true": - log("Play as a widget item.", 1) + log.info("Play as a widget item.") 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) + log.info("Play playlist.") xbmc.Player().play(playlist, startpos=startPos) else: - log("Play as a regular item.", 1) + log.info("Play as a regular item.") xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, listitem) def setProperties(self, playurl, listitem): @@ -228,7 +228,7 @@ class PlaybackUtils(): # Append external subtitles to stream playmethod = window('%s.playmethod' % embyitem) # Only for direct stream - if playmethod in ("DirectStream"): + if playmethod in ("DirectStream") and settings('enableExternalSubs') == "true": # Direct play automatically appends external subtitles = self.externalSubs(playurl) listitem.setSubtitles(subtitles) diff --git a/resources/lib/player.py b/resources/lib/player.py index dfefa0ec..1fd19135 100644 --- a/resources/lib/player.py +++ b/resources/lib/player.py @@ -3,6 +3,7 @@ ################################################################################################# import json +import logging import xbmc import xbmcgui @@ -11,7 +12,11 @@ import clientinfo import downloadutils import kodidb_functions as kodidb import websocket_client as wsc -from utils import Logging, window, settings, language as lang +from utils import window, settings, language as lang + +################################################################################################# + +log = logging.getLogger("EMBY."+__name__) ################################################################################################# @@ -28,18 +33,14 @@ class Player(xbmc.Player): def __init__(self): - global log - log = Logging(self.__class__.__name__).log - 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() - log("Starting playback monitor.", 2) + log.debug("Starting playback monitor.") def GetPlayStats(self): @@ -63,7 +64,7 @@ class Player(xbmc.Player): except: pass if count == 5: # try 5 times - log("Cancelling playback report...", 1) + log.info("Cancelling playback report...") break else: count += 1 @@ -80,12 +81,12 @@ class Player(xbmc.Player): 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) + log.info("Could not find itemId, cancelling playback report...") break else: tryCount += 1 else: - log("ONPLAYBACK_STARTED: %s itemid: %s" % (currentFile, itemId), 0) + log.info("ONPLAYBACK_STARTED: %s itemid: %s" % (currentFile, itemId)) # Only proceed if an itemId was found. embyitem = "emby_%s" % currentFile @@ -98,7 +99,7 @@ class Player(xbmc.Player): 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) + log.info("Seeking to: %s" % customseek) self.xbmcplayer.seekTime(int(customseek)/10000000.0) window('emby_customPlaylist.seektime', clear=True) @@ -185,7 +186,7 @@ class Player(xbmc.Player): if mapping: # Set in playbackutils.py - log("Mapping for external subtitles index: %s" % mapping, 2) + log.debug("Mapping for external subtitles index: %s" % mapping) externalIndex = json.loads(mapping) if externalIndex.get(str(indexSubs)): @@ -203,7 +204,7 @@ class Player(xbmc.Player): # Post playback to server - log("Sending POST play started: %s." % postdata, 2) + log.debug("Sending POST play started: %s." % postdata) self.doUtils(url, postBody=postdata, action_type="POST") # Ensure we do have a runtime @@ -211,7 +212,7 @@ class Player(xbmc.Player): runtime = int(runtime) except ValueError: runtime = self.xbmcplayer.getTotalTime() - log("Runtime is missing, Kodi runtime: %s" % runtime, 1) + log.info("Runtime is missing, Kodi runtime: %s" % runtime) # Save data map for updates and position calls data = { @@ -228,7 +229,7 @@ class Player(xbmc.Player): } self.played_info[currentFile] = data - log("ADDING_FILE: %s" % self.played_info, 1) + log.info("ADDING_FILE: %s" % self.played_info) # log some playback stats '''if(itemType != None): @@ -247,7 +248,7 @@ class Player(xbmc.Player): def reportPlayback(self): - log("reportPlayback Called", 2) + log.debug("reportPlayback Called") # Get current file currentFile = self.currentFile @@ -345,7 +346,7 @@ class Player(xbmc.Player): if mapping: # Set in PlaybackUtils.py - log("Mapping for external subtitles index: %s" % mapping, 2) + log.debug("Mapping for external subtitles index: %s" % mapping) externalIndex = json.loads(mapping) if externalIndex.get(str(indexSubs)): @@ -365,13 +366,13 @@ class Player(xbmc.Player): # Report progress via websocketclient postdata = json.dumps(postdata) - log("Report: %s" % postdata, 2) + log.debug("Report: %s" % postdata) self.ws.sendProgressUpdate(postdata) def onPlayBackPaused(self): currentFile = self.currentFile - log("PLAYBACK_PAUSED: %s" % currentFile, 2) + log.debug("PLAYBACK_PAUSED: %s" % currentFile) if self.played_info.get(currentFile): self.played_info[currentFile]['paused'] = True @@ -381,7 +382,7 @@ class Player(xbmc.Player): def onPlayBackResumed(self): currentFile = self.currentFile - log("PLAYBACK_RESUMED: %s" % currentFile, 2) + log.debug("PLAYBACK_RESUMED: %s" % currentFile) if self.played_info.get(currentFile): self.played_info[currentFile]['paused'] = False @@ -391,7 +392,7 @@ class Player(xbmc.Player): def onPlayBackSeek(self, time, seekOffset): # Make position when seeking a bit more accurate currentFile = self.currentFile - log("PLAYBACK_SEEK: %s" % currentFile, 2) + log.debug("PLAYBACK_SEEK: %s" % currentFile) if self.played_info.get(currentFile): position = self.xbmcplayer.getTime() @@ -401,16 +402,16 @@ class Player(xbmc.Player): def onPlayBackStopped(self): # Will be called when user stops xbmc playing a file - log("ONPLAYBACK_STOPPED", 2) + log.debug("ONPLAYBACK_STOPPED") window('emby_customPlaylist', clear=True) window('emby_customPlaylist.seektime', clear=True) window('emby_playbackProps', clear=True) - log("Clear playlist properties.", 1) + log.info("Clear playlist properties.") self.stopAll() def onPlayBackEnded(self): # Will be called when xbmc stops playing a file - log("ONPLAYBACK_ENDED", 2) + log.debug("ONPLAYBACK_ENDED") window('emby_customPlaylist.seektime', clear=True) self.stopAll() @@ -419,15 +420,15 @@ class Player(xbmc.Player): if not self.played_info: return - log("Played_information: %s" % self.played_info, 1) + log.info("Played_information: %s" % self.played_info) # 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) + log.debug("Item path: %s" % item) + log.debug("Item data: %s" % data) runtime = data['runtime'] currentPosition = data['currentPosition'] @@ -448,8 +449,8 @@ class Player(xbmc.Player): percentComplete = 0 markPlayedAt = float(settings('markPlayed')) / 100 - log("Percent complete: %s Mark played at: %s" - % (percentComplete, markPlayedAt), 1) + log.info("Percent complete: %s Mark played at: %s" + % (percentComplete, markPlayedAt)) # Send the delete action to the server. offerDelete = False @@ -467,16 +468,16 @@ class Player(xbmc.Player): resp = xbmcgui.Dialog().yesno(lang(30091), lang(33015), autoclose=120000) if resp: url = "{server}/emby/Items/%s?format=json" % itemid - log("Deleting request: %s" % itemid, 1) + log.info("Deleting request: %s" % itemid) self.doUtils(url, action_type="DELETE") else: - log("User skipped deletion.", 1) + log.info("User skipped deletion.") self.stopPlayback(data) # Stop transcoding if playMethod == "Transcode": - log("Transcoding for %s terminated." % itemid, 1) + log.info("Transcoding for %s terminated." % itemid) deviceId = self.clientInfo.getDeviceId() url = "{server}/emby/Videos/ActiveEncodings?DeviceId=%s" % deviceId self.doUtils(url, action_type="DELETE") @@ -485,7 +486,7 @@ class Player(xbmc.Player): def stopPlayback(self, data): - log("stopPlayback called", 2) + log.debug("stopPlayback called") itemId = data['item_id'] currentPosition = data['currentPosition'] diff --git a/resources/lib/playlist.py b/resources/lib/playlist.py index 1f0819b6..6f40fa71 100644 --- a/resources/lib/playlist.py +++ b/resources/lib/playlist.py @@ -3,17 +3,21 @@ ################################################################################################# import json +import logging import xbmc import xbmcgui import xbmcplugin -import clientinfo import playutils import playbackutils import embydb_functions as embydb import read_embyserver as embyserver -from utils import Logging, window, settings, language as lang, kodiSQL +from utils import window, settings, language as lang, kodiSQL + +################################################################################################# + +log = logging.getLogger("EMBY."+__name__) ################################################################################################# @@ -23,12 +27,6 @@ class Playlist(): def __init__(self): - global log - log = Logging(self.__class__.__name__).log - - self.clientInfo = clientinfo.ClientInfo() - self.addonName = self.clientInfo.getAddonName() - self.userid = window('emby_currUser') self.server = window('emby_server%s' % self.userid) @@ -45,8 +43,8 @@ class Playlist(): playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO) playlist.clear() - log("---*** PLAY ALL ***---", 1) - log("Items: %s and start at: %s" % (itemids, startat), 1) + log.info("---*** PLAY ALL ***---") + log.info("Items: %s and start at: %s" % (itemids, startat)) started = False window('emby_customplaylist', value="true") @@ -62,14 +60,14 @@ class Playlist(): 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) + log.info("Item was not found in the database, manually adding item.") 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) + log.info("Adding %s to playlist." % itemid) if not started: started = True @@ -84,8 +82,8 @@ class Playlist(): embycursor = embyconn.cursor() emby_db = embydb.Embydb_Functions(embycursor) - log("---*** ADD TO PLAYLIST ***---", 1) - log("Items: %s" % itemids, 1) + log.info("---*** ADD TO PLAYLIST ***---") + log.info("Items: %s" % itemids) player = xbmc.Player() playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO) @@ -103,7 +101,7 @@ class Playlist(): # Add to playlist self.addtoPlaylist(dbid, mediatype) - log("Adding %s to playlist." % itemid, 1) + log.info("Adding %s to playlist." % itemid) self.verifyPlaylist() embycursor.close() @@ -126,17 +124,17 @@ class Playlist(): else: pl['params']['item'] = {'file': url} - log(xbmc.executeJSONRPC(json.dumps(pl)), 2) + log.debug(xbmc.executeJSONRPC(json.dumps(pl))) def addtoPlaylist_xbmc(self, playlist, item): playurl = playutils.PlayUtils(item).getPlayUrl() if not playurl: # Playurl failed - log("Failed to retrieve playurl.", 1) + log.info("Failed to retrieve playurl.") return - log("Playurl: %s" % playurl) + log.info("Playurl: %s" % playurl) listitem = xbmcgui.ListItem() playbackutils.PlaybackUtils(item).setProperties(playurl, listitem) @@ -160,7 +158,7 @@ class Playlist(): else: pl['params']['item'] = {'file': url} - log(xbmc.executeJSONRPC(json.dumps(pl)), 2) + log.debug(xbmc.executeJSONRPC(json.dumps(pl))) def verifyPlaylist(self): @@ -174,7 +172,7 @@ class Playlist(): 'playlistid': 1 } } - log(xbmc.executeJSONRPC(json.dumps(pl)), 2) + log.debug(xbmc.executeJSONRPC(json.dumps(pl))) def removefromPlaylist(self, position): @@ -189,4 +187,4 @@ class Playlist(): 'position': position } } - log(xbmc.executeJSONRPC(json.dumps(pl)), 2) \ No newline at end of file + log.debug(xbmc.executeJSONRPC(json.dumps(pl))) \ No newline at end of file diff --git a/resources/lib/playutils.py b/resources/lib/playutils.py index 0674be71..3375af55 100644 --- a/resources/lib/playutils.py +++ b/resources/lib/playutils.py @@ -2,6 +2,7 @@ ################################################################################################# +import logging import sys import xbmc @@ -9,7 +10,11 @@ import xbmcgui import xbmcvfs import clientinfo -from utils import Logging, window, settings, language as lang +from utils import window, settings, language as lang + +################################################################################################# + +log = logging.getLogger("EMBY."+__name__) ################################################################################################# @@ -19,12 +24,8 @@ class PlayUtils(): def __init__(self, item): - global log - log = Logging(self.__class__.__name__).log - self.item = item self.clientInfo = clientinfo.ClientInfo() - self.addonName = self.clientInfo.getAddonName() self.userid = window('emby_currUser') self.server = window('emby_server%s' % self.userid) @@ -37,19 +38,19 @@ class PlayUtils(): if (self.item.get('Type') in ("Recording", "TvChannel") and self.item.get('MediaSources') and self.item['MediaSources'][0]['Protocol'] == "Http"): # Play LiveTV or recordings - log("File protocol is http (livetv).", 1) + log.info("File protocol is http (livetv).") playurl = "%s/emby/Videos/%s/stream.ts?audioCodec=copy&videoCodec=copy" % (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 - log("File protocol is http.", 1) + log.info("File protocol is http.") playurl = self.httpPlay() window('emby_%s.playmethod' % playurl, value="DirectStream") elif self.isDirectPlay(): - log("File is direct playing.", 1) + log.info("File is direct playing.") playurl = self.directPlay() playurl = playurl.encode('utf-8') # Set playmethod property @@ -57,14 +58,14 @@ class PlayUtils(): elif self.isDirectStream(): - log("File is direct streaming.", 1) + log.info("File is direct streaming.") playurl = self.directStream() # Set playmethod property window('emby_%s.playmethod' % playurl, value="DirectStream") elif self.isTranscoding(): - log("File is transcoding.", 1) + log.info("File is transcoding.") playurl = self.transcoding() # Set playmethod property window('emby_%s.playmethod' % playurl, value="Transcode") @@ -89,7 +90,7 @@ class PlayUtils(): # Requirement: Filesystem, Accessible path if settings('playFromStream') == "true": # User forcing to play via HTTP - log("Can't direct play, play from HTTP enabled.", 1) + log.info("Can't direct play, play from HTTP enabled.") return False videotrack = self.item['MediaSources'][0]['Name'] @@ -109,23 +110,23 @@ class PlayUtils(): '2': 720, '3': 1080 } - log("Resolution is: %sP, transcode for resolution: %sP+" - % (resolution, res[transcodeH265]), 1) + log.info("Resolution is: %sP, transcode for resolution: %sP+" + % (resolution, res[transcodeH265])) if res[transcodeH265] <= resolution: return False canDirectPlay = self.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) + log.info("Can't direct play, server doesn't allow/support it.") return False location = self.item['LocationType'] if location == "FileSystem": # Verify the path if not self.fileExists(): - log("Unable to direct play.", 1) - log(self.directPlay(), 1) + log.info("Unable to direct play.") + log.info(self.directPlay()) xbmcgui.Dialog().ok( heading=lang(29999), line1=lang(33011), @@ -167,18 +168,18 @@ class PlayUtils(): # Convert path to direct play path = self.directPlay() - log("Verifying path: %s" % path, 1) + log.info("Verifying path: %s" % path) if xbmcvfs.exists(path): - log("Path exists.", 1) + log.info("Path exists.") return True elif ":" not in path: - log("Can't verify path, assumed linux. Still try to direct play.", 1) + log.info("Can't verify path, assumed linux. Still try to direct play.") return True else: - log("Failed to find file.", 1) + log.info("Failed to find file.") return False def isDirectStream(self): @@ -200,8 +201,8 @@ class PlayUtils(): '2': 720, '3': 1080 } - log("Resolution is: %sP, transcode for resolution: %sP+" - % (resolution, res[transcodeH265]), 1) + log.info("Resolution is: %sP, transcode for resolution: %sP+" + % (resolution, res[transcodeH265])) if res[transcodeH265] <= resolution: return False @@ -213,7 +214,7 @@ class PlayUtils(): # Verify the bitrate if not self.isNetworkSufficient(): - log("The network speed is insufficient to direct stream file.", 1) + log.info("The network speed is insufficient to direct stream file.") return False return True @@ -237,10 +238,10 @@ class PlayUtils(): try: sourceBitrate = int(self.item['MediaSources'][0]['Bitrate']) except (KeyError, TypeError): - log("Bitrate value is missing.", 1) + log.info("Bitrate value is missing.") else: - log("The add-on settings bitrate is: %s, the video bitrate required is: %s" - % (settings, sourceBitrate), 1) + log.info("The add-on settings bitrate is: %s, the video bitrate required is: %s" + % (settings, sourceBitrate)) if settings < sourceBitrate: return False diff --git a/resources/lib/read_embyserver.py b/resources/lib/read_embyserver.py index 15fe2d7a..f2d241a9 100644 --- a/resources/lib/read_embyserver.py +++ b/resources/lib/read_embyserver.py @@ -2,11 +2,16 @@ ################################################################################################# +import logging + import xbmc -import clientinfo import downloadutils -from utils import Logging, window, settings, kodiSQL +from utils import window, settings, kodiSQL + +################################################################################################# + +log = logging.getLogger("EMBY."+__name__) ################################################################################################# @@ -18,11 +23,6 @@ class Read_EmbyServer(): def __init__(self): - global log - log = Logging(self.__class__.__name__).log - - self.clientInfo = clientinfo.ClientInfo() - self.addonName = self.clientInfo.getAddonName() self.doUtils = downloadutils.DownloadUtils().downloadUrl self.userId = window('emby_currUser') @@ -211,7 +211,7 @@ class Read_EmbyServer(): items['TotalRecordCount'] = total except TypeError: # Failed to retrieve - log("%s:%s Failed to retrieve the server response." % (url, params), 2) + log.debug("%s:%s Failed to retrieve the server response." % (url, params)) else: index = 0 @@ -253,27 +253,27 @@ class Read_EmbyServer(): # Something happened to the connection if not throttled: throttled = True - log("Throttle activated.", 1) + log.info("Throttle activated.") if jump == highestjump: # We already tried with the highestjump, but it failed. Reset value. - log("Reset highest value.", 1) + log.info("Reset highest value.") highestjump = 0 # Lower the number by half if highestjump: throttled = False jump = highestjump - log("Throttle deactivated.", 1) + log.info("Throttle deactivated.") else: jump = int(jump/4) - log("Set jump limit to recover: %s" % jump, 2) + log.debug("Set jump limit to recover: %s" % jump) retry = 0 while window('emby_online') != "true": # Wait server to come back online if retry == 5: - log("Unable to reconnect to server. Abort process.", 1) + log.info("Unable to reconnect to server. Abort process.") return items retry += 1 @@ -301,7 +301,7 @@ class Read_EmbyServer(): increment = 10 jump += increment - log("Increase jump limit to: %s" % jump, 1) + log.info("Increase jump limit to: %s" % jump) return items def getViews(self, mediatype="", root=False, sortedlist=False): @@ -318,7 +318,7 @@ class Read_EmbyServer(): try: items = result['Items'] except TypeError: - log("Error retrieving views for type: %s" % mediatype, 2) + log.debug("Error retrieving views for type: %s" % mediatype) else: for item in items: @@ -462,7 +462,7 @@ class Read_EmbyServer(): items['TotalRecordCount'] = total except TypeError: # Failed to retrieve - log("%s:%s Failed to retrieve the server response." % (url, params), 2) + log.debug("%s:%s Failed to retrieve the server response." % (url, params)) else: index = 1 @@ -550,9 +550,9 @@ class Read_EmbyServer(): url = "{server}/emby/Users/{UserId}/FavoriteItems/%s?format=json" % itemid doUtils(url, action_type="DELETE") else: - log("Error processing user rating.", 1) + log.info("Error processing user rating.") - log("Update user rating to emby for itemid: %s | favourite: %s" % (itemid, favourite), 1) + log.info("Update user rating to emby for itemid: %s | favourite: %s" % (itemid, favourite)) def refreshItem(self, itemid): diff --git a/resources/lib/userclient.py b/resources/lib/userclient.py index d08e00ae..37282efb 100644 --- a/resources/lib/userclient.py +++ b/resources/lib/userclient.py @@ -3,6 +3,7 @@ ################################################################################################## import hashlib +import logging import threading import xbmc @@ -13,7 +14,11 @@ import xbmcvfs import artwork import clientinfo import downloadutils -from utils import Logging, window, settings, language as lang +from utils import window, settings, language as lang + +################################################################################################## + +log = logging.getLogger("EMBY."+__name__) ################################################################################################## @@ -23,7 +28,7 @@ class UserClient(threading.Thread): # Borg - multiple instances, shared state _shared_state = {} - stopClient = False + stop_thread = False auth = True retry = 0 @@ -39,13 +44,9 @@ class UserClient(threading.Thread): def __init__(self): - global log - log = Logging(self.__class__.__name__).log - self.__dict__ = self._shared_state self.addon = xbmcaddon.Addon() - self.addonName = clientinfo.ClientInfo().getAddonName() self.doUtils = downloadutils.DownloadUtils() threading.Thread.__init__(self) @@ -63,7 +64,7 @@ class UserClient(threading.Thread): username = settings('username') if not username: - log("No username saved.", 2) + log.debug("No username saved.") return "" return username @@ -88,17 +89,17 @@ class UserClient(threading.Thread): 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) + log.debug("Returning userId from WINDOW for username: %s UserId: %s" + % (username, w_userId)) return w_userId # Verify the settings elif s_userId: - log("Returning userId from SETTINGS for username: %s userId: %s" - % (username, s_userId), 2) + log.debug("Returning userId from SETTINGS for username: %s userId: %s" + % (username, s_userId)) return s_userId # No userId found else: - log("No userId saved for username: %s." % username, 1) + log.info("No userId saved for username: %s." % username) def getServer(self, prefix=True): @@ -117,7 +118,7 @@ class UserClient(threading.Thread): server = host + ":" + port if not host: - log("No server information saved.", 2) + log.debug("No server information saved.") return False # If https is true @@ -144,17 +145,17 @@ class UserClient(threading.Thread): 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) + log.debug("Returning accessToken from WINDOW for username: %s accessToken: %s" + % (username, w_token)) return w_token # Verify the settings elif s_token: - log("Returning accessToken from SETTINGS for username: %s accessToken: %s" - % (username, s_token), 2) + log.debug("Returning accessToken from SETTINGS for username: %s accessToken: %s" + % (username, s_token)) window('emby_accessToken%s' % username, value=s_token) return s_token else: - log("No token found.", 1) + log.info("No token found.") return "" def getSSLverify(self): @@ -210,7 +211,7 @@ class UserClient(threading.Thread): if result == False: # Access is restricted, set in downloadutils.py via exception - log("Access is restricted.", 1) + log.info("Access is restricted.") self.HasAccess = False elif window('emby_online') != "true": @@ -218,7 +219,7 @@ class UserClient(threading.Thread): pass elif window('emby_serverStatus') == "restricted": - log("Access is granted.", 1) + log.info("Access is granted.") self.HasAccess = True window('emby_serverStatus', clear=True) xbmcgui.Dialog().notification(lang(29999), lang(33007)) @@ -283,12 +284,12 @@ class UserClient(threading.Thread): # If there's no settings.xml if not hasSettings: - log("No settings.xml found.", 1) + log.info("No settings.xml found.") self.auth = False return # If no user information elif not server or not username: - log("Missing server information.", 1) + log.info("Missing server information.") self.auth = False return # If there's a token, load the user @@ -298,9 +299,9 @@ class UserClient(threading.Thread): 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) + log.info("Current user: %s" % self.currUser) + log.info("Current userId: %s" % self.currUserId) + log.debug("Current accessToken: %s" % self.currToken) return ##### AUTHENTICATE USER ##### @@ -320,7 +321,7 @@ class UserClient(threading.Thread): option=xbmcgui.ALPHANUM_HIDE_INPUT) # If password dialog is cancelled if not password: - log("No password entered.", 0) + log.warn("No password entered.") window('emby_serverStatus', value="Stop") self.auth = False return @@ -328,24 +329,24 @@ class UserClient(threading.Thread): else: # Manual login, user is hidden password = dialog.input( - heading="%s %s" % (lang(33008), username), + heading="%s %s" % (lang(33008), username.decode('utf-8')), option=xbmcgui.ALPHANUM_HIDE_INPUT) sha1 = hashlib.sha1(password) sha1 = sha1.hexdigest() # Authenticate username and password data = {'username': username, 'password': sha1} - log(data, 2) + log.debug(data) url = "%s/emby/Users/AuthenticateByName?format=json" % server result = self.doUtils.downloadUrl(url, postBody=data, action_type="POST", authenticate=False) try: - log("Auth response: %s" % result, 1) + log.info("Auth response: %s" % result) accessToken = result['AccessToken'] except (KeyError, TypeError): - log("Failed to retrieve the api key.", 1) + log.info("Failed to retrieve the api key.") accessToken = None if accessToken is not None: @@ -354,20 +355,20 @@ class UserClient(threading.Thread): "%s %s!" % (lang(33000), self.currUser.decode('utf-8'))) settings('accessToken', value=accessToken) settings('userId%s' % username, value=result['User']['Id']) - log("User Authenticated: %s" % accessToken, 1) + log.info("User Authenticated: %s" % accessToken) self.loadCurrUser(authenticated=True) window('emby_serverStatus', clear=True) self.retry = 0 else: - log("User authentication failed.", 1) + log.error("User authentication failed.") 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) + log.info("Too many retries. " + "You can retry by resetting attempts in the addon settings.") window('emby_serverStatus', value="Stop") dialog.ok(lang(33001), lang(33010)) @@ -376,13 +377,13 @@ class UserClient(threading.Thread): def resetClient(self): - log("Reset UserClient authentication.", 1) + log.info("Reset UserClient authentication.") if self.currToken is not None: # In case of 401, removed saved token settings('accessToken', value="") window('emby_accessToken%s' % self.getUserId(), clear=True) self.currToken = None - log("User token has been removed.", 1) + log.info("User token has been removed.") self.auth = True self.currUser = None @@ -390,7 +391,7 @@ class UserClient(threading.Thread): def run(self): monitor = xbmc.Monitor() - log("----===## Starting UserClient ##===----", 0) + log.warn("----===## Starting UserClient ##===----") while not monitor.abortRequested(): @@ -425,12 +426,12 @@ class UserClient(threading.Thread): # 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) + log.debug("Server found: %s" % server) + log.debug("Username found: %s" % username) self.auth = True - if self.stopClient == True: + if self.stop_thread == True: # If stopping the client didn't work break @@ -439,8 +440,8 @@ class UserClient(threading.Thread): break self.doUtils.stopSession() - log("##===---- UserClient Stopped ----===##", 0) + log.warn("##===---- UserClient Stopped ----===##") def stopClient(self): # When emby for kodi terminates - self.stopClient = True \ No newline at end of file + self.stop_thread = True \ No newline at end of file diff --git a/resources/lib/utils.py b/resources/lib/utils.py index 079c9514..db01fd35 100644 --- a/resources/lib/utils.py +++ b/resources/lib/utils.py @@ -5,6 +5,7 @@ import cProfile import inspect import json +import logging import pstats import sqlite3 import StringIO @@ -19,46 +20,13 @@ import xbmcaddon import xbmcgui import xbmcvfs +################################################################################################# + +log = logging.getLogger("EMBY."+__name__) + ################################################################################################# # Main methods -class Logging(object): - - - def __init__(self, title=""): - - self.title = title - - def _getLogLevel(self, level): - - try: - logLevel = int(window('emby_logLevel')) - except ValueError: - logLevel = 0 - - return logLevel >= level - - def _printMsg(self, title, msg): - - try: - xbmc.log("%s -> %s" % (title, msg)) - except UnicodeEncodeError: - xbmc.log("%s -> %s" % (title, msg.encode('utf-8'))) - - def log(self, msg, level=1): - - extra = "" - if level == -1: - # Error mode - extra = "::ERROR" - - if self._getLogLevel(level): - self._printMsg("EMBY %s%s" % (self.title, extra), msg) - -# Initiate class for utils.py document logging -log = Logging('Utils').log - - def window(property, value=None, clear=False, window_id=10000): # Get or set window property WINDOW = xbmcgui.Window(window_id) @@ -143,7 +111,7 @@ def querySQL(query, args=None, cursor=None, conntype=None): if cursor is None: if conntype is None: - log("New connection type is missing.", 1) + log.info("New connection type is missing.") return result else: manualconn = True @@ -153,7 +121,7 @@ def querySQL(query, args=None, cursor=None, conntype=None): attempts = 0 while attempts < 3: try: - log("Query: %s Args: %s" % (query, args), 2) + log.debug("Query: %s Args: %s" % (query, args)) if args is None: result = cursor.execute(query) else: @@ -161,22 +129,22 @@ def querySQL(query, args=None, cursor=None, conntype=None): break # Query successful, break out of while loop except sqlite3.OperationalError as e: if "database is locked" in e: - log("%s...Attempt: %s" % (e, attempts), 0) + log.warn("%s...Attempt: %s" % (e, attempts)) attempts += 1 xbmc.sleep(1000) else: - log("Error sqlite3: %s" % e, 0) + log.error(e) if manualconn: cursor.close() raise except sqlite3.Error as e: - log("Error sqlite3: %s" % e, 0) + log.error(e) if manualconn: cursor.close() raise else: failed = True - log("FAILED // Query: %s Args: %s" % (query, args), 1) + log.info("FAILED // Query: %s Args: %s" % (query, args)) if manualconn: if failed: @@ -185,7 +153,7 @@ def querySQL(query, args=None, cursor=None, conntype=None): connection.commit() cursor.close() - log(result, 2) + log.debug(result) return result ################################################################################################# @@ -219,7 +187,7 @@ def setScreensaver(value): } } result = xbmc.executeJSONRPC(json.dumps(query)) - log("Toggling screensaver: %s %s" % (value, result), 1) + log.info("Toggling screensaver: %s %s" % (value, result)) def convertDate(date): try: @@ -300,7 +268,7 @@ def profiling(sortby="cumulative"): s = StringIO.StringIO() ps = pstats.Stats(pr, stream=s).sort_stats(sortby) ps.print_stats() - log(s.getvalue(), 1) + log.info(s.getvalue()) return result @@ -321,7 +289,7 @@ def reset(): window('emby_shouldStop', value="true") count = 10 while window('emby_dbScan') == "true": - log("Sync is running, will retry: %s..." % count) + log.info("Sync is running, will retry: %s..." % count) count -= 1 if count == 0: dialog.ok(language(29999), language(33085)) @@ -335,7 +303,7 @@ def reset(): deleteNodes() # Wipe the kodi databases - log("Resetting the Kodi video database.", 0) + log.warn("Resetting the Kodi video database.") connection = kodiSQL('video') cursor = connection.cursor() cursor.execute('SELECT tbl_name FROM sqlite_master WHERE type="table"') @@ -348,7 +316,7 @@ def reset(): cursor.close() if settings('enableMusic') == "true": - log("Resetting the Kodi music database.", 0) + log.warn("Resetting the Kodi music database.") connection = kodiSQL('music') cursor = connection.cursor() cursor.execute('SELECT tbl_name FROM sqlite_master WHERE type="table"') @@ -361,7 +329,7 @@ def reset(): cursor.close() # Wipe the emby database - log("Resetting the Emby database.", 0) + log.warn("Resetting the Emby database.") connection = kodiSQL('emby') cursor = connection.cursor() cursor.execute('SELECT tbl_name FROM sqlite_master WHERE type="table"') @@ -378,7 +346,7 @@ def reset(): # Offer to wipe cached thumbnails resp = dialog.yesno(language(29999), language(33086)) if resp: - log("Resetting all cached artwork.", 0) + log.warn("Resetting all cached artwork.") # Remove all existing textures first path = xbmc.translatePath("special://thumbnails/").decode('utf-8') if xbmcvfs.exists(path): @@ -414,7 +382,7 @@ def reset(): addondir = xbmc.translatePath(addon.getAddonInfo('profile')).decode('utf-8') dataPath = "%ssettings.xml" % addondir xbmcvfs.delete(dataPath) - log("Deleting: settings.xml", 1) + log.info("Deleting: settings.xml") dialog.ok(heading=language(29999), line1=language(33088)) xbmc.executebuiltin('RestartApp') @@ -488,11 +456,11 @@ def passwordsXML(): for path in paths: if path.find('.//from').text == "smb://%s/" % credentials: paths.remove(path) - log("Successfully removed credentials for: %s" % credentials, 1) + log.info("Successfully removed credentials for: %s" % credentials) etree.ElementTree(root).write(xmlpath) break else: - log("Failed to find saved server: %s in passwords.xml" % credentials, 1) + log.info("Failed to find saved server: %s in passwords.xml" % credentials) settings('networkCreds', value="") xbmcgui.Dialog().notification( @@ -541,7 +509,7 @@ def passwordsXML(): # Add credentials settings('networkCreds', value="%s" % server) - log("Added server: %s to passwords.xml" % server, 1) + log.info("Added server: %s to passwords.xml" % server) # Prettify and write to file try: indent(root) @@ -569,7 +537,7 @@ def playlistXSP(mediatype, tagname, viewid, viewtype="", delete=False): # Create the playlist directory if not xbmcvfs.exists(path): - log("Creating directory: %s" % path, 1) + log.info("Creating directory: %s" % path) xbmcvfs.mkdirs(path) # Only add the playlist if it doesn't already exists @@ -577,7 +545,7 @@ def playlistXSP(mediatype, tagname, viewid, viewtype="", delete=False): if delete: xbmcvfs.delete(xsppath) - log("Successfully removed playlist: %s." % tagname, 1) + log.info("Successfully removed playlist: %s." % tagname) return @@ -585,11 +553,11 @@ def playlistXSP(mediatype, tagname, viewid, viewtype="", delete=False): itemtypes = { 'homevideos': "movies" } - log("Writing playlist file to: %s" % xsppath, 1) + log.info("Writing playlist file to: %s" % xsppath) try: f = xbmcvfs.File(xsppath, 'w') except: - log("Failed to create playlist: %s" % xsppath, 1) + log.info("Failed to create playlist: %s" % xsppath) return else: f.write( @@ -603,7 +571,7 @@ def playlistXSP(mediatype, tagname, viewid, viewtype="", delete=False): '' % (itemtypes.get(mediatype, mediatype), plname, tagname)) f.close() - log("Successfully added playlist: %s" % tagname, 1) + log.info("Successfully added playlist: %s" % tagname) def deletePlaylists(): @@ -625,10 +593,10 @@ def deleteNodes(): try: shutil.rmtree("%s%s" % (path, dir.decode('utf-8'))) except: - log("Failed to delete directory: %s" % dir.decode('utf-8'), 0) + log.warn("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: - log("Failed to file: %s" % file.decode('utf-8'), 0) \ No newline at end of file + log.warn("Failed to delete 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 bf1d20f4..6873487c 100644 --- a/resources/lib/videonodes.py +++ b/resources/lib/videonodes.py @@ -2,6 +2,7 @@ ################################################################################################# +import logging import shutil import xml.etree.ElementTree as etree @@ -9,9 +10,12 @@ import xbmc import xbmcaddon import xbmcvfs -import clientinfo import utils -from utils import Logging, window, language as lang +from utils import window, language as lang + +################################################################################################# + +log = logging.getLogger("EMBY."+__name__) ################################################################################################# @@ -21,12 +25,6 @@ class VideoNodes(object): def __init__(self): - global log - log = Logging(self.__class__.__name__).log - - clientInfo = clientinfo.ClientInfo() - self.addonName = clientInfo.getAddonName() - self.kodiversion = int(xbmc.getInfoLabel('System.BuildVersion')[:2]) @@ -79,7 +77,7 @@ class VideoNodes(object): for file in files: xbmcvfs.delete(nodepath + file) - log("Sucessfully removed videonode: %s." % tagname, 1) + log.info("Sucessfully removed videonode: %s." % tagname) return # Create index entry @@ -364,7 +362,7 @@ class VideoNodes(object): def clearProperties(self): - log("Clearing nodes properties.", 1) + log.info("Clearing nodes properties.") embyprops = window('Emby.nodes.total') propnames = [ diff --git a/resources/lib/websocket_client.py b/resources/lib/websocket_client.py index 87d1e012..db0f8c00 100644 --- a/resources/lib/websocket_client.py +++ b/resources/lib/websocket_client.py @@ -3,6 +3,7 @@ ################################################################################################# import json +import logging import threading import websocket @@ -14,9 +15,13 @@ import downloadutils import librarysync import playlist import userclient -from utils import Logging, window, settings, language as lang +from utils import window, settings, language as lang -################################################################################################# +################################################################################################## + +log = logging.getLogger("EMBY."+__name__) + +################################################################################################## class WebSocket_Client(threading.Thread): @@ -29,15 +34,11 @@ class WebSocket_Client(threading.Thread): def __init__(self): - global log - log = Logging(self.__class__.__name__).log - 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() @@ -46,7 +47,7 @@ class WebSocket_Client(threading.Thread): def sendProgressUpdate(self, data): - log("sendProgressUpdate", 2) + log.debug("sendProgressUpdate") try: messageData = { @@ -55,10 +56,10 @@ class WebSocket_Client(threading.Thread): } messageString = json.dumps(messageData) self.client.send(messageString) - log("Message data: %s" % messageString, 2) + log.debug("Message data: %s" % messageString) except Exception as e: - log("Exception: %s" % e, 1) + log.exception(e) def on_message(self, ws, message): @@ -69,7 +70,7 @@ class WebSocket_Client(threading.Thread): if messageType not in ('SessionEnded'): # Mute certain events - log("Message: %s" % message, 1) + log.info("Message: %s" % message) if messageType == "Play": # A remote control play command has been sent from the server. @@ -118,10 +119,10 @@ class WebSocket_Client(threading.Thread): seekto = data['SeekPositionTicks'] seektime = seekto / 10000000.0 action(seektime) - log("Seek to %s." % seektime, 1) + log.info("Seek to %s." % seektime) else: action() - log("Command: %s completed." % command, 1) + log.info("Command: %s completed." % command) window('emby_command', value="true") @@ -254,7 +255,7 @@ class WebSocket_Client(threading.Thread): self.librarySync.refresh_views = True def on_close(self, ws): - log("Closed.", 2) + log.debug("Closed.") def on_open(self, ws): self.doUtils.postCapabilities(self.deviceId) @@ -264,7 +265,7 @@ class WebSocket_Client(threading.Thread): # Server is offline pass else: - log("Error: %s" % error, 2) + log.debug("Error: %s" % error) def run(self): @@ -281,7 +282,7 @@ class WebSocket_Client(threading.Thread): server = server.replace('http', "ws") websocket_url = "%s?api_key=%s&deviceId=%s" % (server, token, self.deviceId) - log("websocket url: %s" % websocket_url, 1) + log.info("websocket url: %s" % websocket_url) self.client = websocket.WebSocketApp(websocket_url, on_message=self.on_message, @@ -289,7 +290,7 @@ class WebSocket_Client(threading.Thread): on_close=self.on_close) self.client.on_open = self.on_open - log("----===## Starting WebSocketClient ##===----", 0) + log.warn("----===## Starting WebSocketClient ##===----") while not self.monitor.abortRequested(): @@ -301,10 +302,10 @@ class WebSocket_Client(threading.Thread): # Abort was requested, exit break - log("##===---- WebSocketClient Stopped ----===##", 0) + log.warn("##===---- WebSocketClient Stopped ----===##") def stopClient(self): self.stopWebsocket = True self.client.close() - log("Stopping thread.", 1) \ No newline at end of file + log.info("Stopping thread.") \ No newline at end of file diff --git a/resources/settings.xml b/resources/settings.xml index 91006dbb..25e8b247 100644 --- a/resources/settings.xml +++ b/resources/settings.xml @@ -54,6 +54,7 @@ + diff --git a/service.py b/service.py index 9d79adfd..d70dd2b4 100644 --- a/service.py +++ b/service.py @@ -2,6 +2,7 @@ ################################################################################################# +import logging import os import sys import time @@ -30,7 +31,14 @@ import librarysync import player import videonodes import websocket_client as wsc -from utils import Logging, window, settings, language as lang +from utils import window, settings, language as lang + +################################################################################################# + +import loghandler + +loghandler.config() +log = logging.getLogger("EMBY.service") ################################################################################################# @@ -49,9 +57,6 @@ class Service(): def __init__(self): - global log - log = Logging(self.__class__.__name__).log - self.clientInfo = clientinfo.ClientInfo() self.addonName = self.clientInfo.getAddonName() logLevel = userclient.UserClient().getLogLevel() @@ -61,12 +66,12 @@ class Service(): window('emby_kodiProfile', value=xbmc.translatePath('special://profile')) # Initial logging - log("======== START %s ========" % self.addonName, 0) - log("Platform: %s" % (self.clientInfo.getPlatform()), 0) - log("KODI Version: %s" % xbmc.getInfoLabel('System.BuildVersion'), 0) - log("%s Version: %s" % (self.addonName, self.clientInfo.getVersion()), 0) - log("Using plugin paths: %s" % (settings('useDirectPaths') == "0"), 0) - log("Log Level: %s" % logLevel, 0) + log.warn("======== START %s ========" % self.addonName) + log.warn("Platform: %s" % (self.clientInfo.getPlatform())) + log.warn("KODI Version: %s" % xbmc.getInfoLabel('System.BuildVersion')) + log.warn("%s Version: %s" % (self.addonName, self.clientInfo.getVersion())) + log.warn("Using plugin paths: %s" % (settings('useDirectPaths') == "0")) + log.warn("Log Level: %s" % logLevel) # Reset window props for profile switch properties = [ @@ -108,8 +113,8 @@ class Service(): if window('emby_kodiProfile') != kodiProfile: # Profile change happened, terminate this thread and others - log("Kodi profile was: %s and changed to: %s. Terminating old Emby thread." - % (kodiProfile, window('emby_kodiProfile')), 1) + log.info("Kodi profile was: %s and changed to: %s. Terminating old Emby thread." + % (kodiProfile, window('emby_kodiProfile'))) break @@ -151,9 +156,8 @@ class Service(): kplayer.reportPlayback() lastProgressUpdate = datetime.today() - except Exception as e: - log("Exception in Playback Monitor Service: %s" % e, 1) - pass + except Exception: + log.exception("Exception in Playback Monitor Service") else: # Start up events self.warn_auth = True @@ -192,7 +196,7 @@ class Service(): if (user.currUser is None) and self.warn_auth: # Alert user is not authenticated and suppress future warning self.warn_auth = False - log("Not authenticated yet.", 1) + log.info("Not authenticated yet.") # User access is restricted. # Keep verifying until access is granted @@ -221,7 +225,7 @@ class Service(): # Server is offline. # Alert the user and suppress future warning if self.server_online: - log("Server is offline.", 1) + log.info("Server is offline.") window('emby_online', value="false") xbmcgui.Dialog().notification( @@ -231,7 +235,19 @@ class Service(): sound=False) self.server_online = False - + + elif window('emby_online') == "sleep": + # device going to sleep + if self.websocket_running: + ws.stopClient() + ws = wsc.WebSocket_Client() + self.websocket_running = False + + if self.library_running: + library.stopThread() + library = librarysync.LibrarySync() + self.library_running = False + else: # Server is online if not self.server_online: @@ -249,7 +265,7 @@ class Service(): sound=False) self.server_online = True - log("Server is online and ready.", 1) + log.info("Server is online and ready.") window('emby_online', value="true") # Start the userclient thread @@ -278,14 +294,14 @@ class Service(): if self.websocket_running: ws.stopClient() - log("======== STOP %s ========" % self.addonName, 0) + log.warn("======== STOP %s ========" % self.addonName) # Delay option delay = int(settings('startupDelay')) +log.warn("Delaying emby startup by: %s sec..." % delay) -xbmc.log("Delaying emby startup by: %s sec..." % delay) if delay and xbmc.Monitor().waitForAbort(delay): # Start the service - xbmc.log("Abort requested while waiting. Emby for kodi not started.") + log.warn("Abort requested while waiting. Emby for kodi not started.") else: Service().ServiceEntryPoint() \ No newline at end of file From ea5ac3748a4af5f08fb1c0737433061d8db215c6 Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Mon, 25 Jul 2016 03:08:45 -0500 Subject: [PATCH 077/103] Log exception before dialog Using logging module, it won't prevent the dialog from displaying. --- resources/lib/librarysync.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/resources/lib/librarysync.py b/resources/lib/librarysync.py index 4c7ff088..2c260dad 100644 --- a/resources/lib/librarysync.py +++ b/resources/lib/librarysync.py @@ -901,14 +901,13 @@ class LibrarySync(threading.Thread): self.run_internal() except Exception as e: window('emby_dbScan', clear=True) + log.exception(e) xbmcgui.Dialog().ok( heading=lang(29999), line1=( "Library sync thread has exited! " "You should restart Kodi now. " "Please report this on the forum.")) - log.exception(e) - raise def run_internal(self): From 541afbb989628b3a92bb1402901b04742c6b1786 Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Mon, 25 Jul 2016 17:36:43 -0500 Subject: [PATCH 078/103] 2.2.18 (#51) Fix logging error (#51) --- resources/lib/librarysync.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/lib/librarysync.py b/resources/lib/librarysync.py index 4c7ff088..46e5c4b1 100644 --- a/resources/lib/librarysync.py +++ b/resources/lib/librarysync.py @@ -417,8 +417,8 @@ class LibrarySync(threading.Thread): # 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 - log("Found corresponding view: %s %s" - % (grouped_view['Name'], grouped_view['Id']), 1) + log.info("Found corresponding view: %s %s" + % (grouped_view['Name'], grouped_view['Id'])) foldername = grouped_view['Name'] break else: From 3e23f274994fa04492f3b76aaa7fc6695199087e Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Mon, 25 Jul 2016 17:44:33 -0500 Subject: [PATCH 079/103] Version bump 2.2.18 --- addon.xml | 2 +- changelog.txt | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/addon.xml b/addon.xml index 746cc7b4..dfd90b01 100644 --- a/addon.xml +++ b/addon.xml @@ -1,7 +1,7 @@ diff --git a/changelog.txt b/changelog.txt index 70d0235c..f778ce1b 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,6 @@ +version 2.2.18 +- Fix logging + version 2.2.17 - Fix crash when device wakes up - Add option to disable external subs for direct stream - under add-on settings > playback From 48f20295aae917d80530830ce458f4c8cbe465b4 Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Mon, 25 Jul 2016 21:58:49 -0500 Subject: [PATCH 080/103] Catch keyerror If advancedsettings.xml modifies the webserver, it seems to cause errors in jsonrpc and will return invalid params. Bug in Kodi? --- resources/lib/artwork.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/lib/artwork.py b/resources/lib/artwork.py index 4b0124f7..a9ca780c 100644 --- a/resources/lib/artwork.py +++ b/resources/lib/artwork.py @@ -126,7 +126,7 @@ class Artwork(): result = json.loads(result) try: self.xbmc_port = result['result']['value'] - except TypeError: + except (TypeError, KeyError): pass web_user = { From dc15d4d832d140480fcfe32456835923a1792e5b Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Fri, 29 Jul 2016 04:12:30 -0500 Subject: [PATCH 081/103] 2.2.19 (#52) Fix typo in logging --- addon.xml | 2 +- changelog.txt | 3 +++ resources/lib/playutils.py | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/addon.xml b/addon.xml index dfd90b01..1dd2f9f5 100644 --- a/addon.xml +++ b/addon.xml @@ -1,7 +1,7 @@ diff --git a/changelog.txt b/changelog.txt index f778ce1b..f4f3314a 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,6 @@ +version 2.2.19 +- Fix transcode (logging error) + version 2.2.18 - Fix logging diff --git a/resources/lib/playutils.py b/resources/lib/playutils.py index 3375af55..c683f599 100644 --- a/resources/lib/playutils.py +++ b/resources/lib/playutils.py @@ -389,7 +389,7 @@ class PlayUtils(): 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) + log.info("Set up subtitles: %s %s" % (selectSubsIndex, url)) listitem.setSubtitles(url) else: # Burn subtitles From 95066b0f7753855fbb76f387385fe2b90d76ea96 Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Sat, 30 Jul 2016 16:17:35 -0500 Subject: [PATCH 082/103] Add German translation Fix escape \' displaying --- resources/language/Dutch/strings.xml | 30 +-- resources/language/German/strings.xml | 329 +++++++++++++++++++++++++ resources/language/Swedish/strings.xml | 2 +- resources/lib/downloadutils.py | 10 +- 4 files changed, 350 insertions(+), 21 deletions(-) create mode 100644 resources/language/German/strings.xml diff --git a/resources/language/Dutch/strings.xml b/resources/language/Dutch/strings.xml index a75eb29f..b2f13680 100644 --- a/resources/language/Dutch/strings.xml +++ b/resources/language/Dutch/strings.xml @@ -99,12 +99,12 @@ Binnenkort op TV BoxSets Trailers - Muziek video\'s - Foto\'s + Muziek video's + Foto's Onbekeken films Film Genres - Film Studio\'s + Film Studio's Film Acteurs Onbekeken afleveringen TV Genres @@ -130,7 +130,7 @@ Willekeurige Items Aanbevolen Items - Extra\'s + Extra's Synchroniseer Thema Muziek Synchroniseer Extra Fanart @@ -140,14 +140,14 @@ Activeer HTTPS Forceer Transcoderen van Codecs Activeer server verbindings melding bij het opstarten - Onlangs bekeken Thuis Video\'s - Onlangs toegevoegde Foto\'s - Favoriete Home Video\'s - Favoriete Foto\'s + Onlangs bekeken Thuis Video's + Onlangs toegevoegde Foto's + Favoriete Home Video's + Favoriete Foto's Favoriete Albums - Onlangs toegevoegde Muziek Video\'s - Niet afgekeken Muziek Video\'s - Onbekeken Muziek Video\'s + Onlangs toegevoegde Muziek Video's + Niet afgekeken Muziek Video's + Onbekeken Muziek Video's Actief @@ -160,7 +160,7 @@ Afleveringen Muziek - artiesten Muziek - albums - Muziek Video\'s + Muziek Video's Muziek - nummers Kanalen @@ -232,7 +232,7 @@ Trailers afspelen? Verzamelen films van: verzamelen boxsets - Verzamelen muziek-video\'s van: + Verzamelen muziek-video's van: Verzamelen TV-series van: Verzamelen: Gedetecteerd dat de database moet worden vernieuwd voor deze versie van Emby voor Kodi. Doorgaan? @@ -241,7 +241,7 @@ voltooid in: Vergelijken van films uit: Vergelijken van boxsets - Vergelijken van muziek-video\'s uit: + Vergelijken van muziek-video's uit: Vergelijken van TV-series uit: Vergelijken van afleveringen uit: Vergelijken: @@ -285,7 +285,7 @@ De taak is geslaagd De taak is mislukt Direct Stream - Afspeel methode voor uw thema\'s + Afspeel methode voor uw thema's Het instellingenbestand bestaat niet in TV-Tunes. Wijzig een instelling en voer de taak opnieuw uit. Weet u zeker dat u uw lokale Kodi database opnieuw wilt instellen? Wijzig/verwijder netwerkreferenties diff --git a/resources/language/German/strings.xml b/resources/language/German/strings.xml new file mode 100644 index 00000000..847aa1cf --- /dev/null +++ b/resources/language/German/strings.xml @@ -0,0 +1,329 @@ + + + + Emby für Kodi + Primäre Server Adresse + + Via HTTP abspielen statt SMB/NFS: + + Log Level: + + Gerätename + + Erweitert + Benutzername: + + Portnummer: + + Anzahl der zuletzt hinzugefügten Alben: + Anzahl der zuletzt hinzugefügten Filme: + Anzahl der zuletzt hinzugefügten Episoden: + Aktualisieren + Löschen + Benutzername/Passwort falsch + Benutzername nicht gefunden + Lösche + Lösche von Server + Sortiere nach + Kein Filter + Action + Abenteuer + Animation + Crime + Comedy + Dokumentar + Drama + Fantasy + Ausländisch + History + Horror + Musik + Musical + Mystery + Romanze + Science Fiction + Kurzfilm + Spannung + Thriller + Western + Genre Filter + Löschen von Dateien bestätigen? + + Als 'gesehen' markieren + Als 'ungesehen' markieren + Sortiere nach ... + Sortierreihenfolge absteigend + Sortierreihenfolge aufsteigend + + Fortsetzen + Fortsetzen bei + Am Anfang starten + Löschen von Medien nach dem Abspielen anbieten + + Für Episoden + + Für Filme + + Prozentanzeige für Fortsetzen + Episodennummer hinzufügen + Ladefortschritt anzeigen + Lade Inhalt + Lade Daten + Fertig + Warnung + + Fehler + Suche + Deaktiviere erweiterte Bilder (z.B. CoverArt) + + Metadaten + Artwork + Videoqualität + + Direkte Wiedergabe + + Transkodierung + Serversuche erfolgreich + Server gefunden + Addresse : + + Zuletzt hinzugefügte Serien + + Begonnene Serien + + Alles an Musik + Kanäle + + Zuletzt hinzugefügte Filme + + Zuletzt hinzugefügte Episoden + + Zuletzt hinzugefügte Alben + Begonnene Filme + + Begonnene Episoden + + Nächste Episoden + + Favorisierte Filme + + Favorisierte Serien + + Favorisierte Episoden + Häufig gespielte Alben + Anstehende Serien + Sammlungen + Trailer + Musikvideos + Fotos + Ungesehene Filme + + Filmgenres + Studios + Filmdarsteller + Ungesehene Episoden + Seriengenres + Fernsehsender + Seriendarsteller + Wiedergabelisten + Ansichten festlegen + Wähle Benutzer + + Verbindung zum Server fehlgeschlagen + Songs + Alben + Album-Interpreten + Interpreten + Musik-Genres + Zuletzt hinzugefügte + Begonnene + Anstehende + Benutzerdefinierte Ansichten + Statistiken senden + Zufällige Filme + Zufällige Episoden + Zufallseintrag + + Empfohlene Medien + + Extras + + Synchronisiere Themen-Musik + Synchronisiere Extra-Fanart + Synchronisiere Film-BoxSets + Lokale Kodi Datenbank zurücksetzen + + Aktiviere HTTPS + + Erzwinge Transkodierung + Unterdrücke Server-Verbindungsmeldungen beim Starten + + Kürzliche hinzugefügte Heimvideos + + Kürzlich hinzugefügte Fotos + + Favorisierte Heim Videos + + Favorisierte Fotos + + Favorisierte Alben + Kürzlich hinzugefügte Musikvideos + + Begonnene Musikvideos + + Ungesehene Musikvideos + + + Aktiviert + Zurücksetzen + Filme + BoxSets + Trailer + Serien + Staffeln + Episoden + Interpreten + Alben + Musikvideos + Musikstücke + Kanäle + + Emby Einstellungen + Zu Emby Favoriten hinzufügen + Entferne von Emby Favoriten + Setze eigenes Song-Rating + Emby Addon Einstellungen + Lösche Element vom Server + Dieses Element aktualisieren + Setze eigenes Song-Rating (0-5) + + Überprüfe Host SSL Zertifikat + Client SSL Zertifikat + Benutze alternative Adresse + Alternative Server Adresse + Benutze alternativen Gerätenamen + [COLOR yellow]Erneut versuchen[/COLOR] + Synchronisations Einstellungen + Zeige Synchronisationsfortschritt + Synchronisiere leere Serien + Aktiviere Musik Bibliothek + Direktes streamen der Musik Bibliothek + Wiedergabe Modus + Erzwinge Artwork caching + Limitiere Artwork cache threads (empfohlen für rpi) + Aktiviere Schnellstart (benötigt Server plugin) + Maximale zeitgleiche Abfrageanzahl der Elemente vom Server + Wiedergabe + Netzwerk Anmeldung + aktiviere Emby Kino Modus + Frage nach Trailerwiedergabe + Überspringe Emby Löschanfrage für das Kontextmenu + Rücksprung beim Fortsetzen (in Sekunden) + Erzwinge H265 Transkodierung + Musik Metadaten Einstellungen (nicht kompatibel mit direktem Musik stream) + Importiere Songrating direkt aus Datei + Konvertiere Songrating nach Emby rating + Erlaube Ratings in Musikdateien zu akualisieren + Ignoriere Bonusmaterial bei nächsten Episoden + Permanentes hinzufügen von Benutzern zu dieser Sitzung + Startverzögerung (in Sekunden) + Aktiviere Serverneustart Meldung + Aktiviere Benachrichtigung bei neuen Inhalten + Dauer der Anzeige für Video Bibliotheks PopUp (in Sekunden) + Dauer der Anzeige für Musik Bibliotheks PopUp (in Sekunden) + Server Nachrichten + Erstelle neue Geräte ID + Syncronisieren bei deakiviertem Bildschirmschoner + Erzwinge Hi10P Transkodierung + Deaktiviert + + Willkommen + Fehler bei Verbindung + Server kann nicht erreicht werden + Server ist online + Elemente zur Playlist hinzugefügt + Elemente in Playlist eingereiht + Server startet neu + Zugang erlaubt + Nutzerpasswort eingeben: + Falscher Benutzername oder Passwort. + Authentifizierung zu oft fehlgeschlagen + Direkte Wiedergabe nicht möglich + Direkte Wiedergabe 3 mal fehlgeschlagen. Aktiviere Wiedergabe über HTTP. + Wähle Audiostream + Wähle Untertitelstream + Lösche Datei von deinem Emby Server? + Trailer abspielen? + Erfasse Filme von: + Erfasse Boxsets + Erfasse Musikvideos von: + Erfasse Serien von: + Erfassung: + Für diese Version von 'Emby für Kodi' muss die Datenbank neu erstellt werden. Fortfahren? + 'Emby für Kodi' funktioniert womöglich nicht richtig, bis die Datenbank neu gesetzt wurde. + Beende Datenbank-Syncronisationsprozess. Die aktuelle Kodi Version wird nicht unterstützt. + abgeschlossen in: + Vergleiche Filme von: + Vergleiche Boxsets + Vergleiche Musikvideos von: + Vergleiche Serien von: + Vergleiche Episoden von: + Vergleiche: + Erstellung einer neuen Geräte ID fehlgeschlagen. Schau in die Logs für weitere Informationen. + Einer neue Geräte ID wurde erstellt. Kodi startet nun neu. + Mit dem folgendem Server fortfahren? + Achtung! Wenn du den 'Nativen Modus' auswählst, fehlen einige Emby Funktionalitäten, wie: Emby Kino Modus, Direct Stream/Transkodiereingenschaften und elterliche Zugangsplanung. + Addon (Standard) + Nativ (Direkte Pfade) + Netzwerk -Anmeldeinformationen hinzufügen, um Kodi Zugriff auf die Inhalte zu geben? Wichtig: Kodi muss neugestartet werden, um die Netzwerk -Anmeldeinformationen zu sehen. Die Daten können auch später hinzugefügt werden. + Deakiviere Emby Musikbibliothek? + Direct Stream für die Musikbibliothek aktivieren? Wählen Sie diese Option wenn auf die Bibliothek später außerhalb des eigenen Netzwerkes zugegriffen wird. + Datei(en) vom Emby-Server löschen? Die Datei(en) werden auch von der Festplatte gelöscht! + Der Caching-Prozess dauert etwas. Weitermachen? + Artwork Cache Syncronisation + Vorhandenen Artwork Cache zurücksetzen? + Aktualisiere Artwork Cache: + Warte auf Beendigung aller Threads: + Kodi kann Datei nicht finden: + Sie müssen Ihre Netzwerk -Anmeldeinformationen in den Addon-Einstellungen bestätigen oder Ihre Pfadersetzungen innerhalb des Emby-Servers korrekt setzen (Emby Dashboard -> Bibliothek). Synchronisation beenden? + Hinzugefügt: + Wenn Sie sich zu oft falsch anmelden, blockiert der Emby Server möglicherwiese den Account. Trotzdem weiter? + Live TV Kanäle (experimentell) + Live TV Aufnahmen (experimentell) + Einstellungen + Füge Benutzer zur Sitzung hinzu + Aktualisiere Emby Abspiellisten/Videoknoten + Manuelle Syncronisation + Repariere lokale Datenbank (erzwinge Aktualisierung des gesamten Inhalts) + Lokale Datenbank zurücksetzen + Gesamtes Artwork cachen + TV/Movie Music or Movie Themes mit Kodi synchronisieren + Hinzufügen/Entfernen von Benutzer zur Sitzung + Benutzer hinzufügen + Benutzer entfernen + Benutzer von Sitzung entfernen + Erfolg! + Von der Videositzung entfernt: + Zur Videositzung hinzugefügt: + Benutzer von Session entfernen nicht möglich. + Task erfolgreich ausgeführt + Task fehlgeschlagen + Direktes Streamen + Playbackmethode für Ihre Themes + Die Einstellungsdatei existiert nicht in TV Tunes. Ändern Sie eine Einstellung und starten den Task erneut. + Sind Sie sicher, dass Sie die lokale Kodi Datenbank zurücksetzen möchten? + Bearbeiten/Entfernen der Netzwerk -Anmeldeinformationen + Ändern + Entfernen + Entfernt: + Netzwerk-Nutzernamen eingeben + Netzwerk-Passwort eingeben + Netzwerk -Anmeldeinformationen hinzugefügt für: + Servername oder IP-Adresse, wie in der Emby Server Bibliothek angezeigt, eingeben. (Bsp Servername: \"\\\\SERVER-PC\\Pfad\\\" ist \"SERVER-PC\") + Servername oder IP-Adresse ändern + Servername oder IP-Adresse eingeben + Konnte die Datenbank nicht zurücksetzen. Nochmal versuchen. + Alle zwischengespeicherten Bilder entfernen? + Alle Emby Addon-Einstellungen zurücksetzen? + Zurücksetzen der Datenbank abgeschlossen, Kodi wird nun neustarten um die Änderungen anzuwenden. + diff --git a/resources/language/Swedish/strings.xml b/resources/language/Swedish/strings.xml index 7530f729..8c6c5f88 100644 --- a/resources/language/Swedish/strings.xml +++ b/resources/language/Swedish/strings.xml @@ -273,7 +273,7 @@ Kunde inte generera ett nytt enhetsID. Se i loggarna för mer information. Ett nytt enhetsID har genererats. Kodi kommer nu starta om. Fortsätt med följande server? - OBS! Om du väljer \'Native\'-läget så tappar du vissa funktioner i Emby, som; Emby bioläge, direktströmning/omkodning och schema för föräldralås. + OBS! Om du väljer 'Native'-läget så tappar du vissa funktioner i Emby, som; Emby bioläge, direktströmning/omkodning och schema för föräldralås. Tillägg (Standard) Native (Direkta Sökvägar) Lägg till nätverksuppgifter för att ge Kodi åtkomst till ditt innehåll? Viktigt: Kodi kommer behöva startas om för att se uppgifterna. Dom kan också läggas till vid ett senare tillfälle. diff --git a/resources/lib/downloadutils.py b/resources/lib/downloadutils.py index 47a5db59..a74b634f 100644 --- a/resources/lib/downloadutils.py +++ b/resources/lib/downloadutils.py @@ -294,11 +294,11 @@ class DownloadUtils(): except requests.exceptions.ConnectionError as e: # Make the addon aware of status if window('emby_online') != "false": - log.warn("Server unreachable at: %s" % url) + log.error("Server unreachable at: %s" % url) window('emby_online', value="false") except requests.exceptions.ConnectTimeout as e: - log.warn("Server timeout at: %s" % url) + log.error("Server timeout at: %s" % url) except requests.exceptions.HTTPError as e: @@ -325,7 +325,7 @@ class DownloadUtils(): elif status not in ("401", "Auth"): # Tell userclient token has been revoked. window('emby_serverStatus', value="401") - log.warn("HTTP Error: %s" % e) + log.error("HTTP Error: %s" % e) xbmcgui.Dialog().notification( heading="Error connecting", message="Unauthorized.", @@ -340,10 +340,10 @@ class DownloadUtils(): pass except requests.exceptions.SSLError as e: - log.warn("Invalid SSL certificate for: %s" % url) + log.error("Invalid SSL certificate for: %s" % url) except requests.exceptions.RequestException as e: - log.warn("Unknown error connecting to: %s" % url) + log.error("Unknown error connecting to: %s" % url) return default_link From 54db63c37860bb7648b6bff972e6bbd59b7f7047 Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Sat, 30 Jul 2016 19:19:07 -0500 Subject: [PATCH 083/103] Adjust logging for itemtypes not found --- resources/lib/itemtypes.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/resources/lib/itemtypes.py b/resources/lib/itemtypes.py index 7438c83f..048ac830 100644 --- a/resources/lib/itemtypes.py +++ b/resources/lib/itemtypes.py @@ -288,7 +288,7 @@ class Movies(Items): except TypeError: update_item = False - log.info("movieid: %s not found." % itemid) + log.debug("movieid: %s not found." % itemid) # movieid kodicursor.execute("select coalesce(max(idMovie),0) from movie") movieid = kodicursor.fetchone()[0] + 1 @@ -670,7 +670,7 @@ class MusicVideos(Items): except TypeError: update_item = False - log.info("mvideoid: %s not found." % itemid) + log.debug("mvideoid: %s not found." % itemid) # mvideoid kodicursor.execute("select coalesce(max(idMVideo),0) from musicvideo") mvideoid = kodicursor.fetchone()[0] + 1 @@ -1008,7 +1008,7 @@ class TVShows(Items): except TypeError: update_item = False - log.info("showid: %s not found." % itemid) + log.debug("showid: %s not found." % itemid) kodicursor.execute("select coalesce(max(idShow),0) from tvshow") showid = kodicursor.fetchone()[0] + 1 From 1ef24be65e90d1c9b7480de88c713e2a6691b7cf Mon Sep 17 00:00:00 2001 From: xnappo Date: Sun, 31 Jul 2016 09:07:38 -0500 Subject: [PATCH 084/103] Update README.md --- README.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index a9fdc034..88f085c0 100644 --- a/README.md +++ b/README.md @@ -7,15 +7,15 @@ The Emby addon for Kodi combines the best of Kodi - ultra smooth navigation, bea View this short [Youtube video](https://youtu.be/IaecDPcXI3I?t=119) to give you a better idea of the general process. -1. Install the Emby repository for Kodi, from the repo install the Emby addon. -2. Within a few seconds you should be prompted for your server-details (or auto discovered). If not, try to restart Kodi -3. Once you're succesfully authenticated to your Emby server, the initial sync will start. -4. The first sync of the Emby server to local Kodi database may take some time depending on your device and library size. -5. Once the full sync is done, you can browse your media in Kodi, syncs will be automatically done in the background. +1. Install the Emby for Kodi repository, from the repo install the Emby addon. +2. Within a few seconds you should be prompted for your server-details (or it may be auto discovered). If not, try to restart Kodi +3. Once you're succesfully authenticated with your Emby server, the initial sync will start. +4. The first sync of the Emby server to the local Kodi database may take some time depending on your device and library size. +5. Once the full sync is done, you can browse your media in Kodi, and syncs will be done automatically in the background. ### Our Wiki -If you need additional information on Emby for Kodi, check out our [wiki](https://github.com/MediaBrowser/plugin.video.emby/wiki). +If you need additional information for Emby for Kodi, check out our [wiki](https://github.com/MediaBrowser/plugin.video.emby/wiki). ### What does Emby for Kodi do? @@ -26,11 +26,11 @@ The Emby addon synchronizes your media on your Emby server to the native Kodi da - If you require help, post to our [Emby-Kodi forums](http://emby.media/community/index.php?/forum/99-kodi/) for faster replies. - To achieve direct play, you will need to ensure your Emby library paths point to network paths (e.g: "\\\\server\Media\Movies"). See the [Emby wiki](https://github.com/MediaBrowser/Wiki/wiki/Path%20Substitution) for additional information. - **The addon is not (and will not be) compatible with the MySQL database replacement in Kodi.** In fact, Emby takes over the point of having a MySQL database because it acts as a "man in the middle" for your entire media library. -- Emby for Kodi is currently not compatible with Kodi's Video Extras addon unless native playback mode is used. **Deactivate Video Extras if content start randomly playing.** +- Emby for Kodi is notcurrently compatible with Kodi's Video Extras addon unless native playback mode is used. **Deactivate Video Extras if content start randomly playing.** ### What is currently supported? -Emby for Kodi is constantly being worked on. The following features are currently provided: +Emby for Kodi is under constant development. The following features are currently provided: - Library types available: + Movies From 5744f30c66aaeb8d62a153155027034b1648ce9e Mon Sep 17 00:00:00 2001 From: xnappo Date: Sun, 31 Jul 2016 09:09:56 -0500 Subject: [PATCH 085/103] Update README.md typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 88f085c0..849b3b20 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ The Emby addon synchronizes your media on your Emby server to the native Kodi da - If you require help, post to our [Emby-Kodi forums](http://emby.media/community/index.php?/forum/99-kodi/) for faster replies. - To achieve direct play, you will need to ensure your Emby library paths point to network paths (e.g: "\\\\server\Media\Movies"). See the [Emby wiki](https://github.com/MediaBrowser/Wiki/wiki/Path%20Substitution) for additional information. - **The addon is not (and will not be) compatible with the MySQL database replacement in Kodi.** In fact, Emby takes over the point of having a MySQL database because it acts as a "man in the middle" for your entire media library. -- Emby for Kodi is notcurrently compatible with Kodi's Video Extras addon unless native playback mode is used. **Deactivate Video Extras if content start randomly playing.** +- Emby for Kodi is not currently compatible with Kodi's Video Extras addon unless native playback mode is used. **Deactivate Video Extras if content start randomly playing.** ### What is currently supported? From 133773d5b6f4d76711039d5e1ace17cce16172f4 Mon Sep 17 00:00:00 2001 From: xnappo Date: Sun, 31 Jul 2016 12:15:38 -0500 Subject: [PATCH 086/103] Krypton prep --- resources/lib/itemtypes.py | 39 +++++++++++++++++++++++++++++--------- resources/lib/utils.py | 2 +- 2 files changed, 31 insertions(+), 10 deletions(-) diff --git a/resources/lib/itemtypes.py b/resources/lib/itemtypes.py index 7438c83f..ad408b0f 100644 --- a/resources/lib/itemtypes.py +++ b/resources/lib/itemtypes.py @@ -2158,15 +2158,36 @@ class Music(Items): 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)) - + if self.kodiversion >= 17: + # Kodi Krypton + query = ( + ''' + INSERT OR REPLACE INTO song_artist(idArtist, idSong, idRole, iOrder, strArtist) + + VALUES (?, ?, ?, ?, ?) + ''' + ) + kodicursor.execute(query, (artistid, songid, 1, index, artist_name)) + + # May want to look into only doing this once? + query = ( + ''' + INSERT OR REPLACE INTO role(idRole, strRole) + + VALUES (?, ?) + ''' + ) + kodicursor.execute(query, (1, 'Composer')) + else: + 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']: diff --git a/resources/lib/utils.py b/resources/lib/utils.py index db01fd35..84c32624 100644 --- a/resources/lib/utils.py +++ b/resources/lib/utils.py @@ -79,7 +79,7 @@ def getKodiVideoDBPath(): "14": 90, # Helix "15": 93, # Isengard "16": 99, # Jarvis - "17": 104 # Krypton + "17": 106 # Krypton } dbPath = xbmc.translatePath( From 367440dc7f9665febe87a599aa7d67d3cd6582d0 Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Sun, 31 Jul 2016 15:40:03 -0500 Subject: [PATCH 087/103] Add French translation --- resources/language/French/strings.xml | 329 ++++++++++++++++++++++++++ 1 file changed, 329 insertions(+) create mode 100644 resources/language/French/strings.xml diff --git a/resources/language/French/strings.xml b/resources/language/French/strings.xml new file mode 100644 index 00000000..9481c755 --- /dev/null +++ b/resources/language/French/strings.xml @@ -0,0 +1,329 @@ + + + + Emby pour Kodi + Adresse principale du serveur + + Lire avec HTTP à la place de SMB + + Niveau de journalisation + + Nom de l'appareil + + Avancé + Nom d'utilisateur + + Numéro de port + + Nombre d'album de musique récents à afficher: + Nombre de films récents à afficher: + Nombre d'épisodes télévisés récents à afficher: + Actualiser + Supprimer + Nom d'utilisateur/Mot de passe incorrect + Nom d'utilisateur introuvable + Suppression + En attente du serveur pour la suppression + Trier par + Aucun + Action + Aventure + Animation + Crime + Comédie + Documentaire + Drame + Fantaisie + Étranger + Historique + Horreur + Musique + Musical + Mystère + Romance + Science Fiction + Court + Suspense + Thriller + Western + Filtre de Genre + Confirmer la suppression du fichier + + Marquer comme lu + Marquer comme non vu + Trier par + Ordre de Trie décroissant + Ordre de Trie croissant + + Reprendre + Reprendre à partir de + Lire depuis le début + Offrir la possibilité de supprimer après la lecture + + Pour Épisodes + + Pour Films + + Ajouter un pourcentage de reprise + Ajouter Numéro Épisode + Afficher la progression du chargement + Chargement du contenu + Récupération des données + Fait + Avertissement + + Erreur + Rechercher + Activer les images améliorées (eg Coverart) + + Métadonnées + Artwork + Qualité vidéo + + Lecture directe + + Transcodage + Détection du serveur Réussi + Serveur trouvé + Addresse: + + Séries TV Récemment Ajouté + + Séries TV En cours + + Toute la musique + Chaînes + + Films récemment ajoutés + + Épisodes récemment ajoutés + + Albums récemment ajoutés + Films en Cours + + Épisodes en Cours + + Prochain Épisodes + + Films Favoris + + Séries Favorites + + Épisodes Favoris + Albums fréquemment joués + TV à venir + Collections + Bandes-annonces + Vidéo Clips + Photos + Films Non vu + + Film Genres + Film Studios + Film Acteurs + Épisodes Non vu + TV Genres + TV Réseaux + TV Acteurs + Listes de lecture + Définir Vues + Sélectionner l'utilisateur + + Impossible de se connecter au serveur + Chansons + Albums + Artiste de l'album + Artistes + Music Genres + Derniers + En cours + Prochain + Vues de l'utilisateur + Rapport Metrics + Films aléatoire + Épisodes aléatoire + Objets aléatoire + + Élements recommandés + + Extras + + Sync Thème Musique + Sync Extra Fanart + Sync Saga Films + Réinitialiser la base de données locale de Kodi + + Activer HTTPS + + Forcer le transcodage Codecs + Activer le message de connexion au serveur pendant le démarrage + + Vidéos personnel récemment ajoutés + + Photos récemment ajoutés + + Vidéos personnelles favorites + + Photos favorites + + Albums favoris + Vidéo Clips récemment ajoutés + + Vidéo Clips en cours + + Vidéo Clips non vu + + + Actif + Effacer les Paramètres + Films + Collections + Bandes-annonces + Séries + Saisons + Épisodes + Artistes musicaux + Albums de musique + Vidéo Clips + Pistes Musicales + Chaînes + + Emby options + Ajouter aux favoris Emby + Supprimer des favoris Emby + Définir une note personnalisée de la chanson + Paramètres addon Emby + Supprimer un élément du serveur + Actualiser cet article + Définir une note personnalisée de la chanson (0-5) + + Vérifier certificat SSL + Certificat SSL client + Utiliser une adresse alternative + Adresse du serveur alternatif + Utiliser un nom alternatif de périphérique + [COLOR yellow]Relancez la connexion[/COLOR] + Options de synchronisation + Afficher l'avancement de la synchro + Sync Séries TV vides + Activer la bibliothèque musicale + Direct stream bibliothèque musicale + Mode de lecture + Force mise en cache d'artwork + Limiter artwork en cache (recommandé pour rpi) + Activer le démarrage rapide (nécessite le plugin du serveur) + Nombre maximum d'éléments à demander à partir du serveur en une seule fois + Lecture + Identifiants réseau + Activer le mode cinéma + Demander à jouer des bandes annonces + Ne pas demander de confirmation de suppression pour le menu contextuel (utiliser à vos risques et périls) + Aller en arrière à la reprise (en secondes) + Force transcode H265 + Options métadonnées musique (non compatibles avec direct stream) + Importation de la note de la chanson directement à partir des fichiers + Convertir la note du morceau pour Emby note + Autoriser les notes dans les fichiers des chansons à être mis à jour + Ignorer les spéciaux dans les épisodes suivants + Utilisateurs permanents à ajouter à la session + Délai de démarrage (en secondes) + Activer le message redémarrage du serveur + Activer une notification nouveau contenu + Durée de la fenêtre de la bibliothèque vidéo (en secondes) + Durée de la fenêtre de la bibliothèque musical (en secondes) + Messages du serveur + Générer un nouveau Id d'appareil + Sync si l'écran est désactivé + Force Transcode Hi10P + Désactivé + + Bienvenue + Erreur de connexion + Le Serveur est inaccessible + Le Serveur est en ligne + Éléments ajoutés à la liste de lecture + Éléments en file d'attente à la liste de lecture + Le serveur redémarre + L'accès est activé + Entrer le mot de passe pour l'utilisateur: + Utilisateur ou mot de passe invalide + Échec de l'authentification de trop nombreuses fois + Lecture directe impossible + Lecture directe a échoué 3 fois. Activer la lecture a partir de HTTP. + Choisissez le flux audio + Choisissez le flux de sous-titres + Supprimer le fichier de votre serveur Emby? + Lire bande-annonces ? + Récupération des films à partir de: + Récupération collections + Récupération des vidéo clips à partir de: + Récupération des séries Tv à partir de: + Récupération: + La base de données doit être créé pour cette version de Emby pour Kodi. Procéder ? + Emby pour Kodi peut ne pas fonctionner correctement jusqu'à ce que la base de données soit remise à zéro. + Annulation du processus de synchronisation de la base de données. La version actuelle de Kodi n’est pas prise en charge. + complété en: + Comparaison des films à partir de: + Comparaison des collections + Comparaison des vidéo clips à partir de: + Comparaison des séries tv à partir de: + Comparaison des épisodes à partir de: + Comparaison: + Impossible de générer un nouvel ID de périphérique. Consultez les fichiers journaux pour plus d'informations. + Un nouvel Id de périphérique a été généré. Kodi va redémarrer maintenant. + Procédez avec le serveur suivant ? + Avertissement ! Si vous choisissez le mode natif, certaines fonctionnalités de Emby seront manquantes, tels que: Emby mode cinéma, direct stream/transcode et planification d'accès parental. + Addon (Par défaut) + Natif (Chemins directs) + Ajouter les informations d'identification du réseau pour permettre l'accès à votre contenu Kodi ? Important: Kodi devra être redémarré pour voir les informations d'identification. Elles peuvent également être ajoutés à un moment ultérieur. + Désactiver bibliothèque musicale Emby? + Direct stream la bibliothèque musicale ? Sélectionnez cette option si la bibliothèque musicale sera accessible à distance. + Supprimer les fichiers de Emby Server ? Cela permettra également de supprimer les fichiers du disque ! + L'exécution du processus de mise en cache peut prendre un certain temps. + Artwork cache sync + Réinitialiser le cache des artwork existant ? + Mise à jour du cache des artwork : + Attendre que tous les sujets aient quitter : + Kodi ne peut pas localiser le fichier: + Vous devrez peut-être vérifier vos informations d'identification de réseau dans les paramètres add-on ou utiliser la substitution de chemin Emby pour formater votre chemin correctement (Emby tableau de bord> bibliothèque). Arrêter la synchronisation ? + Ajoutée: + Si vous ne parvenez pas à vous connecter de trop nombreuses fois, le serveur Emby peut verrouiller votre compte. Continuer quand même ? + Chaînes TV en direct (expérimental) + Enregistrements TV en direct (expérimental) + Paramètres + Ajouter l'utilisateur à la session + Actualiser listes de lecture/nœuds vidéo d'Emby + Effectuer une sync manuelle + Réparer la base de données locale (force la mise à jour de tout le contenu) + Effectuer une réinitialisation de la base de données locale + Mettre en cache tout les artwork + Sync Emby Thème Media pour Kodi + Ajouter/Supprimer l'utilisateur de la session + Ajouter un utilisateur + Supprimer un utilisateur + Supprimer l'utilisateur de la session + Réussi ! + Suppression de la session de visualisation: + Ajouté à la session de visualisation: + Impossible d'ajouter/supprimer l'utilisateur de la session. + La tâche a réussi + La tâche a échoué + Direct Stream + Méthode de lecture pour votre thèmes + Le fichier de paramètres n'existe pas dans Tunes TV. Modifier un paramètre et exécutez à nouveau la tâche. + Êtes-vous sûr de vouloir réinitialiser votre base de données locale Kodi ? + Modifier/Supprimer les informations d'identification du réseau + Modifier + Supprimer + Supprimé: + Entrer le nom d'utilisateur réseau + Entrer le mot de passe réseau + Ajouter des informations d'identification de réseau pour: + Entrez le nom du serveur ou l'adresse IP comme indiqué dans vos chemins de bibliothèque de Emby. Par exemple, le nom du serveur: \\\\SERVEUR-PC\\chemin\\ est \"SERVEUR-PC\" + Modifier le nom du serveur ou l'adresse IP + Entrer le nom du serveur ou l'adresse IP + Impossible de réinitialiser la base de données. Réessayer. + Supprimer toutes les artwork en cache? + Réinitialiser tous les réglages de l'addon Emby? + La réinitialisation de la base de données est terminée, Kodi va maintenant redémarrer pour appliquer les modifications. + From 46121656f88b23aa4002a8265729f7c19c98cadc Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Wed, 3 Aug 2016 18:35:55 -0500 Subject: [PATCH 088/103] Fix fail detection for tvshows Due to missing end slash - for xbmcfvs.exists() --- resources/lib/itemtypes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/lib/itemtypes.py b/resources/lib/itemtypes.py index c75a32ac..51abdf3c 100644 --- a/resources/lib/itemtypes.py +++ b/resources/lib/itemtypes.py @@ -1066,7 +1066,7 @@ class TVShows(Items): path = "%s/" % playurl toplevelpath = "%s/" % dirname(dirname(path)) - if not self.pathValidation(playurl): + if not self.pathValidation(path): return False window('emby_pathverified', value="true") From f4a80a5ea8810e18934df7be024dc0778f195a17 Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Fri, 5 Aug 2016 23:21:32 -0500 Subject: [PATCH 089/103] Keep track of fast sync If server plugin is not installed, automatically fall back to client time. --- resources/lib/librarysync.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/resources/lib/librarysync.py b/resources/lib/librarysync.py index a029a4b8..b90a671b 100644 --- a/resources/lib/librarysync.py +++ b/resources/lib/librarysync.py @@ -33,6 +33,8 @@ class LibrarySync(threading.Thread): _shared_state = {} + isFastSync = False + stop_thread = False suspend_thread = False @@ -87,6 +89,7 @@ class LibrarySync(threading.Thread): for plugin in result: if plugin['Name'] == "Emby.Kodi Sync Queue": log.debug("Found server plugin.") + self.isFastSync = True completed = self.fastSync() break @@ -152,14 +155,17 @@ class LibrarySync(threading.Thread): # 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) + if isFastSync: + result = self.doUtils("{server}/emby/Emby.Kodi.SyncQueue/GetServerDateTime?format=json") + server_time = result['ServerDateTime'] + server_time = utils.convertDate(server_time) + else: + raise Exception("Fast sync server plugin is not enabled.") except Exception as e: # If the server plugin is not installed or an error happened. - log.error("An exception occurred: %s" % e) + log.debug("An exception occurred: %s" % e) time_now = datetime.utcnow()-timedelta(minutes=overlap) lastSync = time_now.strftime('%Y-%m-%dT%H:%M:%SZ') log.info("New sync time: client time -%s min: %s" % (overlap, lastSync)) From 01800c0c3bcb129a15eff9e2852e4a8016a7b4d3 Mon Sep 17 00:00:00 2001 From: marcelveldt Date: Sat, 6 Aug 2016 15:43:04 +0200 Subject: [PATCH 090/103] fix for krypton compatability --- resources/lib/videonodes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/lib/videonodes.py b/resources/lib/videonodes.py index 6873487c..a2d21cf2 100644 --- a/resources/lib/videonodes.py +++ b/resources/lib/videonodes.py @@ -209,7 +209,7 @@ class VideoNodes(object): if mediatype == "photos": windowpath = "ActivateWindow(Pictures,%s,return)" % path else: - windowpath = "ActivateWindow(Video,%s,return)" % path + windowpath = "ActivateWindow(Videos,%s,return)" % path if nodetype == "all": From d582888ffbb625a1307b0100232d1dbd08b71fe2 Mon Sep 17 00:00:00 2001 From: im85288 Date: Sat, 6 Aug 2016 16:20:15 +0100 Subject: [PATCH 091/103] update video db version for krypton --- resources/lib/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/lib/utils.py b/resources/lib/utils.py index 84c32624..12aba200 100644 --- a/resources/lib/utils.py +++ b/resources/lib/utils.py @@ -79,7 +79,7 @@ def getKodiVideoDBPath(): "14": 90, # Helix "15": 93, # Isengard "16": 99, # Jarvis - "17": 106 # Krypton + "17": 107 # Krypton } dbPath = xbmc.translatePath( From 3a859279eceffd0bec32001dcfc19ae8e38e7fb8 Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Sun, 7 Aug 2016 03:40:18 -0500 Subject: [PATCH 092/103] Fix missing logging --- resources/lib/loghandler.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/lib/loghandler.py b/resources/lib/loghandler.py index b576e078..9ae91f89 100644 --- a/resources/lib/loghandler.py +++ b/resources/lib/loghandler.py @@ -28,9 +28,9 @@ class LogHandler(logging.StreamHandler): if self._getLogLevel(record.levelno): try: - xbmc.log(self.format(record)) + xbmc.log(self.format(record), level=xbmc.LOGNOTICE) except UnicodeEncodeError: - xbmc.log(self.format(record).encode('utf-8')) + xbmc.log(self.format(record).encode('utf-8'), level=xbmc.LOGNOTICE) def _getLogLevel(self, level): From 51400fd8282441c3fe7f9de0d62588900c0078db Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Sun, 7 Aug 2016 19:57:11 -0500 Subject: [PATCH 093/103] Add JSONRPC class --- resources/lib/utils.py | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/resources/lib/utils.py b/resources/lib/utils.py index 12aba200..18a74b8e 100644 --- a/resources/lib/utils.py +++ b/resources/lib/utils.py @@ -51,6 +51,36 @@ def language(string_id): # Central string retrieval - unicode return xbmcaddon.Addon(id='plugin.video.emby').getLocalizedString(string_id) +class JSONRPC(object): + + id_ = 1 + jsonrpc = "2.0" + + def __init__(self, method, **kwargs): + + self.method = method + + for arg in kwargs: # id_(int), jsonrpc(str) + self.arg = arg + + def _query(self): + + query = { + + 'jsonrpc': self.jsonrpc, + 'id': self.id_, + 'method': self.method, + } + if self.params is not None: + query['params'] = self.params + + return json.dumps(query) + + def execute(self, params=None): + + self.params = params + return json.loads(xbmc.executeJSONRPC(self._query())) + ################################################################################################# # Database related methods From fd395083bdf7c1fcf513a7308dc300f4592e2ae5 Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Sun, 7 Aug 2016 20:37:35 -0500 Subject: [PATCH 094/103] Fix navigation not waking up screen --- resources/lib/websocket_client.py | 36 +++++++++++++++---------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/resources/lib/websocket_client.py b/resources/lib/websocket_client.py index db0f8c00..bc158338 100644 --- a/resources/lib/websocket_client.py +++ b/resources/lib/websocket_client.py @@ -15,7 +15,7 @@ import downloadutils import librarysync import playlist import userclient -from utils import window, settings, language as lang +from utils import window, settings, language as lang, JSONRPC ################################################################################################## @@ -200,19 +200,24 @@ class WebSocket_Client(threading.Thread): elif command == "SendString": - string = arguments['String'] - text = { - - 'jsonrpc': "2.0", - 'id': 0, - 'method': "Input.SendText", - 'params': { - - 'text': "%s" % string, - 'done': False - } + params = { + 'text': arguments['String'], + 'done': False } - result = xbmc.executeJSONRPC(json.dumps(text)) + result = JSONRPC("Input.SendText").execute(params) + + elif command in ("MoveUp", "MoveDown", "MoveRight", "MoveLeft"): + # Commands that should wake up display + actions = { + 'MoveUp': "Input.Up", + 'MoveDown': "Input.Down", + 'MoveRight': "Input.Right", + 'MoveLeft': "Input.Left" + } + result = JSONRPC(actions[command]).execute() + + elif command == "GoHome": + result = JSONRPC("GUI.ActivateWindow").execute({"window":"home"}) else: builtin = { @@ -220,13 +225,8 @@ class WebSocket_Client(threading.Thread): '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', From 00fd67cab319c262a3af98f3fee92206555a37cb Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Thu, 18 Aug 2016 01:41:18 -0500 Subject: [PATCH 095/103] Fix typo --- resources/lib/read_embyserver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/lib/read_embyserver.py b/resources/lib/read_embyserver.py index f2d241a9..091e1730 100644 --- a/resources/lib/read_embyserver.py +++ b/resources/lib/read_embyserver.py @@ -123,7 +123,7 @@ class Read_EmbyServer(): return [viewName, viewId, mediatype] def getFilteredSection(self, parentid, itemtype=None, sortby="SortName", recursive=True, - limit=None, sortorder="Ascending", filter=""): + limit=None, sortorder="Ascending", filter_type=""): params = { 'ParentId': parentid, From 8cd4cb903e1f3ab3bfbab02f7a23fcd3f90c8aee Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Sun, 21 Aug 2016 16:28:26 -0500 Subject: [PATCH 096/103] Add logging Prevent error in event profiling modules are not part of the standard python library --- resources/lib/utils.py | 7 ++++--- service.py | 1 + 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/resources/lib/utils.py b/resources/lib/utils.py index 18a74b8e..9d38e710 100644 --- a/resources/lib/utils.py +++ b/resources/lib/utils.py @@ -2,11 +2,10 @@ ################################################################################################# -import cProfile + import inspect import json import logging -import pstats import sqlite3 import StringIO import os @@ -288,7 +287,9 @@ def profiling(sortby="cumulative"): # Will print results to Kodi log def decorator(func): def wrapper(*args, **kwargs): - + import cProfile + import pstats + pr = cProfile.Profile() pr.enable() diff --git a/service.py b/service.py index d70dd2b4..dbdfa414 100644 --- a/service.py +++ b/service.py @@ -67,6 +67,7 @@ class Service(): # Initial logging log.warn("======== START %s ========" % self.addonName) + log.warn("Python Version: %s", sys.version) log.warn("Platform: %s" % (self.clientInfo.getPlatform())) log.warn("KODI Version: %s" % xbmc.getInfoLabel('System.BuildVersion')) log.warn("%s Version: %s" % (self.addonName, self.clientInfo.getVersion())) From 8b9b0821c0ad2c1a67b997c6f2edd9a92febce67 Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Sun, 21 Aug 2016 21:51:23 -0500 Subject: [PATCH 097/103] Pylint (testing) --- resources/lib/artwork.py | 12 +++---- resources/lib/image_cache_thread.py | 55 +++++++++++++++-------------- resources/lib/loghandler.py | 19 +++++----- 3 files changed, 44 insertions(+), 42 deletions(-) diff --git a/resources/lib/artwork.py b/resources/lib/artwork.py index a9ca780c..28ace1fa 100644 --- a/resources/lib/artwork.py +++ b/resources/lib/artwork.py @@ -254,7 +254,7 @@ class Artwork(): while len(self.imageCacheThreads): for thread in self.imageCacheThreads: - if thread.isFinished: + if thread.is_finished: self.imageCacheThreads.remove(thread) pdialog.update(100, "%s %s" % (lang(33046), len(self.imageCacheThreads))) log.info("Waiting for all threads to exit: %s" % len(self.imageCacheThreads)) @@ -267,15 +267,15 @@ class Artwork(): while True: # removed finished for thread in self.imageCacheThreads: - if thread.isFinished: + if thread.is_finished: 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(url)) - newThread.setHost(self.xbmc_host, self.xbmc_port) - newThread.setAuth(self.xbmc_username, self.xbmc_password) + newThread = image_cache_thread.ImageCacheThread() + newThread.set_url(self.double_urlencode(url)) + newThread.set_host(self.xbmc_host, self.xbmc_port) + newThread.set_auth(self.xbmc_username, self.xbmc_password) newThread.start() self.imageCacheThreads.append(newThread) return diff --git a/resources/lib/image_cache_thread.py b/resources/lib/image_cache_thread.py index ffed967b..9f17f1f9 100644 --- a/resources/lib/image_cache_thread.py +++ b/resources/lib/image_cache_thread.py @@ -3,8 +3,8 @@ ################################################################################################# import logging -import requests import threading +import requests ################################################################################################# @@ -12,49 +12,50 @@ log = logging.getLogger("EMBY."+__name__) ################################################################################################# -class image_cache_thread(threading.Thread): +class ImageCacheThread(threading.Thread): + + url_to_process = None + is_finished = False - urlToProcess = None - isFinished = False - xbmc_host = "" xbmc_port = "" xbmc_username = "" xbmc_password = "" - + def __init__(self): threading.Thread.__init__(self) - - def setUrl(self, url): - self.urlToProcess = url - - def setHost(self, host, port): + def set_url(self, url): + + self.url_to_process = url + + def set_host(self, host, port): self.xbmc_host = host self.xbmc_port = port - - def setAuth(self, user, pwd): - self.xbmc_username = user - self.xbmc_password = pwd - + def set_auth(self, username, password): + + self.xbmc_username = username + self.xbmc_password = password + def run(self): - - log.debug("Image Caching Thread Processing: %s" % self.urlToProcess) + + log.debug("Image Caching Thread Processing: %s" % self.url_to_process) try: - response = requests.head( - url=( - "http://%s:%s/image/image://%s" - % (self.xbmc_host, self.xbmc_port, self.urlToProcess)), - auth=(self.xbmc_username, self.xbmc_password), - timeout=(35.1, 35.1)) + requests.head( + url=( + "http://%s:%s/image/image://%s" + % (self.xbmc_host, self.xbmc_port, self.url_to_process)), + auth=(self.xbmc_username, self.xbmc_password), + timeout=(35.1, 35.1)) # We don't need the result - except: pass - + except: + pass + log.debug("Image Caching Thread Exited") - self.isFinished = True \ No newline at end of file + self.is_finished = True diff --git a/resources/lib/loghandler.py b/resources/lib/loghandler.py index 9ae91f89..5169d361 100644 --- a/resources/lib/loghandler.py +++ b/resources/lib/loghandler.py @@ -11,7 +11,7 @@ from utils import window def config(): - + logger = logging.getLogger('EMBY') logger.addHandler(LogHandler()) logger.setLevel(logging.DEBUG) @@ -20,19 +20,20 @@ def config(): class LogHandler(logging.StreamHandler): def __init__(self): - + logging.StreamHandler.__init__(self) self.setFormatter(MyFormatter()) def emit(self, record): - if self._getLogLevel(record.levelno): + if self._get_log_level(record.levelno): try: xbmc.log(self.format(record), level=xbmc.LOGNOTICE) except UnicodeEncodeError: xbmc.log(self.format(record).encode('utf-8'), level=xbmc.LOGNOTICE) - def _getLogLevel(self, level): + @classmethod + def _get_log_level(cls, level): levels = { logging.ERROR: 0, @@ -41,17 +42,17 @@ class LogHandler(logging.StreamHandler): logging.DEBUG: 2 } try: - logLevel = int(window('emby_logLevel')) + log_level = int(window('emby_logLevel')) except ValueError: - logLevel = 0 + log_level = 0 - return logLevel >= levels[level] + return log_level >= levels[level] class MyFormatter(logging.Formatter): def __init__(self, fmt="%(name)s -> %(message)s"): - + logging.Formatter.__init__(self, fmt) def format(self, record): @@ -70,4 +71,4 @@ class MyFormatter(logging.Formatter): # Restore the original format configured by the user self._fmt = format_orig - return result \ No newline at end of file + return result From 965340db01ae7050234f5132a19cee9d3934ffad Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Sun, 21 Aug 2016 21:57:43 -0500 Subject: [PATCH 098/103] Fix logging and exception --- resources/lib/image_cache_thread.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/resources/lib/image_cache_thread.py b/resources/lib/image_cache_thread.py index 9f17f1f9..c789c66f 100644 --- a/resources/lib/image_cache_thread.py +++ b/resources/lib/image_cache_thread.py @@ -44,17 +44,16 @@ class ImageCacheThread(threading.Thread): def run(self): - log.debug("Image Caching Thread Processing: %s" % self.url_to_process) + log.debug("Image Caching Thread Processing: %s", self.url_to_process) try: requests.head( - url=( - "http://%s:%s/image/image://%s" - % (self.xbmc_host, self.xbmc_port, self.url_to_process)), - auth=(self.xbmc_username, self.xbmc_password), - timeout=(35.1, 35.1)) + url=("http://%s:%s/image/image://%s" + % (self.xbmc_host, self.xbmc_port, self.url_to_process)), + auth=(self.xbmc_username, self.xbmc_password), + timeout=(35.1, 35.1)) # We don't need the result - except: + except Exception: pass log.debug("Image Caching Thread Exited") From 9de0e69896cd738ade33705ef6af17a967b8dca9 Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Sun, 21 Aug 2016 22:00:35 -0500 Subject: [PATCH 099/103] Fix spacing (testing pylint) --- resources/lib/image_cache_thread.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/lib/image_cache_thread.py b/resources/lib/image_cache_thread.py index c789c66f..dbaa06e0 100644 --- a/resources/lib/image_cache_thread.py +++ b/resources/lib/image_cache_thread.py @@ -45,11 +45,11 @@ class ImageCacheThread(threading.Thread): def run(self): log.debug("Image Caching Thread Processing: %s", self.url_to_process) - + try: requests.head( url=("http://%s:%s/image/image://%s" - % (self.xbmc_host, self.xbmc_port, self.url_to_process)), + % (self.xbmc_host, self.xbmc_port, self.url_to_process)), auth=(self.xbmc_username, self.xbmc_password), timeout=(35.1, 35.1)) # We don't need the result From d5ee31e76e14dffc94ed826c5d7e970225695c75 Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Thu, 25 Aug 2016 17:00:22 -0500 Subject: [PATCH 100/103] Add Portuguese translation --- resources/language/Portuguese/strings.xml | 329 ++++++++++++++++++++++ 1 file changed, 329 insertions(+) create mode 100644 resources/language/Portuguese/strings.xml diff --git a/resources/language/Portuguese/strings.xml b/resources/language/Portuguese/strings.xml new file mode 100644 index 00000000..efadd32b --- /dev/null +++ b/resources/language/Portuguese/strings.xml @@ -0,0 +1,329 @@ + + + + Emby for Kodi + Endereço do Servidor Primário + + Reproduzir por HTTP ao invés de SMB + + Nível do log + + Nome do Dispositivo + + Avançado + Nome do usuário + + Número da Porta + + Número de Álbuns de Música recentes a exibir: + Número de Filmes recentes a exibir: + Número de episódios de TV recentes a exibir: + Atualizar + Excluir + Nome de usuário/Senha incorretos + Nome de usuário não encontrado + Excluindo + Aguardando pelo servidor para excluir + Classificar por + Nenhum + Ação + Aventura + Animação + Crime + Comédia + Documentário + Drama + Fantasia + Estrangeiro + História + Terror + Música + Musical + Mistério + Romance + Ficção Científica + Curta Metragem + Suspense + Suspense + Western + Filtro do Gênero + Confirmar exclusão do arquivo + + Marcar como assistido + Marcar como não-assistido + Classificar por + Classificar em Ordem Descendente + Classificar em Ordem Ascendente + + Retomar + Retomar a partir de + Iniciar do começo + Disponibilizar exclusão depois da reprodução + + Para Episódios + + Para Filmes + + Adicionar a Porcentagem para Retomar + Adicionar o Número do Episódio + Exibir Progresso do Carregamento + Carregando Conteúdo + Recuperando Dados + Feito + Aviso + + Erro + Busca + Ativar Imagens Melhoradas (ex. Capa) + + Metadados + Artwork + Qualidade do Vídeo + + Reprodução Direta + + Transcodificação + Sucesso na Detecção do Servidor + Servidor Encontrado + Endereço: + + Séries Recentemente Adicionadas + + Séries em Reprodução + + Todas as Músicas + Canais + + Filmes Recentemente Adicionados + + Episódios Recentemente Adicionados + + Álbuns Recentemente Adicionados + Filmes em Reprodução + + Episódios em Reprodução + + Próximos Episódios + + Filmes Favoritos + + Séries Favoritas + + Episódios Favoritos + Álbuns Mais Reproduzidos + Séries a Estrear + BoxSets + Trailers + Vídeos de Música + Fotos + Filmes Não-Assistidos + + Gêneros do Filme + Estúdios do Filme + Atores do Filme + Episódios Não-Assistidos + Gêneros da Série + Redes de TV + Atores da Série + Listas de Reprodução + Definir Visualizações + Selecionar Usuário + + Não foi possível conectar ao servidor + Músicas + Álbuns + Artistas do Álbum + Artistas + Gêneros da Música + Mais Recentes + Em Reprodução + Próxima + Visualizações do Usuário + Métricas do Relatório + Filmes Aleatórios + Episódios Aleatórios + Itens Aleatórios + + Itens Recomendados + + Extras + + Sincronizar Música-Tema + Sincronizar Extra Fanart + Sincronizar Imagens de Coletâneas + Repor base de dados local do Kodi + + Ativar HTTPS + + Forçar Codecs de Transcodificação + Ativar mensagem de conexão do servidor ao iniciar + + Vídeos Caseiros adicionados recentemente + + Fotos adicionadas recentemente + + Vídeos Caseiros Favoritos + + Fotos Favoritas + + Álbuns Favoritos + Vídeos de Música adicionados recentemente + + Vídeos de Música em Reprodução + + Vídeos de Música não-assistidos + + + Ativo + Limpar Definições + Filmes + BoxSets + Trailers + Séries + Temporadas + Episódios + Artistas da Música + Álbuns de Música + Vídeos de Música + Trilhas de Música + Canais + + Opções do Emby + Adicionar aos favoritos do Emby + Remover dos Favoritos do Emby + Definir a avaliação personalizada da música + Ajustes do addon do Emby + Excluir o item do servidor + Atualizar este item + Definir a avaliação personalizada da música (0-5) + + Verificar o Certificado SSL do Servidor + Certificado SSL do cliente + Usar endereço alternativo + Endereço Alternativo do Servidor + Usar Nome alternativo do dispositivo + [COLOR yellow]Tentar Login Novamente[/COLOR] + Opções de Sincronização + Exibir progresso de sincronização + Sincronizar Séries de TV vazias + Ativar Biblioteca de Música + Stream direto da biblioteca de música + Modo de Reprodução + Forçar o caching de artwork + Limitar as threads de cache de artwork (recomendado para rpi) + Ativar inicialização rápida (é necessário o plugin do servidor) + Máximo de itens para solicitar ao servidor de uma única vez + Reprodução + Credenciais de rede + Ativar cinema mode do Emby + Confirmar a reprodução de trailers + Ignorar a confirmação de exclusão no Emby para o menú de contexto (use sob seu próprio risco) + intervalo para voltar na função retomar (em segundos) + Foçar transcodificação H265 + Opções de metadados de música (não compatível com stream direto) + Importar avaliação da música diretamente dos arquivos + Converter a avaliação da música para a avaliação Emby. + Permitir que as avaliações nos arquivos de música sejam atualizadas + Ignorar especiais nos próximos episódios + Usuários permanentes para adicionar à sessão + Atraso na inicialização (em segundos) + Ativar mensagem de reinicialização do servidor + Ativar notificação de novo conteúdo + Duração do janela da biblioteca de vídeo (em segundos) + Duração da janela da biblioteca de música (em segundos) + Mensagens do servidor + Gerar um novo id do dispositivo + Sincronizar quando o protetor de tela está desativado + Forçar Transcodificação Hi10P + Desativado + + Bem vindo + Erro na conexão + Servidor não pode ser encontrado + Servidor está ativo + Itens adicionados à lista de reprodução + Itens enfileirados na lista de reprodução + Servidor está reiniciando + O acesso está ativo + Digite a senha do usuário: + Nome de usuário ou senha inválidos + Falha ao autenticar inúmeras vezes + Não é possível a reprodução direta + A reprodução direta falhou 3 vezes. Ative a reprodução por HTTP. + Escolha o stream de áudio + Escolha o stream de legendas + Excluir arquivo de seu Servidor Emby? + Reproduzir trailers? + Coletando filmes de: + Coletando boxsets + Coletando vídeos de música de: + Coletando séries de: + Coletando: + Foi detectado que a base de dados necessita ser recriada para esta versão do Emby for Kodi. Prosseguir? + O Emby for Kodi pode não funcionar corretamente até que a base de dados seja atualizada. + Cancelando o processo de sincronização da base de dados. A versão atual do Kodi não é suportada. + completa em: + Comparando filmes de: + Comparando coletâneas + Comparando vídeos clipes de: + Comparando séries de tv de: + Comparando episódios de: + Comparando: + Falha ao gerar um novo Id de dispositivo. Veja seus logs para mais informações. + Um novo Id de dispositivo foi gerado. Kodi irá reiniciar. + Prosseguir com o seguinte servidor? + Cuidado! Se escolher modo Nativo, certas funções do Emby serão perdidas, como: Emby cinema mode, opções de stream/transcodificação direta e agendamento de acesso pelos pais. + Addon (Padrão) + Nativo (Locais Diretos) + Adicionar credenciais de rede para permitir que o Kodi acesse seu conteúdo? Importante: Kodi precisará ser reiniciado para ver as credenciais. Elas também podem ser adicionadas mais tarde. + Desativar biblioteca de músicas do Emby? + Fazer stream direto da biblioteca de músicas? Selecione esta opção se a biblioteca de músicas for acessada remotamente. + Excluir arquivo(s) do Servidor Emby? Esta opção também excluirá o(s) arquivo(s) do disco! + Executar o processo de caching pode levar bastante tempo. Continuar assim mesmo? + Sincronização do cache de artwork + Limpar cache de artwork atual? + Atualizando o cache de artwork: + Aguardando que todos os processos terminem: + Kodi não pode localizar o arquivo: + Você precisa verificar suas credenciais de rede nos ajustes de add-on ou usar substituição de locais no Emby para formatar seu local corretamente (Painel Emby > biblioteca). Parar de sincronizar? + Adicionado: + Se falhar para entrar diversas vezes, o servidor Emby pode bloquear sua conta, Deseja prosseguir? + Canais de TV ao Vivo (experimental) + Gravações de TV ao Vivo (experimental) + Ajustes + Adicionar usuário à sessão + Atualizar nós de listas de reprodução/Vídeo + Executar sincronização manual + Reparar base de dados local (forçar atualização para todo o conteúdo) + Executar reset da base de dados local + Colocar toda artwork no cache + Sincronizar Mídia de Tema do Emby para o Kodi + Adicionar/Remover usuário da sessão + Adicionar usuário + Remover usuário + Remover usuário da sessão + Sucesso! + Removido da seguinte sessão: + Adicionado à seguinte sessão: + Não foi possível adicionar/remover usuário da sessão. + Sucesso na tarefa + Falha na tarefa + Stream Direto + Método de reprodução para seus temas + O arquivo de ajustes não existe na TV Tunes. Altere o ajuste e execute a tarefa novamente. + Deseja realmente resetar a base de dados local do Kodi? + Modificar/Remover as credenciais de rede + Modificar + Remover + Removido: + Digite o nome de usuário da rede + Digite a senha da rede + Credenciais de rede adicionadas para: + Digite o nome do servidor ou endereço IP como indicado nos locais de sua biblioteca do emby. Por exemplo, o nome do servidor \\\\SERVIDOR-PC\\local\\ é \"SERVIDOR-PC\" + Modificar o nome do servidor ou endereço IP + Digite o nome do servidor ou endereço IP + Não é possível resetar a base de dados. Tente novamente. + Remover toda artwork do cache? + Resetar todos os ajustes do add-on Emby? + O reset da base de dados foi concluída, Kodi irá reiniciar para aplicar as alterações. + From 1222d4d9067d3bcbf80634401f0890132063d412 Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Fri, 26 Aug 2016 01:33:46 -0500 Subject: [PATCH 101/103] Reinstate airsbefore default value -1 --- resources/lib/itemtypes.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/lib/itemtypes.py b/resources/lib/itemtypes.py index 51abdf3c..1ce7bfe2 100644 --- a/resources/lib/itemtypes.py +++ b/resources/lib/itemtypes.py @@ -1277,8 +1277,8 @@ class TVShows(Items): airsBeforeSeason = item['AirsAfterSeasonNumber'] airsBeforeEpisode = 4096 # Kodi default number for afterseason ordering else: - airsBeforeSeason = item.get('AirsBeforeSeasonNumber') - airsBeforeEpisode = item.get('AirsBeforeEpisodeNumber') + airsBeforeSeason = item.get('AirsBeforeSeasonNumber', -1) + airsBeforeEpisode = item.get('AirsBeforeEpisodeNumber', -1) # Append multi episodes to title if item.get('IndexNumberEnd'): From dbec225072fb71971ea5a136b3a8b963156fa6b9 Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Fri, 26 Aug 2016 04:49:14 -0500 Subject: [PATCH 102/103] Revert "Reinstate airsbefore default value -1" This reverts commit 1222d4d9067d3bcbf80634401f0890132063d412. --- resources/lib/itemtypes.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/lib/itemtypes.py b/resources/lib/itemtypes.py index 1ce7bfe2..51abdf3c 100644 --- a/resources/lib/itemtypes.py +++ b/resources/lib/itemtypes.py @@ -1277,8 +1277,8 @@ class TVShows(Items): airsBeforeSeason = item['AirsAfterSeasonNumber'] airsBeforeEpisode = 4096 # Kodi default number for afterseason ordering else: - airsBeforeSeason = item.get('AirsBeforeSeasonNumber', -1) - airsBeforeEpisode = item.get('AirsBeforeEpisodeNumber', -1) + airsBeforeSeason = item.get('AirsBeforeSeasonNumber') + airsBeforeEpisode = item.get('AirsBeforeEpisodeNumber') # Append multi episodes to title if item.get('IndexNumberEnd'): From 00c0556300041cecf91715a5a1707504f42bd96e Mon Sep 17 00:00:00 2001 From: xnappo Date: Sun, 28 Aug 2016 16:49:01 -0500 Subject: [PATCH 103/103] Fix for Krypton --- resources/lib/itemtypes.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/resources/lib/itemtypes.py b/resources/lib/itemtypes.py index 51abdf3c..73dc4c59 100644 --- a/resources/lib/itemtypes.py +++ b/resources/lib/itemtypes.py @@ -2304,7 +2304,10 @@ class Music(Items): elif mediatype == "album": # Process playstates - query = "UPDATE album SET iRating = ? WHERE idAlbum = ?" + if self.kodiversion >= 17: + query = "UPDATE album SET fRating = ? WHERE idAlbum = ?" + else: + query = "UPDATE album SET iRating = ? WHERE idAlbum = ?" kodicursor.execute(query, (rating, kodiid)) emby_db.updateReference(itemid, checksum)