diff --git a/resources/lib/API.py b/resources/lib/API.py deleted file mode 100644 index d15822bb..00000000 --- a/resources/lib/API.py +++ /dev/null @@ -1,378 +0,0 @@ -# -*- coding: utf-8 -*- - -################################################################################################## - -import clientinfo -import utils - -################################################################################################## - - -class API(): - - def __init__(self, item): - - 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 - favorite = False - playcount = None - played = False - lastPlayedDate = None - resume = 0 - rating = 0 - - try: - userdata = self.item['UserData'] - - except KeyError: # No userdata found. - pass - - else: - favorite = userdata['IsFavorite'] - likes = userdata.get('Likes') - # Rating for album and songs - if favorite: - rating = 5 - elif likes: - rating = 3 - elif likes == False: - rating = 1 - else: - rating = 0 - - lastPlayedDate = userdata.get('LastPlayedDate') - if lastPlayedDate: - lastPlayedDate = lastPlayedDate.split('.')[0].replace('T', " ") - - if userdata['Played']: - # Playcount is tied to the watch status - played = True - playcount = userdata['PlayCount'] - if playcount == 0: - playcount = 1 - - if lastPlayedDate is None: - lastPlayedDate = self.getDateCreated() - - playbackPosition = userdata.get('PlaybackPositionTicks') - if playbackPosition: - resume = playbackPosition / 10000000.0 - - return { - - 'Favorite': favorite, - 'PlayCount': playcount, - 'Played': played, - 'LastPlayedDate': lastPlayedDate, - 'Resume': resume, - 'Rating': rating - } - - def getPeople(self): - # Process People - director = [] - writer = [] - cast = [] - - try: - people = self.item['People'] - - except KeyError: - pass - - else: - for person in people: - - type = person['Type'] - name = person['Name'] - - if "Director" in type: - director.append(name) - elif "Actor" in type: - cast.append(name) - elif type in ("Writing", "Writer"): - writer.append(name) - - return { - - 'Director': director, - 'Writer': writer, - 'Cast': cast - } - - def getMediaStreams(self): - item = self.item - videotracks = [] - audiotracks = [] - subtitlelanguages = [] - - try: - media_streams = item['MediaSources'][0]['MediaStreams'] - - except KeyError: - media_streams = item['MediaStreams'] - - for media_stream in media_streams: - # Sort through Video, Audio, Subtitle - stream_type = media_stream['Type'] - codec = media_stream.get('Codec', "").lower() - profile = media_stream.get('Profile', "").lower() - - if stream_type == "Video": - # Height, Width, Codec, AspectRatio, AspectFloat, 3D - track = { - - 'videocodec': codec, - 'height': media_stream.get('Height'), - 'width': media_stream.get('Width'), - 'video3DFormat': item.get('Video3DFormat'), - 'aspectratio': 1.85 - } - - try: - container = item['MediaSources'][0]['Container'].lower() - except: - container = "" - - # Sort codec vs container/profile - if "msmpeg4" in codec: - track['videocodec'] = "divx" - elif "mpeg4" in codec: - if "simple profile" in profile or not profile: - track['videocodec'] = "xvid" - elif "h264" in codec: - if container in ("mp4", "mov", "m4v"): - track['videocodec'] = "avc1" - - # Aspect ratio - if item.get('AspectRatio'): - # Metadata AR - aspectratio = item['AspectRatio'] - else: # File AR - aspectratio = media_stream.get('AspectRatio', "0") - - try: - aspectwidth, aspectheight = aspectratio.split(':') - track['aspectratio'] = round(float(aspectwidth) / float(aspectheight), 6) - - except ValueError: - width = track['width'] - height = track['height'] - - if width and height: - track['aspectratio'] = round(float(width / height), 6) - - videotracks.append(track) - - elif stream_type == "Audio": - # Codec, Channels, language - track = { - - 'audiocodec': codec, - 'channels': media_stream.get('Channels'), - 'audiolanguage': media_stream.get('Language') - } - - if "dca" in codec and "dts-hd ma" in profile: - track['audiocodec'] = "dtshd_ma" - - audiotracks.append(track) - - elif stream_type == "Subtitle": - # Language - subtitlelanguages.append(media_stream.get('Language', "Unknown")) - - return { - - 'video': videotracks, - 'audio': audiotracks, - 'subtitle': subtitlelanguages - } - - def getRuntime(self): - item = self.item - try: - runtime = item['RunTimeTicks'] / 10000000.0 - - except KeyError: - runtime = item.get('CumulativeRunTimeTicks', 0) / 10000000.0 - - return runtime - - def adjustResume(self, resume_seconds): - - resume = 0 - if resume_seconds: - resume = round(float(resume_seconds), 6) - jumpback = int(utils.settings('resumeJumpBack')) - if resume > jumpback: - # To avoid negative bookmark - resume = resume - jumpback - - return resume - - def getStudios(self): - # Process Studios - item = self.item - studios = [] - - try: - studio = item['SeriesStudio'] - studios.append(self.verifyStudio(studio)) - - except KeyError: - studioList = item['Studios'] - for studio in studioList: - - name = studio['Name'] - studios.append(self.verifyStudio(name)) - - return studios - - def verifyStudio(self, studioName): - # Convert studio for Kodi to properly detect them - studios = { - - 'abc (us)': "ABC", - 'fox (us)': "FOX", - 'mtv (us)': "MTV", - 'showcase (ca)': "Showcase", - 'wgn america': "WGN" - } - - return studios.get(studioName.lower(), studioName) - - def getChecksum(self): - # Use the etags checksum and userdata - item = self.item - userdata = item['UserData'] - - checksum = "%s%s%s%s%s%s" % ( - - item['Etag'], - userdata['Played'], - userdata['IsFavorite'], - userdata['PlaybackPositionTicks'], - userdata.get('UnplayedItemCount', ""), - userdata.get('LastPlayedDate', "") - ) - - return checksum - - def getGenres(self): - item = self.item - all_genres = "" - genres = item.get('Genres', item.get('SeriesGenres')) - - if genres: - all_genres = " / ".join(genres) - - return all_genres - - def getDateCreated(self): - - try: - dateadded = self.item['DateCreated'] - dateadded = dateadded.split('.')[0].replace('T', " ") - except KeyError: - dateadded = None - - return dateadded - - def getPremiereDate(self): - - try: - premiere = self.item['PremiereDate'] - premiere = premiere.split('.')[0].replace('T', " ") - except KeyError: - premiere = None - - return premiere - - def getOverview(self): - - try: - overview = self.item['Overview'] - overview = overview.replace("\"", "\'") - overview = overview.replace("\n", " ") - overview = overview.replace("\r", " ") - except KeyError: - overview = "" - - return overview - - def getTagline(self): - - try: - tagline = self.item['Taglines'][0] - except IndexError: - tagline = None - - return tagline - - def getProvider(self, providername): - - try: - provider = self.item['ProviderIds'][providername] - except KeyError: - provider = None - - return provider - - def getMpaa(self): - # Convert more complex cases - mpaa = self.item.get('OfficialRating', "") - - if mpaa in ("NR", "UR"): - # Kodi seems to not like NR, but will accept Not Rated - mpaa = "Not Rated" - - return mpaa - - def getCountry(self): - - try: - country = self.item['ProductionLocations'][0] - except IndexError: - country = None - - return country - - def getFilePath(self): - - item = self.item - try: - filepath = item['Path'] - - except KeyError: - filepath = "" - - else: - if "\\\\" in filepath: - # append smb protocol - filepath = filepath.replace("\\\\", "smb://") - filepath = filepath.replace("\\", "/") - - if item.get('VideoType'): - videotype = item['VideoType'] - # Specific format modification - if 'Dvd'in videotype: - filepath = "%s/VIDEO_TS/VIDEO_TS.IFO" % filepath - elif 'Bluray' in videotype: - filepath = "%s/BDMV/index.bdmv" % filepath - - if "\\" in filepath: - # Local path scenario, with special videotype - filepath = filepath.replace("/", "\\") - - return filepath \ No newline at end of file diff --git a/resources/lib/DownloadUtils.py b/resources/lib/DownloadUtils.py deleted file mode 100644 index 04d2da85..00000000 --- a/resources/lib/DownloadUtils.py +++ /dev/null @@ -1,347 +0,0 @@ -import xbmc -import xbmcgui -import xbmcaddon - -import requests -import json -import logging - -import Utils as utils -from ClientInformation import ClientInformation -from requests.packages.urllib3.exceptions import InsecureRequestWarning - -# Disable requests logging -requests.packages.urllib3.disable_warnings(InsecureRequestWarning) -#logging.getLogger("requests").setLevel(logging.WARNING) - -class DownloadUtils(): - - # Borg - multiple instances, shared state - _shared_state = {} - clientInfo = ClientInformation() - - addonName = clientInfo.getAddonName() - addon = xbmcaddon.Addon() - WINDOW = xbmcgui.Window(10000) - - # Requests session - s = None - timeout = 60 - - def __init__(self): - - self.__dict__ = self._shared_state - - def logMsg(self, msg, lvl=1): - - self.className = self.__class__.__name__ - utils.logMsg("%s %s" % (self.addonName, self.className), msg, int(lvl)) - - def setUsername(self, username): - # Reserved for UserClient only - self.username = username - self.logMsg("Set username: %s" % username, 2) - - def setUserId(self, userId): - # Reserved for UserClient only - self.userId = userId - self.logMsg("Set userId: %s" % userId, 2) - - def setServer(self, server): - # Reserved for UserClient only - self.server = server - self.logMsg("Set server: %s" % server, 2) - - def setToken(self, token): - # Reserved for UserClient only - self.token = token - self.logMsg("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) - - def postCapabilities(self, deviceId): - - # Post settings to session - url = "{server}/mediabrowser/Sessions/Capabilities/Full" - data = { - 'PlayableMediaTypes': "Audio,Video", - 'SupportsMediaControl': True, - 'SupportedCommands': ( - - "MoveUp,MoveDown,MoveLeft,MoveRight,Select," - "Back,ToggleContextMenu,ToggleFullscreen,ToggleOsdMenu," - "GoHome,PageUp,NextLetter,GoToSearch," - "GoToSettings,PageDown,PreviousLetter,TakeScreenshot," - "VolumeUp,VolumeDown,ToggleMute,SendString,DisplayMessage," - "SetAudioStreamIndex,SetSubtitleStreamIndex," - - "Mute,Unmute,SetVolume," - "Play,Playstate,PlayNext" - ) - } - - self.logMsg("Capabilities URL: %s" % url, 2) - self.logMsg("PostData: %s" % data, 2) - - try: - self.downloadUrl(url, postBody=data, type="POST") - self.logMsg("Posted capabilities to %s" % self.server, 1) - except: - self.logMsg("Posted capabilities failed.") - - # Attempt at getting sessionId - url = "{server}/mediabrowser/Sessions?DeviceId=%s&format=json" % deviceId - - try: - result = self.downloadUrl(url) - self.logMsg("Session: %s" % result, 2) - - sessionId = result[0][u'Id'] - self.logMsg("SessionId: %s" % sessionId) - self.WINDOW.setProperty("sessionId%s" % self.username, sessionId) - except: - self.logMsg("Failed to retrieve sessionId.", 1) - else: - # Post any permanent additional users - additionalUsers = utils.settings('additionalUsers').split(',') - self.logMsg("List of permanent users that should be added to the session: %s" % str(additionalUsers), 1) - # Get the user list from server to get the userId - url = "{server}/mediabrowser/Users?format=json" - result = self.downloadUrl(url) - - if result: - for user in result: - username = user['Name'].lower() - userId = user['Id'] - for additional in additionalUsers: - addUser = additional.decode('utf-8').lower() - if username in addUser: - url = "{server}/mediabrowser/Sessions/%s/Users/%s" % (sessionId, userId) - postdata = {} - self.downloadUrl(url, postBody=postdata, type="POST") - #xbmcgui.Dialog().notification("Success!", "%s added to viewing session" % username, time=1000) - - def startSession(self): - - self.deviceId = self.clientInfo.getMachineId() - - # User is identified from this point - # Attach authenticated header to the session - verify = None - cert = None - header = self.getHeader() - - # If user enabled host certificate verification - try: - verify = self.sslverify - cert = self.sslclient - except: - self.logMsg("Could not load SSL settings.", 1) - - # Start session - self.s = requests.Session() - self.s.headers = header - self.s.verify = verify - self.s.cert = cert - # Retry connections to the server - 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) - - def stopSession(self): - try: - self.s.close() - except: - self.logMsg("Requests session could not be terminated.", 1) - - def getHeader(self, authenticate=True): - - clientInfo = self.clientInfo - - deviceName = clientInfo.getDeviceName() - deviceId = clientInfo.getMachineId() - version = clientInfo.getVersion() - - if not authenticate: - # If user is not authenticated - auth = 'MediaBrowser Client="Kodi", Device="%s", DeviceId="%s", Version="%s"' % (deviceName, deviceId, version) - header = {'Content-type': 'application/json', 'Accept-encoding': 'gzip', 'Accept-Charset': 'UTF-8,*', 'Authorization': auth} - - self.logMsg("Header: %s" % header, 2) - return header - - 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} - - self.logMsg("Header: %s" % header, 2) - return header - - def downloadUrl(self, url, postBody=None, type="GET", authenticate=True): - - self.logMsg("=== ENTER downloadUrl ===", 2) - - WINDOW = self.WINDOW - timeout = self.timeout - default_link = "" - - try: - - # If user is authenticated - if (authenticate): - # Get requests session - try: - s = self.s - # Replace for the real values and append api_key - url = url.replace("{server}", self.server, 1) - url = url.replace("{UserId}", self.userId, 1) - - self.logMsg("URL: %s" % url, 2) - # Prepare request - if type == "GET": - r = s.get(url, json=postBody, timeout=timeout) - elif type == "POST": - r = s.post(url, json=postBody, timeout=timeout) - elif type == "DELETE": - r = s.delete(url, json=postBody, timeout=timeout) - - except AttributeError: - - # Get user information - self.username = WINDOW.getProperty('currUser') - self.userId = WINDOW.getProperty('userId%s' % self.username) - self.server = WINDOW.getProperty('server%s' % self.username) - self.token = WINDOW.getProperty('accessToken%s' % self.username) - header = self.getHeader() - verifyssl = False - cert = None - - # IF user enables ssl verification - try: - if utils.settings('sslverify') == "true": - verifyssl = True - if utils.settings('sslcert') != "None": - cert = utils.settings('sslcert') - except: - self.logMsg("Could not load SSL settings.", 1) - pass - - # Replace for the real values and append api_key - url = url.replace("{server}", self.server, 1) - url = url.replace("{UserId}", self.userId, 1) - - self.logMsg("URL: %s" % url, 2) - # Prepare request - if type == "GET": - r = requests.get(url, json=postBody, headers=header, timeout=timeout, cert=cert, verify=verifyssl) - elif type == "POST": - r = requests.post(url, json=postBody, headers=header, timeout=timeout, cert=cert, verify=verifyssl) - elif type == "DELETE": - r = requests.delete(url, json=postBody, headers=header, timeout=timeout, cert=cert, verify=verifyssl) - - # If user is not authenticated - elif not authenticate: - - self.logMsg("URL: %s" % url, 2) - header = self.getHeader(authenticate=False) - verifyssl = False - - # If user enables ssl verification - try: - verifyssl = self.sslverify - except AttributeError: - pass - - # Prepare request - if type == "GET": - r = requests.get(url, json=postBody, headers=header, timeout=timeout, verify=verifyssl) - elif type == "POST": - r = requests.post(url, json=postBody, headers=header, timeout=timeout, verify=verifyssl) - - # Process the response - if r.status_code == 204: - # No body in the response - self.logMsg("====== 204 Success ======", 2) - return default_link - - elif r.status_code == requests.codes.ok: - try: - # UTF-8 - JSON object - r = r.json() - self.logMsg("====== 200 Success ======", 2) - self.logMsg("Response: %s" % r, 2) - return r - except: - if r.headers.get('content-type') == "text/html": - pass - else: - self.logMsg("Unable to convert the response for: %s" % url, 1) - else: - r.raise_for_status() - - return default_link - - # TO REVIEW EXCEPTIONS - except requests.exceptions.ConnectionError as e: - # Make the addon aware of status - if WINDOW.getProperty("Server_online") != "false": - self.logMsg("Server unreachable at: %s" % url, 0) - self.logMsg(e, 2) - WINDOW.setProperty("Server_online", "false") - pass - - except requests.exceptions.ConnectTimeout as e: - self.logMsg("Server timeout at: %s" % url, 0) - self.logMsg(e, 1) - - except requests.exceptions.HTTPError as e: - - if r.status_code == 401: - # Unauthorized - status = WINDOW.getProperty("Server_status") - - if 'x-application-error-code' in r.headers: - if r.headers['X-Application-Error-Code'] == "ParentalControl": - # Parental control - access restricted - WINDOW.setProperty("Server_status", "restricted") - xbmcgui.Dialog().notification("Emby server", "Access restricted.", xbmcgui.NOTIFICATION_ERROR, time=5000) - return False - elif r.headers['X-Application-Error-Code'] == "UnauthorizedAccessException": - # User tried to do something his emby account doesn't allow - admin restricted in some way - pass - - elif (status == "401") or (status == "Auth"): - pass - - else: - # Tell UserClient token has been revoked. - WINDOW.setProperty("Server_status", "401") - self.logMsg("HTTP Error: %s" % e, 0) - xbmcgui.Dialog().notification("Error connecting", "Unauthorized.", xbmcgui.NOTIFICATION_ERROR) - return 401 - - elif (r.status_code == 301) or (r.status_code == 302): - # Redirects - pass - elif r.status_code == 400: - # Bad requests - pass - - except requests.exceptions.SSLError as e: - self.logMsg("Invalid SSL certificate for: %s" % url, 0) - self.logMsg(e, 1) - - except requests.exceptions.RequestException as e: - self.logMsg("Unknown error connecting to: %s" % url, 0) - self.logMsg(e, 1) - - return default_link diff --git a/resources/lib/Entrypoint.py b/resources/lib/Entrypoint.py deleted file mode 100644 index 95a5a327..00000000 --- a/resources/lib/Entrypoint.py +++ /dev/null @@ -1,694 +0,0 @@ -import xbmcaddon -import xbmcplugin -import xbmc -import xbmcgui -import xbmcvfs -import os, sys -import threading -import json -import urllib -import time - -WINDOW = xbmcgui.Window(10000) - -import Utils as utils -from ClientInformation import ClientInformation -from PlaybackUtils import PlaybackUtils -from PlayUtils import PlayUtils -from DownloadUtils import DownloadUtils -from ReadEmbyDB import ReadEmbyDB -from API import API -from UserPreferences import UserPreferences - - -##### Play items via plugin://plugin.video.emby/ ##### -def doPlayback(id): - url = "{server}/mediabrowser/Users/{UserId}/Items/%s?format=json&ImageTypeLimit=1" % id - result = DownloadUtils().downloadUrl(url) - item = PlaybackUtils().PLAY(result, setup="default") - -#### DO RESET AUTH ##### -def resetAuth(): - # User tried login and failed too many times - resp = xbmcgui.Dialog().yesno("Warning", "Emby might lock your account if you fail to log in too many times. Proceed anyway?") - if resp == 1: - xbmc.log("Reset login attempts.") - WINDOW.setProperty("Server_status", "Auth") - else: - xbmc.executebuiltin('Addon.OpenSettings(plugin.video.emby)') - -### ADD ADDITIONAL USERS ### -def addUser(): - - doUtils = DownloadUtils() - clientInfo = ClientInformation() - currUser = WINDOW.getProperty("currUser") - deviceId = clientInfo.getMachineId() - deviceName = clientInfo.getDeviceName() - - # Get session - url = "{server}/mediabrowser/Sessions?DeviceId=%s" % deviceId - result = doUtils.downloadUrl(url) - - try: - sessionId = result[0][u'Id'] - additionalUsers = result[0][u'AdditionalUsers'] - # Add user to session - userlist = {} - users = [] - url = "{server}/mediabrowser/Users?IsDisabled=false&IsHidden=false" - result = doUtils.downloadUrl(url) - - # pull the list of users - for user in result: - name = user[u'Name'] - userId = user[u'Id'] - if currUser not in name: - userlist[name] = userId - users.append(name) - - # Display dialog if there's additional users - if additionalUsers: - - option = xbmcgui.Dialog().select("Add/Remove user from the session", ["Add user", "Remove user"]) - # Users currently in the session - additionalUserlist = {} - additionalUsername = [] - # Users currently in the session - for user in additionalUsers: - name = user[u'UserName'] - userId = user[u'UserId'] - additionalUserlist[name] = userId - additionalUsername.append(name) - - if option == 1: - # User selected Remove user - resp = xbmcgui.Dialog().select("Remove user from the session", additionalUsername) - if resp > -1: - selected = additionalUsername[resp] - selected_userId = additionalUserlist[selected] - url = "{server}/mediabrowser/Sessions/%s/Users/%s" % (sessionId, selected_userId) - postdata = {} - doUtils.downloadUrl(url, postBody=postdata, type="DELETE") - xbmcgui.Dialog().notification("Success!", "%s removed from viewing session" % selected, time=1000) - - # clear picture - position = WINDOW.getProperty('EmbyAdditionalUserPosition.' + selected_userId) - WINDOW.clearProperty('EmbyAdditionalUserImage.' + str(position)) - return - else: - return - - elif option == 0: - # User selected Add user - for adduser in additionalUsername: - try: # Remove from selected already added users. It is possible they are hidden. - users.remove(adduser) - except: pass - - elif option < 0: - # User cancelled - return - - # Subtract any additional users - xbmc.log("Displaying list of users: %s" % users) - resp = xbmcgui.Dialog().select("Add user to the session", users) - # post additional user - if resp > -1: - selected = users[resp] - selected_userId = userlist[selected] - url = "{server}/mediabrowser/Sessions/%s/Users/%s" % (sessionId, selected_userId) - postdata = {} - doUtils.downloadUrl(url, postBody=postdata, type="POST") - xbmcgui.Dialog().notification("Success!", "%s added to viewing session" % selected, time=1000) - - except: - xbmc.log("Failed to add user to session.") - xbmcgui.Dialog().notification("Error", "Unable to add/remove user from the session.", xbmcgui.NOTIFICATION_ERROR) - - try: - # Add additional user images - #always clear the individual items first - totalNodes = 10 - for i in range(totalNodes): - if not WINDOW.getProperty('EmbyAdditionalUserImage.' + str(i)): - break - WINDOW.clearProperty('EmbyAdditionalUserImage.' + str(i)) - - url = "{server}/mediabrowser/Sessions?DeviceId=%s" % deviceId - result = doUtils.downloadUrl(url) - additionalUsers = result[0][u'AdditionalUsers'] - count = 0 - for additionaluser in additionalUsers: - url = "{server}/mediabrowser/Users/%s?format=json" % (additionaluser[u'UserId']) - result = doUtils.downloadUrl(url) - WINDOW.setProperty("EmbyAdditionalUserImage." + str(count),API().getUserArtwork(result,"Primary")) - WINDOW.setProperty("EmbyAdditionalUserPosition." + str(additionaluser[u'UserId']),str(count)) - count +=1 - except: - pass - -# THEME MUSIC/VIDEOS -def getThemeMedia(): - - doUtils = DownloadUtils() - playUtils = PlayUtils() - - currUser = WINDOW.getProperty('currUser') - server = WINDOW.getProperty('server%s' % currUser) - playback = None - - library = xbmc.translatePath("special://profile/addon_data/plugin.video.emby/library/").decode('utf-8') - - # Choose playback method - resp = xbmcgui.Dialog().select("Choose playback method for your themes", ["Direct Play", "Direct Stream"]) - if resp == 0: - # Direct Play - playback = "DirectPlay" - elif resp == 1: - # Direct Stream - playback = "DirectStream" - else:return - - # Set custom path for user - tvtunes_path = xbmc.translatePath("special://profile/addon_data/script.tvtunes/").decode('utf-8') - if xbmcvfs.exists(tvtunes_path): - tvtunes = xbmcaddon.Addon(id="script.tvtunes") - tvtunes.setSetting('custom_path_enable', "true") - tvtunes.setSetting('custom_path', library) - xbmc.log("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. - dialog = xbmcgui.Dialog() - dialog.ok('Warning', 'The settings file does not exist in tvtunes. Go to the tvtunes addon and change a setting, then come back and re-run') - return - - - # Create library directory - if not xbmcvfs.exists(library): - xbmcvfs.mkdir(library) - - # Get every user view Id - userViews = [] - url = "{server}/mediabrowser/Users/{UserId}/Items?format=json" - result = doUtils.downloadUrl(url) - - for view in result[u'Items']: - userviewId = view[u'Id'] - userViews.append(userviewId) - - - # Get Ids with Theme Videos - itemIds = {} - for view in userViews: - url = "{server}/mediabrowser/Users/{UserId}/Items?HasThemeVideo=True&ParentId=%s&format=json" % view - result = doUtils.downloadUrl(url) - if result[u'TotalRecordCount'] != 0: - for item in result[u'Items']: - itemId = item[u'Id'] - folderName = item[u'Name'] - folderName = utils.normalize_string(folderName.encode('utf-8')) - itemIds[itemId] = folderName - - # Get paths for theme videos - for itemId in itemIds: - nfo_path = xbmc.translatePath("special://profile/addon_data/plugin.video.emby/library/%s/" % itemIds[itemId]) - # Create folders for each content - if not xbmcvfs.exists(nfo_path): - xbmcvfs.mkdir(nfo_path) - # Where to put the nfos - nfo_path = "%s%s" % (nfo_path, "tvtunes.nfo") - - url = "{server}/mediabrowser/Items/%s/ThemeVideos?format=json" % itemId - result = doUtils.downloadUrl(url) - - # Create nfo and write themes to it - nfo_file = open(nfo_path, 'w') - pathstowrite = "" - # May be more than one theme - for theme in result[u'Items']: - if playback == "DirectPlay": - playurl = playUtils.directPlay(theme) - else: - playurl = playUtils.directStream(result, server, theme[u'Id'], "ThemeVideo") - pathstowrite += ('%s' % playurl.encode('utf-8')) - - # Check if the item has theme songs and add them - url = "{server}/mediabrowser/Items/%s/ThemeSongs?format=json" % itemId - result = doUtils.downloadUrl(url) - - # May be more than one theme - for theme in result[u'Items']: - if playback == "DirectPlay": - playurl = playUtils.directPlay(theme) - else: - playurl = playUtils.directStream(result, server, theme[u'Id'], "Audio") - pathstowrite += ('%s' % playurl.encode('utf-8')) - - nfo_file.write( - '%s' % pathstowrite - ) - # Close nfo file - nfo_file.close() - - # Get Ids with Theme songs - musicitemIds = {} - for view in userViews: - url = "{server}/mediabrowser/Users/{UserId}/Items?HasThemeSong=True&ParentId=%s&format=json" % view - result = doUtils.downloadUrl(url) - if result[u'TotalRecordCount'] != 0: - for item in result[u'Items']: - itemId = item[u'Id'] - folderName = item[u'Name'] - folderName = utils.normalize_string(folderName.encode('utf-8')) - musicitemIds[itemId] = folderName - - # Get paths - for itemId in musicitemIds: - - # if the item was already processed with video themes back out - if itemId in itemIds: - continue - - nfo_path = xbmc.translatePath("special://profile/addon_data/plugin.video.emby/library/%s/" % musicitemIds[itemId]) - # Create folders for each content - if not xbmcvfs.exists(nfo_path): - xbmcvfs.mkdir(nfo_path) - # Where to put the nfos - nfo_path = "%s%s" % (nfo_path, "tvtunes.nfo") - - url = "{server}/mediabrowser/Items/%s/ThemeSongs?format=json" % itemId - result = doUtils.downloadUrl(url) - - # Create nfo and write themes to it - nfo_file = open(nfo_path, 'w') - pathstowrite = "" - # May be more than one theme - for theme in result[u'Items']: - if playback == "DirectPlay": - playurl = playUtils.directPlay(theme) - else: - playurl = playUtils.directStream(result, server, theme[u'Id'], "Audio") - pathstowrite += ('%s' % playurl.encode('utf-8')) - - nfo_file.write( - '%s' % pathstowrite - ) - # Close nfo file - nfo_file.close() - -def userPreferences(): - doUtils = DownloadUtils() - addonSettings = xbmcaddon.Addon(id='plugin.video.emby') - userPreferencesPage = UserPreferences("script-emby-kodi-UserPreferences.xml", addonSettings.getAddonInfo('path'), "default", "1080i") - url = "{server}/mediabrowser/Users/{UserId}" - result = doUtils.downloadUrl(url) - configuration = result[u'Configuration'] - userPreferencesPage.setConfiguration(configuration) - userPreferencesPage.setName(result[u'Name']) - userPreferencesPage.setImage(API().getUserArtwork(result,"Primary")) - - userPreferencesPage.doModal() - if userPreferencesPage.isSave(): - url = "{server}/mediabrowser/Users/{UserId}/Configuration" - postdata = userPreferencesPage.getConfiguration() - doUtils.downloadUrl(url, postBody=postdata, type="POST") - -##### BROWSE EMBY CHANNELS ##### -def BrowseChannels(id, folderid=None): - - _addon_id = int(sys.argv[1]) - _addon_url = sys.argv[0] - - xbmcplugin.setContent(int(sys.argv[1]), 'files') - if folderid: - url = "{server}/mediabrowser/Channels/" + id + "/Items?userid={UserId}&folderid=" + folderid + "&format=json" - else: - if id == "0": # id 0 is the root channels folder - url = "{server}/mediabrowser/Channels?{UserId}&format=json" - else: - url = "{server}/mediabrowser/Channels/" + id + "/Items?userid={UserId}&format=json" - - results = DownloadUtils().downloadUrl(url) - if results: - result = results.get("Items") - if(result == None): - result = [] - - item_count = len(result) - current_item = 1; - - for item in result: - id=str(item.get("Id")).encode('utf-8') - type=item.get("Type").encode('utf-8') - - - if(item.get("Name") != None): - tempTitle = item.get("Name") - tempTitle=tempTitle.encode('utf-8') - else: - tempTitle = "Missing Title" - - if type=="ChannelFolderItem": - isFolder = True - else: - isFolder = False - item_type = str(type).encode('utf-8') - - if(item.get("ChannelId") != None): - channelId = str(item.get("ChannelId")).encode('utf-8') - - channelName = '' - if(item.get("ChannelName") != None): - channelName = item.get("ChannelName").encode('utf-8') - - if(item.get("PremiereDate") != None): - premieredatelist = (item.get("PremiereDate")).split("T") - premieredate = premieredatelist[0] - else: - premieredate = "" - - #mediaStreams=API().getMediaStreams(item, True) - - #people = API().getPeople(item) - - # Process Genres - genre = API().getGenre(item) - - # Process UserData - userData = item.get("UserData") - PlaybackPositionTicks = '100' - overlay = "0" - favorite = "False" - seekTime = 0 - if(userData != None): - if userData.get("Played") != True: - overlay = "7" - watched = "true" - else: - overlay = "6" - watched = "false" - if userData.get("IsFavorite") == True: - overlay = "5" - favorite = "True" - else: - favorite = "False" - if userData.get("PlaybackPositionTicks") != None: - PlaybackPositionTicks = str(userData.get("PlaybackPositionTicks")) - reasonableTicks = int(userData.get("PlaybackPositionTicks")) / 1000 - seekTime = reasonableTicks / 10000 - - playCount = 0 - if(userData != None and userData.get("Played") == True): - playCount = 1 - # Populate the details list - details={'title' : tempTitle, - 'channelname' : channelName, - 'plot' : item.get("Overview"), - 'Overlay' : overlay, - 'playcount' : str(playCount)} - - if item.get("Type") == "ChannelVideoItem": - xbmcplugin.setContent(_addon_id, 'movies') - elif item.get("Type") == "ChannelAudioItem": - xbmcplugin.setContent(_addon_id, 'songs') - - # Populate the extraData list - extraData={'thumb' : API().getArtwork(item, "Primary") , - 'fanart_image' : API().getArtwork(item, "Backdrop") , - 'poster' : API().getArtwork(item, "poster") , - 'tvshow.poster': API().getArtwork(item, "tvshow.poster") , - 'banner' : API().getArtwork(item, "Banner") , - 'clearlogo' : API().getArtwork(item, "Logo") , - 'discart' : API().getArtwork(item, "Disc") , - 'clearart' : API().getArtwork(item, "Art") , - 'landscape' : API().getArtwork(item, "Thumb") , - 'id' : id , - 'rating' : item.get("CommunityRating"), - 'year' : item.get("ProductionYear"), - 'premieredate' : premieredate, - 'genre' : genre, - 'playcount' : str(playCount), - 'itemtype' : item_type} - - if extraData['thumb'] == '': - extraData['thumb'] = extraData['fanart_image'] - - liz = xbmcgui.ListItem(tempTitle) - - artTypes=['poster', 'tvshow.poster', 'fanart_image', 'clearlogo', 'discart', 'banner', 'clearart', 'landscape', 'small_poster', 'tiny_poster', 'medium_poster','small_fanartimage', 'medium_fanartimage', 'medium_landscape', 'fanart_noindicators'] - - for artType in artTypes: - imagePath=str(extraData.get(artType,'')) - liz=PlaybackUtils().setArt(liz,artType, imagePath) - - liz.setThumbnailImage(API().getArtwork(item, "Primary")) - liz.setIconImage('DefaultTVShows.png') - #liz.setInfo( type="Video", infoLabels={ "Rating": item.get("CommunityRating") }) - #liz.setInfo( type="Video", infoLabels={ "Plot": item.get("Overview") }) - - if type=="Channel": - file = _addon_url + "?id=%s&mode=channels"%id - xbmcplugin.addDirectoryItem(handle=_addon_id, url=file, listitem=liz, isFolder=True) - - elif isFolder == True: - file = _addon_url + "?id=%s&mode=channelsfolder&folderid=%s" %(channelId, id) - xbmcplugin.addDirectoryItem(handle=_addon_id, url=file, listitem=liz, isFolder=True) - else: - file = _addon_url + "?id=%s&mode=play"%id - liz.setProperty('IsPlayable', 'true') - xbmcplugin.addDirectoryItem(handle=_addon_id, url=file, listitem=liz) - - xbmcplugin.endOfDirectory(handle=int(sys.argv[1])) - -##### GET NEXTUP EPISODES FOR TAGNAME ##### -def getNextUpEpisodes(tagname,limit): - count=0 - - #if the addon is called with nextup parameter, we return the nextepisodes list of the given tagname - xbmcplugin.setContent(int(sys.argv[1]), 'episodes') - # First we get a list of all the in-progress TV shows - filtered by tag - json_query_string = xbmc.executeJSONRPC('{"jsonrpc": "2.0", "method": "VideoLibrary.GetTVShows", "params": { "sort": { "order": "descending", "method": "lastplayed" }, "filter": {"and": [{"operator":"true", "field":"inprogress", "value":""}, {"operator": "is", "field": "tag", "value": "%s"}]}, "properties": [ "title", "studio", "mpaa", "file", "art" ] }, "id": "libTvShows"}' %tagname) - - json_result = json.loads(json_query_string) - # If we found any, find the oldest unwatched show for each one. - if json_result.has_key('result') and json_result['result'].has_key('tvshows'): - for item in json_result['result']['tvshows']: - - # If Ignore Specials is true only choose episodes from seasons greater than 0. - if utils.settings("ignoreSpecialsNextEpisodes")=="true": - json_query2 = xbmc.executeJSONRPC('{"jsonrpc": "2.0", "method": "VideoLibrary.GetEpisodes", "params": { "tvshowid": %d, "sort": {"method":"episode"}, "filter": {"and": [ {"field": "playcount", "operator": "lessthan", "value":"1"}, {"field": "season", "operator": "greaterthan", "value": "0"} ]}, "properties": [ "title", "playcount", "season", "episode", "showtitle", "plot", "file", "rating", "resume", "tvshowid", "art", "streamdetails", "firstaired", "runtime", "writer", "dateadded", "lastplayed" ], "limits":{"end":1}}, "id": "1"}' %item['tvshowid']) - else: - json_query2 = xbmc.executeJSONRPC('{"jsonrpc": "2.0", "method": "VideoLibrary.GetEpisodes", "params": { "tvshowid": %d, "sort": {"method":"episode"}, "filter": {"field": "playcount", "operator": "lessthan", "value":"1"}, "properties": [ "title", "playcount", "season", "episode", "showtitle", "plot", "file", "rating", "resume", "tvshowid", "art", "streamdetails", "firstaired", "runtime", "writer", "dateadded", "lastplayed" ], "limits":{"end":1}}, "id": "1"}' %item['tvshowid']) - - if json_query2: - json_query2 = json.loads(json_query2) - if json_query2.has_key('result') and json_query2['result'].has_key('episodes'): - for item in json_query2['result']['episodes']: - liz = createListItem(item) - xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=item['file'], listitem=liz) - count +=1 - if count == limit: - break - xbmcplugin.endOfDirectory(handle=int(sys.argv[1])) - -def getInProgressEpisodes(tagname,limit): - count = 0 - #if the addon is called with inprogressepisodes parameter, we return the inprogressepisodes list of the given tagname - xbmcplugin.setContent(int(sys.argv[1]), 'episodes') - # First we get a list of all the in-progress TV shows - filtered by tag - json_query_string = xbmc.executeJSONRPC('{"jsonrpc": "2.0", "method": "VideoLibrary.GetTVShows", "params": { "sort": { "order": "descending", "method": "lastplayed" }, "filter": {"and": [{"operator":"true", "field":"inprogress", "value":""}, {"operator": "contains", "field": "tag", "value": "%s"}]}, "properties": [ "title", "studio", "mpaa", "file", "art" ] }, "id": "libTvShows"}' %tagname) - json_result = json.loads(json_query_string) - # If we found any, find all in progress episodes for each one. - if json_result.has_key('result') and json_result['result'].has_key('tvshows'): - for item in json_result['result']['tvshows']: - json_query2 = xbmc.executeJSONRPC('{"jsonrpc": "2.0", "method": "VideoLibrary.GetEpisodes", "params": { "tvshowid": %d, "sort": {"method":"episode"}, "filter": {"field": "inprogress", "operator": "true", "value":""}, "properties": [ "title", "playcount", "season", "episode", "showtitle", "plot", "file", "rating", "resume", "tvshowid", "art", "cast", "streamdetails", "firstaired", "runtime", "writer", "dateadded", "lastplayed" ]}, "id": "1"}' %item['tvshowid']) - - if json_query2: - json_query2 = json.loads(json_query2) - if json_query2.has_key('result') and json_query2['result'].has_key('episodes'): - for item in json_query2['result']['episodes']: - liz = createListItem(item) - xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=item['file'], listitem=liz) - count +=1 - if count == limit: - break - xbmcplugin.endOfDirectory(handle=int(sys.argv[1])) - -def getRecentEpisodes(tagname,limit): - #if the addon is called with recentepisodes parameter, we return the recentepisodes list of the given tagname - xbmcplugin.setContent(int(sys.argv[1]), 'episodes') - # First we get a list of all the TV shows - filtered by tag - json_query_string = xbmc.executeJSONRPC('{"jsonrpc": "2.0", "method": "VideoLibrary.GetTVShows", "params": { "sort": { "order": "descending", "method": "dateadded" }, "properties": [ "title","sorttitle" ], "filter": {"operator": "contains", "field": "tag", "value": "%s"} }, "id": "libTvShows"}' %tagname) - json_result = json.loads(json_query_string) - - # If we found any, put all tv show id's in a list - if json_result.has_key('result') and json_result['result'].has_key('tvshows'): - alltvshowIds = list() - for tvshow in json_result['result']['tvshows']: - alltvshowIds.append(tvshow["tvshowid"]) - alltvshowIds = set(alltvshowIds) - - #get all recently added episodes - json_query2 = xbmc.executeJSONRPC('{"jsonrpc": "2.0", "method": "VideoLibrary.GetEpisodes", "params": { "sort": {"order": "descending", "method": "dateadded"}, "filter": {"field": "playcount", "operator": "lessthan", "value":"1"}, "properties": [ "title", "playcount", "season", "episode", "showtitle", "plot", "file", "rating", "resume", "tvshowid", "art", "streamdetails", "firstaired", "runtime", "cast", "writer", "dateadded", "lastplayed" ]}, "limits":{"end":%d}, "id": "1"}' %limit) - count = 0 - if json_query2: - json_query2 = json.loads(json_query2) - if json_query2.has_key('result') and json_query2['result'].has_key('episodes'): - for item in json_query2['result']['episodes']: - if item["tvshowid"] in alltvshowIds: - liz = createListItem(item) - xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=item['file'], listitem=liz) - count += 1 - if count == limit: - break - xbmcplugin.endOfDirectory(handle=int(sys.argv[1])) - -def createListItem(item): - - liz = xbmcgui.ListItem(item['title']) - liz.setInfo( type="Video", infoLabels={ "Title": item['title'] }) - liz.setProperty('IsPlayable', 'true') - liz.setInfo( type="Video", infoLabels={ "duration": str(item['runtime']/60) }) - - if "episode" in item: - episode = "%.2d" % float(item['episode']) - liz.setInfo( type="Video", infoLabels={ "Episode": item['episode'] }) - - if "season" in item: - season = "%.2d" % float(item['season']) - liz.setInfo( type="Video", infoLabels={ "Season": item['season'] }) - - if season and episode: - episodeno = "s%se%s" %(season,episode) - liz.setProperty("episodeno", episodeno) - - if "firstaired" in item: - liz.setInfo( type="Video", infoLabels={ "Premiered": item['firstaired'] }) - - plot = item['plot'] - liz.setInfo( type="Video", infoLabels={ "Plot": plot }) - - if "showtitle" in item: - liz.setInfo( type="Video", infoLabels={ "TVshowTitle": item['showtitle'] }) - - if "rating" in item: - liz.setInfo( type="Video", infoLabels={ "Rating": str(round(float(item['rating']),1)) }) - liz.setInfo( type="Video", infoLabels={ "Playcount": item['playcount'] }) - if "director" in item: - liz.setInfo( type="Video", infoLabels={ "Director": " / ".join(item['director']) }) - if "writer" in item: - liz.setInfo( type="Video", infoLabels={ "Writer": " / ".join(item['writer']) }) - - if "cast" in item: - listCast = [] - listCastAndRole = [] - for castmember in item["cast"]: - listCast.append( castmember["name"] ) - listCastAndRole.append( (castmember["name"], castmember["role"]) ) - cast = [listCast, listCastAndRole] - liz.setInfo( type="Video", infoLabels={ "Cast": cast[0] }) - liz.setInfo( type="Video", infoLabels={ "CastAndRole": cast[1] }) - - liz.setProperty("resumetime", str(item['resume']['position'])) - liz.setProperty("totaltime", str(item['resume']['total'])) - liz.setArt(item['art']) - liz.setThumbnailImage(item['art'].get('thumb','')) - liz.setIconImage('DefaultTVShows.png') - liz.setProperty("dbid", str(item['episodeid'])) - liz.setProperty("fanart_image", item['art'].get('tvshow.fanart','')) - for key, value in item['streamdetails'].iteritems(): - for stream in value: - liz.addStreamInfo( key, stream ) - - return liz - -##### GET EXTRAFANART FOR LISTITEM ##### -def getExtraFanArt(): - itemPath = "" - embyId = "" - - #get extrafanart for listitem - this will only be used for skins that actually call the listitem's path + fanart dir... - try: - #only do this if the listitem has actually changed - itemPath = xbmc.getInfoLabel("ListItem.FileNameAndPath") - - if not itemPath: - itemPath = xbmc.getInfoLabel("ListItem.Path") - - if ("/tvshows/" in itemPath or "/musicvideos/" in itemPath or "/movies/" in itemPath): - embyId = itemPath.split("/")[-2] - - utils.logMsg("%s %s" % ("Emby addon", "getExtraFanArt"), "requesting extraFanArt for Id: " + embyId, 1) - - #we need to store the images locally for this to work because of the caching system in xbmc - fanartDir = xbmc.translatePath("special://thumbnails/emby/" + embyId + "/") - - if not xbmcvfs.exists(fanartDir): - #download the images to the cache directory - xbmcvfs.mkdir(fanartDir) - item = ReadEmbyDB().getFullItem(embyId) - if item != None: - if item.has_key("BackdropImageTags"): - if(len(item["BackdropImageTags"]) > 0): - WINDOW = xbmcgui.Window(10000) - username = WINDOW.getProperty('currUser') - server = WINDOW.getProperty('server%s' % username) - totalbackdrops = len(item["BackdropImageTags"]) - count = 0 - for backdrop in item["BackdropImageTags"]: - backgroundUrl = "%s/mediabrowser/Items/%s/Images/Backdrop/%s/?MaxWidth=10000&MaxHeight=10000&Format=original&Tag=%s&EnableImageEnhancers=false" % (server, embyId, str(count), backdrop) - count += 1 - fanartFile = os.path.join(fanartDir,"fanart" + backdrop + ".jpg") - li = xbmcgui.ListItem(backdrop, path=fanartFile) - xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=fanartFile, listitem=li) - xbmcvfs.copy(backgroundUrl,fanartFile) - - else: - #use existing cached images - dirs, files = xbmcvfs.listdir(fanartDir) - count = 1 - for file in files: - count +=1 - li = xbmcgui.ListItem(file, path=os.path.join(fanartDir,file)) - xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=os.path.join(fanartDir,file), listitem=li) - except Exception as e: - utils.logMsg("%s %s" % ("Emby addon", "Error in getExtraFanArt"), str(e), 1) - pass - - #always do endofdirectory to prevent errors in the logs - xbmcplugin.endOfDirectory(int(sys.argv[1])) - -def addDirectoryItem(label, path, folder=True): - li = xbmcgui.ListItem(label, path=path) - li.setThumbnailImage("special://home/addons/plugin.video.emby/icon.png") - li.setArt({"fanart":"special://home/addons/plugin.video.emby/fanart.jpg"}) - li.setArt({"landscape":"special://home/addons/plugin.video.emby/fanart.jpg"}) - xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=path, listitem=li, isFolder=folder) - -# if the addon is called without parameters we show the listing... -def doMainListing(): - - xbmcplugin.setContent(int(sys.argv[1]), 'files') - #get emby nodes from the window props - embyProperty = WINDOW.getProperty("Emby.nodes.total") - if embyProperty: - totalNodes = int(embyProperty) - for i in range(totalNodes): - path = WINDOW.getProperty("Emby.nodes.%s.index" %str(i)) - if not path: - path = WINDOW.getProperty("Emby.nodes.%s.content" %str(i)) - label = WINDOW.getProperty("Emby.nodes.%s.title" %str(i)) - if path: - addDirectoryItem(label, path) - - # some extra entries for settings and stuff. TODO --> localize the labels - addDirectoryItem("Settings", "plugin://plugin.video.emby/?mode=settings") - addDirectoryItem("Perform manual sync", "plugin://plugin.video.emby/?mode=manualsync") - addDirectoryItem("Add user to session", "plugin://plugin.video.emby/?mode=adduser") - addDirectoryItem("Configure user preferences", "plugin://plugin.video.emby/?mode=userprefs") - addDirectoryItem("Perform local database reset (full resync)", "plugin://plugin.video.emby/?mode=reset") - addDirectoryItem("Cache all images to Kodi texture cache (advanced)", "plugin://plugin.video.emby/?mode=texturecache") - addDirectoryItem("Sync Emby Theme Media to Kodi", "plugin://plugin.video.emby/?mode=thememedia") - - xbmcplugin.endOfDirectory(int(sys.argv[1])) \ No newline at end of file diff --git a/resources/lib/KodiMonitor.py b/resources/lib/KodiMonitor.py deleted file mode 100644 index 2d4dc943..00000000 --- a/resources/lib/KodiMonitor.py +++ /dev/null @@ -1,150 +0,0 @@ -################################################################################################# -# Kodi Monitor -# Watched events that occur in Kodi, like setting media watched -################################################################################################# - -import xbmc -import xbmcgui -import xbmcaddon -import json - -import Utils as utils -from WriteKodiVideoDB import WriteKodiVideoDB -from ReadKodiDB import ReadKodiDB -from PlayUtils import PlayUtils -from DownloadUtils import DownloadUtils -from PlaybackUtils import PlaybackUtils - - -class Kodi_Monitor( xbmc.Monitor ): - - WINDOW = xbmcgui.Window(10000) - - def __init__(self, *args, **kwargs): - xbmc.Monitor.__init__(self) - - def logMsg(self, msg, lvl = 1): - - className = self.__class__.__name__ - utils.logMsg("%s %s" % ("EMBY", className), msg, int(lvl)) - - def onScanStarted(self, library): - utils.window('kodiScan', value="true") - self.logMsg("Kodi library scan running.", 2) - - def onScanFinished(self, library): - utils.window('kodiScan', clear=True) - self.logMsg("Kodi library scan finished.", 2) - - #this library monitor is used to detect a watchedstate change by the user through the library - #as well as detect when a library item has been deleted to pass the delete to the Emby server - def onNotification (self, sender, method, data): - - WINDOW = self.WINDOW - downloadUtils = DownloadUtils() - #player started playing an item - - if ("Playlist.OnAdd" in method or "Player.OnPlay" in method): - - jsondata = json.loads(data) - if jsondata: - if jsondata.has_key("item"): - if jsondata.get("item").has_key("id") and jsondata.get("item").has_key("type"): - id = jsondata.get("item").get("id") - type = jsondata.get("item").get("type") - - if (utils.settings('useDirectPaths')=='true' and not type == "song") or (type == "song" and utils.settings('enableMusicSync') == "true"): - - if type == "song": - connection = utils.KodiSQL('music') - cursor = connection.cursor() - embyid = ReadKodiDB().getEmbyIdByKodiId(id, type, connection, cursor) - cursor.close() - else: - embyid = ReadKodiDB().getEmbyIdByKodiId(id,type) - - if embyid: - - url = "{server}/mediabrowser/Users/{UserId}/Items/%s?format=json" % embyid - result = downloadUtils.downloadUrl(url) - self.logMsg("Result: %s" % result, 2) - - playurl = None - count = 0 - while not playurl and count < 2: - try: - playurl = xbmc.Player().getPlayingFile() - except RuntimeError: - xbmc.sleep(200) - else: - listItem = xbmcgui.ListItem() - PlaybackUtils().setProperties(playurl, result, listItem) - - if type == "song" and utils.settings('directstreammusic') == "true": - utils.window('%splaymethod' % playurl, value="DirectStream") - else: - utils.window('%splaymethod' % playurl, value="DirectPlay") - - count += 1 - - if method == "VideoLibrary.OnUpdate": - # Triggers 4 times, the following is only for manually marking as watched/unwatched - jsondata = json.loads(data) - - try: - playcount = jsondata.get('playcount') - item = jsondata['item']['id'] - type = jsondata['item']['type'] - prop = utils.window('Played%s%s' % (type, item)) - except: - self.logMsg("Could not process VideoLibrary.OnUpdate data.", 1) - else: - self.logMsg("VideoLibrary.OnUpdate: %s" % data, 2) - if prop != "true": - # Set property to prevent the multi triggering - utils.window('Played%s%s' % (type, item), "true") - WriteKodiVideoDB().updatePlayCountFromKodi(item, type, playcount) - - self.clearProperty(type, item) - - if method == "System.OnWake": - xbmc.sleep(10000) #Allow network to wake up - WINDOW.setProperty("OnWakeSync", "true") - - if method == "VideoLibrary.OnRemove": - xbmc.log('Intercepted remove from sender: ' + sender + ' method: ' + method + ' data: ' + data) - jsondata = json.loads(data) - id = ReadKodiDB().getEmbyIdByKodiId(jsondata.get("id"), jsondata.get("type")) - if id == None: - return - xbmc.log("Deleting Emby ID: " + id + " from database") - connection = utils.KodiSQL() - cursor = connection.cursor() - cursor.execute("DELETE FROM emby WHERE emby_id = ?", (id,)) - connection.commit() - cursor.close - - if jsondata: - if jsondata.get("type") == "episode" or "movie": - url='{server}/mediabrowser/Items?Ids=' + id + '&format=json' - #This is a check to see if the item exists on the server, if it doesn't it may have already been deleted by another client - result = DownloadUtils().downloadUrl(url) - item = result.get("Items")[0] - if data: - return_value = xbmcgui.Dialog().yesno("Confirm Delete", "Delete file on Emby Server?") - if return_value: - url='{server}/mediabrowser/Items/' + id - xbmc.log('Deleting via URL: ' + url) - DownloadUtils().downloadUrl(url, type="DELETE") - - elif method == "Playlist.OnClear": - self.logMsg("Clear playback properties.", 2) - utils.window('propertiesPlayback', clear=True) - - def clearProperty(self, type, id): - # The sleep is necessary since VideoLibrary.OnUpdate - # triggers 4 times in a row. - xbmc.sleep(100) - utils.window('Played%s%s' % (type,id), clear=True) - - # Clear the widget cache - utils.window('clearwidgetcache', value="clear") \ No newline at end of file diff --git a/resources/lib/LibrarySync.py b/resources/lib/LibrarySync.py deleted file mode 100644 index f50c88e2..00000000 --- a/resources/lib/LibrarySync.py +++ /dev/null @@ -1,1191 +0,0 @@ -################################################################################################# -# LibrarySync -################################################################################################# - -import xbmc -import xbmcgui -import xbmcaddon -import xbmcvfs -import json -import sqlite3 -import inspect -import threading -import urllib -from datetime import datetime, timedelta, time -from itertools import chain -import urllib2 -import os - -import KodiMonitor -from API import API -import Utils as utils -from ClientInformation import ClientInformation -from DownloadUtils import DownloadUtils -from ReadEmbyDB import ReadEmbyDB -from ReadKodiDB import ReadKodiDB -from WriteKodiVideoDB import WriteKodiVideoDB -from WriteKodiMusicDB import WriteKodiMusicDB -from VideoNodes import VideoNodes - -addondir = xbmc.translatePath(xbmcaddon.Addon(id='plugin.video.emby').getAddonInfo('profile')) -dataPath = os.path.join(addondir,"library") -movieLibrary = os.path.join(dataPath,'movies') -tvLibrary = os.path.join(dataPath,'tvshows') - -WINDOW = xbmcgui.Window( 10000 ) - -class LibrarySync(threading.Thread): - - _shared_state = {} - - KodiMonitor = KodiMonitor.Kodi_Monitor() - clientInfo = ClientInformation() - - addonName = clientInfo.getAddonName() - - updateItems = [] - userdataItems = [] - removeItems = [] - forceUpdate = False - - def __init__(self, *args): - - self.__dict__ = self._shared_state - threading.Thread.__init__(self, *args) - - def logMsg(self, msg, lvl=1): - - className = self.__class__.__name__ - utils.logMsg("%s %s" % (self.addonName, className), msg, int(lvl)) - - def FullLibrarySync(self,manualRun=False): - - startupDone = WINDOW.getProperty("startup") == "done" - syncInstallRunDone = utils.settings("SyncInstallRunDone") == "true" - performMusicSync = utils.settings("enableMusicSync") == "true" - dbSyncIndication = utils.settings("dbSyncIndication") == "true" - - ### BUILD VIDEO NODES LISTING ### - VideoNodes().buildVideoNodesListing() - ### CREATE SOURCES ### - if utils.settings("Sources") != "true": - # Only create sources once - self.logMsg("Sources.xml created.", 0) - utils.createSources() - utils.settings("Sources", "true") - - # just do a incremental sync if that is what is required - if(utils.settings("useIncSync") == "true" and utils.settings("SyncInstallRunDone") == "true") and manualRun == False: - utils.logMsg("Sync Database", "Using incremental sync instead of full sync useIncSync=True)", 0) - - du = DownloadUtils() - - lastSync = utils.settings("LastIncrenetalSync") - if(lastSync == None or len(lastSync) == 0): - lastSync = "2010-01-01T00:00:00Z" - utils.logMsg("Sync Database", "Incremental Sync Setting Last Run Time Loaded : " + lastSync, 0) - - lastSync = urllib2.quote(lastSync) - - url = "{server}/Emby.Kodi.SyncQueue/{UserId}/GetItems?LastUpdateDT=" + lastSync + "&format=json" - utils.logMsg("Sync Database", "Incremental Sync Get Items URL : " + url, 0) - - try: - results = du.downloadUrl(url) - changedItems = results["ItemsUpdated"] + results["ItemsAdded"] - removedItems = results["ItemsRemoved"] - userChanges = results["UserDataChanged"] - except: - utils.logMsg("Sync Database", "Incremental Sync Get Changes Failed", 0) - pass - else: - maxItems = int(utils.settings("incSyncMaxItems")) - utils.logMsg("Sync Database", "Incremental Sync Changes : " + str(results), 0) - if(len(changedItems) < maxItems and len(removedItems) < maxItems and len(userChanges) < maxItems): - - WINDOW.setProperty("startup", "done") - - LibrarySync().remove_items(removedItems) - LibrarySync().update_items(changedItems) - LibrarySync().user_data_update(userChanges) - - return True - else: - utils.logMsg("Sync Database", "Too Many For Incremental Sync (" + str(maxItems) + "), changedItems" + str(len(changedItems)) + " removedItems:" + str(len(removedItems)) + " userChanges:" + str(len(userChanges)), 0) - - #set some variable to check if this is the first run - WINDOW.setProperty("SyncDatabaseRunning", "true") - - #show the progress dialog - pDialog = None - if (syncInstallRunDone == False or dbSyncIndication or manualRun): - pDialog = xbmcgui.DialogProgressBG() - pDialog.create('Emby for Kodi', 'Performing full sync') - - if(WINDOW.getProperty("SyncDatabaseShouldStop") == "true"): - utils.logMsg("Sync Database", "Can not start SyncDatabaseShouldStop=True", 0) - return True - - try: - completed = True - - ### PROCESS VIDEO LIBRARY ### - - #create the sql connection to video db - connection = utils.KodiSQL("video") - cursor = connection.cursor() - - #Add the special emby table - cursor.execute("CREATE TABLE IF NOT EXISTS emby(emby_id TEXT, kodi_id INTEGER, media_type TEXT, checksum TEXT, parent_id INTEGER, kodi_file_id INTEGER)") - try: - cursor.execute("ALTER TABLE emby ADD COLUMN kodi_file_id INTEGER") - except: pass - self.dbCommit(connection) - - # sync movies - self.MoviesFullSync(connection,cursor,pDialog) - - if (self.ShouldStop()): - return False - - #sync Tvshows and episodes - self.TvShowsFullSync(connection,cursor,pDialog) - - if (self.ShouldStop()): - return False - - # sync musicvideos - self.MusicVideosFullSync(connection,cursor,pDialog) - - #close sql connection - cursor.close() - - ### PROCESS MUSIC LIBRARY ### - if performMusicSync: - #create the sql connection to music db - connection = utils.KodiSQL("music") - cursor = connection.cursor() - - #Add the special emby table - cursor.execute("CREATE TABLE IF NOT EXISTS emby(emby_id TEXT, kodi_id INTEGER, media_type TEXT, checksum TEXT, parent_id INTEGER, kodi_file_id INTEGER)") - try: - cursor.execute("ALTER TABLE emby ADD COLUMN kodi_file_id INTEGER") - except: pass - self.dbCommit(connection) - - self.MusicFullSync(connection,cursor,pDialog) - cursor.close() - - # set the install done setting - if(syncInstallRunDone == False and completed): - utils.settings("SyncInstallRunDone", "true") - utils.settings("dbCreatedWithVersion", self.clientInfo.getVersion()) - - # Commit all DB changes at once and Force refresh the library - #xbmc.executebuiltin("UpdateLibrary(video)") - #self.updateLibrary("video") - #xbmc.executebuiltin("UpdateLibrary(music)") - - # set prop to show we have run for the first time - WINDOW.setProperty("startup", "done") - - # tell any widgets to refresh because the content has changed - WINDOW.setProperty("widgetreload", datetime.now().strftime('%Y-%m-%d %H:%M:%S')) - - self.SaveLastSync() - - finally: - WINDOW.setProperty("SyncDatabaseRunning", "false") - utils.logMsg("Sync DB", "syncDatabase Exiting", 0) - - if(pDialog != None): - pDialog.close() - - return True - - def SaveLastSync(self): - # save last sync time - - du = DownloadUtils() - url = "{server}/Emby.Kodi.SyncQueue/GetServerDateTime?format=json" - - try: - results = du.downloadUrl(url) - lastSync = results["ServerDateTime"] - self.logMsg("Sync Database, Incremental Sync Using Server Time: %s" % lastSync, 0) - lastSync = datetime.strptime(lastSync, "%Y-%m-%dT%H:%M:%SZ") - lastSync = (lastSync - timedelta(minutes=5)).strftime('%Y-%m-%dT%H:%M:%SZ') - self.logMsg("Sync Database, Incremental Sync Using Server Time -5 min: %s" % lastSync, 0) - except: - lastSync = (datetime.utcnow() - timedelta(minutes=5)).strftime('%Y-%m-%dT%H:%M:%SZ') - self.logMsg("Sync Database, Incremental Sync Using Client Time -5 min: %s" % lastSync, 0) - - self.logMsg("Sync Database, Incremental Sync Setting Last Run Time Saved: %s" % lastSync, 0) - utils.settings("LastIncrenetalSync", lastSync) - - def MoviesFullSync(self,connection, cursor, pDialog): - - views = ReadEmbyDB().getCollections("movies") - - allKodiMovieIds = list() - allEmbyMovieIds = list() - - for view in views: - - allEmbyMovies = ReadEmbyDB().getMovies(view.get('id')) - allKodiMovies = ReadKodiDB().getKodiMovies(connection, cursor) - - for kodimovie in allKodiMovies: - allKodiMovieIds.append(kodimovie[1]) - - title = view.get('title') - content = view.get('content') - - if content == "mixed": - title = "%s - Movies" % title - - for kodimovie in allKodiMovies: - allKodiMovieIds.append(kodimovie[1]) - - total = len(allEmbyMovies) + 1 - count = 1 - - #### PROCESS ADDS AND UPDATES ### - for item in allEmbyMovies: - - if (self.ShouldStop()): - return False - - if not item.get('IsFolder'): - allEmbyMovieIds.append(item["Id"]) - - if(pDialog != None): - progressTitle = "Processing " + view.get('title') + " (" + str(count) + " of " + str(total) + ")" - percentage = int(((float(count) / float(total)) * 100)) - pDialog.update(percentage, "Emby for Kodi - Running Sync", progressTitle) - count += 1 - - kodiMovie = None - for kodimovie in allKodiMovies: - if kodimovie[1] == item["Id"]: - kodiMovie = kodimovie - - if kodiMovie == None: - WriteKodiVideoDB().addOrUpdateMovieToKodiLibrary(item["Id"],connection, cursor, title) - else: - if kodiMovie[2] != API().getChecksum(item): - WriteKodiVideoDB().addOrUpdateMovieToKodiLibrary(item["Id"],connection, cursor, title) - - - - #### PROCESS BOX SETS ##### - utils.logMsg("Sync Movies", "BoxSet Sync Started", 1) - boxsets = ReadEmbyDB().getBoxSets() - - total = len(boxsets) + 1 - count = 1 - for boxset in boxsets: - if(pDialog != None): - progressTitle = "Processing BoxSets" + " (" + str(count) + " of " + str(total-1) + ")" - percentage = int(((float(count) / float(total)) * 100)) - pDialog.update(percentage, "Emby for Kodi - Running Sync", progressTitle) - count += 1 - if(self.ShouldStop()): - return False - boxsetMovies = ReadEmbyDB().getMoviesInBoxSet(boxset["Id"]) - WriteKodiVideoDB().addBoxsetToKodiLibrary(boxset, connection, cursor) - - WriteKodiVideoDB().removeMoviesFromBoxset(boxset, connection, cursor) - for boxsetMovie in boxsetMovies: - if(self.ShouldStop()): - return False - WriteKodiVideoDB().updateBoxsetToKodiLibrary(boxsetMovie,boxset, connection, cursor) - - utils.logMsg("Sync Movies", "BoxSet Sync Finished", 1) - - #### PROCESS DELETES ##### - allEmbyMovieIds = set(allEmbyMovieIds) - for kodiId in allKodiMovieIds: - if not kodiId in allEmbyMovieIds: - WINDOW.setProperty(kodiId,"deleted") - WriteKodiVideoDB().deleteItemFromKodiLibrary(kodiId, connection, cursor) - - ### commit all changes to database ### - self.dbCommit(connection) - - def MusicVideosFullSync(self,connection,cursor, pDialog): - - allKodiMusicvideoIds = list() - allEmbyMusicvideoIds = list() - - allEmbyMusicvideos = ReadEmbyDB().getMusicVideos() - allKodiMusicvideos = ReadKodiDB().getKodiMusicVideos(connection, cursor) - - for kodivideo in allKodiMusicvideos: - allKodiMusicvideoIds.append(kodivideo[1]) - - total = len(allEmbyMusicvideos) + 1 - count = 1 - - #### PROCESS ADDS AND UPDATES ### - for item in allEmbyMusicvideos: - - if (self.ShouldStop()): - return False - - if not item.get('IsFolder'): - allEmbyMusicvideoIds.append(item["Id"]) - - if(pDialog != None): - progressTitle = "Processing MusicVideos (" + str(count) + " of " + str(total) + ")" - percentage = int(((float(count) / float(total)) * 100)) - pDialog.update(percentage, "Emby for Kodi - Running Sync", progressTitle) - count += 1 - - kodiVideo = None - for kodivideo in allKodiMusicvideos: - if kodivideo[1] == item["Id"]: - kodiVideo = kodivideo - - if kodiVideo == None: - WriteKodiVideoDB().addOrUpdateMusicVideoToKodiLibrary(item["Id"],connection, cursor) - else: - if kodiVideo[2] != API().getChecksum(item): - WriteKodiVideoDB().addOrUpdateMusicVideoToKodiLibrary(item["Id"],connection, cursor) - - #### PROCESS DELETES ##### - allEmbyMusicvideoIds = set(allEmbyMusicvideoIds) - for kodiId in allKodiMusicvideoIds: - if not kodiId in allEmbyMusicvideoIds: - WINDOW.setProperty(kodiId,"deleted") - WriteKodiVideoDB().deleteItemFromKodiLibrary(kodiId, connection, cursor) - - ### commit all changes to database ### - self.dbCommit(connection) - - def TvShowsFullSync(self,connection,cursor,pDialog): - - views = ReadEmbyDB().getCollections("tvshows") - - allKodiTvShowIds = list() - allEmbyTvShowIds = list() - - for view in views: - - allEmbyTvShows = ReadEmbyDB().getTvShows(view.get('id')) - allKodiTvShows = ReadKodiDB().getKodiTvShows(connection, cursor) - - title = view.get('title') - content = view.get('content') - - if content == "mixed": - title = "%s - TV Shows" % title - - total = len(allEmbyTvShows) + 1 - count = 1 - - for kodishow in allKodiTvShows: - allKodiTvShowIds.append(kodishow[1]) - - #### TVSHOW: PROCESS ADDS AND UPDATES ### - for item in allEmbyTvShows: - - if (self.ShouldStop()): - return False - - if(pDialog != None): - progressTitle = "Processing " + view.get('title') + " (" + str(count) + " of " + str(total) + ")" - percentage = int(((float(count) / float(total)) * 100)) - pDialog.update(percentage, "Emby for Kodi - Running Sync", progressTitle) - count += 1 - - if utils.settings('syncEmptyShows') == "true" or (item.get('IsFolder') and item.get('RecursiveItemCount') != 0): - allEmbyTvShowIds.append(item["Id"]) - - #build a list with all Id's and get the existing entry (if exists) in Kodi DB - kodiShow = None - for kodishow in allKodiTvShows: - if kodishow[1] == item["Id"]: - kodiShow = kodishow - - if kodiShow == None: - # Tv show doesn't exist in Kodi yet so proceed and add it - WriteKodiVideoDB().addOrUpdateTvShowToKodiLibrary(item["Id"],connection, cursor, title) - else: - # If there are changes to the item, perform a full sync of the item - if kodiShow[2] != API().getChecksum(item): - WriteKodiVideoDB().addOrUpdateTvShowToKodiLibrary(item["Id"],connection, cursor, title) - - #### PROCESS EPISODES ###### - self.EpisodesFullSync(connection,cursor,item["Id"]) - - #### TVSHOW: PROCESS DELETES ##### - allEmbyTvShowIds = set(allEmbyTvShowIds) - for kodiId in allKodiTvShowIds: - if not kodiId in allEmbyTvShowIds: - WINDOW.setProperty(kodiId,"deleted") - WriteKodiVideoDB().deleteItemFromKodiLibrary(kodiId, connection, cursor) - - ### commit all changes to database ### - self.dbCommit(connection) - - def EpisodesFullSync(self,connection,cursor,showId): - - WINDOW = xbmcgui.Window( 10000 ) - - allKodiEpisodeIds = list() - allEmbyEpisodeIds = list() - - # Get the kodi parent id - cursor.execute("SELECT kodi_id FROM emby WHERE emby_id=?",(showId,)) - try: - kodiShowId = cursor.fetchone()[0] - except: - self.logMsg("Unable to find show itemId:%s" % showId, 1) - return - - allEmbyEpisodes = ReadEmbyDB().getEpisodes(showId) - allKodiEpisodes = ReadKodiDB().getKodiEpisodes(connection, cursor, kodiShowId) - - for kodiepisode in allKodiEpisodes: - allKodiEpisodeIds.append(kodiepisode[1]) - - #### EPISODES: PROCESS ADDS AND UPDATES ### - for item in allEmbyEpisodes: - - if (self.ShouldStop()): - return False - - allEmbyEpisodeIds.append(item["Id"]) - - #get the existing entry (if exists) in Kodi DB - kodiEpisode = None - for kodiepisode in allKodiEpisodes: - if kodiepisode[1] == item["Id"]: - kodiEpisode = kodiepisode - - if kodiEpisode == None: - # Episode doesn't exist in Kodi yet so proceed and add it - WriteKodiVideoDB().addOrUpdateEpisodeToKodiLibrary(item["Id"], kodiShowId, connection, cursor) - else: - # If there are changes to the item, perform a full sync of the item - if kodiEpisode[2] != API().getChecksum(item): - WriteKodiVideoDB().addOrUpdateEpisodeToKodiLibrary(item["Id"], kodiShowId, connection, cursor) - - #### EPISODES: PROCESS DELETES ##### - allEmbyEpisodeIds = set(allEmbyEpisodeIds) - for kodiId in allKodiEpisodeIds: - if (not kodiId in allEmbyEpisodeIds): - WINDOW.setProperty(kodiId,"deleted") - WriteKodiVideoDB().deleteItemFromKodiLibrary(kodiId, connection, cursor) - - def MusicFullSync(self, connection,cursor, pDialog): - - self.ProcessMusicArtists(connection,cursor,pDialog) - self.dbCommit(connection) - self.ProcessMusicAlbums(connection,cursor,pDialog) - self.dbCommit(connection) - self.ProcessMusicSongs(connection,cursor,pDialog) - - ### commit all changes to database ### - self.dbCommit(connection) - - def ProcessMusicSongs(self,connection,cursor,pDialog): - - allKodiSongIds = list() - allEmbySongIds = list() - - allEmbySongs = ReadEmbyDB().getMusicSongsTotal() - allKodiSongs = ReadKodiDB().getKodiMusicSongs(connection, cursor) - - for kodisong in allKodiSongs: - allKodiSongIds.append(kodisong[1]) - - total = len(allEmbySongs) + 1 - count = 1 - - #### PROCESS SONGS ADDS AND UPDATES ### - for item in allEmbySongs: - - if (self.ShouldStop()): - return False - - allEmbySongIds.append(item["Id"]) - - if(pDialog != None): - progressTitle = "Processing Music Songs (" + str(count) + " of " + str(total) + ")" - percentage = int(((float(count) / float(total)) * 100)) - pDialog.update(percentage, "Emby for Kodi - Running Sync", progressTitle) - count += 1 - - kodiSong = None - for kodisong in allKodiSongs: - if kodisong[1] == item["Id"]: - kodiSong = kodisong - - if kodiSong == None: - WriteKodiMusicDB().addOrUpdateSongToKodiLibrary(item,connection, cursor) - else: - if kodiSong[2] != API().getChecksum(item): - WriteKodiMusicDB().addOrUpdateSongToKodiLibrary(item,connection, cursor) - - #### PROCESS DELETES ##### - allEmbySongIds = set(allEmbySongIds) - for kodiId in allKodiSongIds: - if not kodiId in allEmbySongIds: - WINDOW.setProperty(kodiId,"deleted") - WriteKodiMusicDB().deleteItemFromKodiLibrary(kodiId, connection, cursor) - - def ProcessMusicArtists(self,connection,cursor,pDialog): - - allKodiArtistIds = list() - allEmbyArtistIds = list() - - allEmbyArtists = ReadEmbyDB().getMusicArtistsTotal() - allKodiArtists = ReadKodiDB().getKodiMusicArtists(connection, cursor) - - for kodiartist in allKodiArtists: - allKodiArtistIds.append(kodiartist[1]) - - total = len(allEmbyArtists) + 1 - count = 1 - - #### PROCESS ARTIST ADDS AND UPDATES ### - for item in allEmbyArtists: - - if (self.ShouldStop()): - return False - - allEmbyArtistIds.append(item["Id"]) - - if(pDialog != None): - progressTitle = "Processing Music Artists (" + str(count) + " of " + str(total) + ")" - percentage = int(((float(count) / float(total)) * 100)) - pDialog.update(percentage, "Emby for Kodi - Running Sync", progressTitle) - count += 1 - - kodiArtist = None - for kodiartist in allKodiArtists: - if kodiartist[1] == item["Id"]: - kodiArtist = kodiartist - - if kodiArtist == None: - WriteKodiMusicDB().addOrUpdateArtistToKodiLibrary(item,connection, cursor) - else: - if kodiArtist[2] != API().getChecksum(item): - WriteKodiMusicDB().addOrUpdateArtistToKodiLibrary(item,connection, cursor) - - #### PROCESS DELETES ##### - allEmbyArtistIds = set(allEmbyArtistIds) - for kodiId in allKodiArtistIds: - if not kodiId in allEmbyArtistIds: - WINDOW.setProperty(kodiId,"deleted") - WriteKodiMusicDB().deleteItemFromKodiLibrary(kodiId, connection, cursor) - - def ProcessMusicAlbums(self,connection,cursor,pDialog): - - allKodiAlbumIds = list() - allEmbyAlbumIds = list() - - allEmbyAlbums = ReadEmbyDB().getMusicAlbumsTotal() - allKodiAlbums = ReadKodiDB().getKodiMusicAlbums(connection, cursor) - - for kodialbum in allKodiAlbums: - allKodiAlbumIds.append(kodialbum[1]) - - total = len(allEmbyAlbums) + 1 - count = 1 - - #### PROCESS SONGS ADDS AND UPDATES ### - for item in allEmbyAlbums: - - if (self.ShouldStop()): - return False - - allEmbyAlbumIds.append(item["Id"]) - - if(pDialog != None): - progressTitle = "Processing Music Albums (" + str(count) + " of " + str(total) + ")" - percentage = int(((float(count) / float(total)) * 100)) - pDialog.update(percentage, "Emby for Kodi - Running Sync", progressTitle) - count += 1 - - kodiAlbum = None - for kodialbum in allKodiAlbums: - if kodialbum[1] == item["Id"]: - kodiAlbum = kodialbum - - if kodiAlbum == None: - WriteKodiMusicDB().addOrUpdateAlbumToKodiLibrary(item,connection, cursor) - else: - if kodiAlbum[2] != API().getChecksum(item): - WriteKodiMusicDB().addOrUpdateAlbumToKodiLibrary(item,connection, cursor) - - #### PROCESS DELETES ##### - allEmbyAlbumIds = set(allEmbyAlbumIds) - for kodiId in allKodiAlbumIds: - if not kodiId in allEmbyAlbumIds: - WINDOW.setProperty(kodiId,"deleted") - WriteKodiMusicDB().deleteItemFromKodiLibrary(kodiId, connection, cursor) - - def IncrementalSync(self, itemList): - - startupDone = WINDOW.getProperty("startup") == "done" - - #only perform incremental scan when full scan is completed - if startupDone: - - #this will only perform sync for items received by the websocket - dbSyncIndication = utils.settings("dbSyncIndication") == "true" - performMusicSync = utils.settings("enableMusicSync") == "true" - WINDOW.setProperty("SyncDatabaseRunning", "true") - - #show the progress dialog - pDialog = None - if (dbSyncIndication and xbmc.Player().isPlaying() == False): - pDialog = xbmcgui.DialogProgressBG() - pDialog.create('Emby for Kodi', 'Incremental Sync') - self.logMsg("Doing LibraryChanged : Show Progress IncrementalSync()", 0); - - connection = utils.KodiSQL("video") - cursor = connection.cursor() - - try: - #### PROCESS MOVIES #### - views = ReadEmbyDB().getCollections("movies") - for view in views: - allEmbyMovies = ReadEmbyDB().getMovies(view.get('id'), itemList) - count = 1 - total = len(allEmbyMovies) + 1 - for item in allEmbyMovies: - if(pDialog != None): - progressTitle = "Incremental Sync "+ " (" + str(count) + " of " + str(total) + ")" - percentage = int(((float(count) / float(total)) * 100)) - pDialog.update(percentage, "Emby for Kodi - Incremental Sync Movies", progressTitle) - count = count + 1 - if not item.get('IsFolder'): - WriteKodiVideoDB().addOrUpdateMovieToKodiLibrary(item["Id"],connection, cursor, view.get('title')) - - #### PROCESS BOX SETS ##### - boxsets = ReadEmbyDB().getBoxSets() - count = 1 - total = len(boxsets) + 1 - for boxset in boxsets: - if(boxset["Id"] in itemList): - utils.logMsg("IncrementalSync", "Updating box Set : " + str(boxset["Name"]), 1) - boxsetMovies = ReadEmbyDB().getMoviesInBoxSet(boxset["Id"]) - WriteKodiVideoDB().addBoxsetToKodiLibrary(boxset, connection, cursor) - if(pDialog != None): - progressTitle = "Incremental Sync "+ " (" + str(count) + " of " + str(total) + ")" - percentage = int(((float(count) / float(total)) * 100)) - pDialog.update(percentage, "Emby for Kodi - Incremental Sync BoxSet", progressTitle) - count = count + 1 - WriteKodiVideoDB().removeMoviesFromBoxset(boxset, connection, cursor) - for boxsetMovie in boxsetMovies: - WriteKodiVideoDB().updateBoxsetToKodiLibrary(boxsetMovie, boxset, connection, cursor) - else: - utils.logMsg("IncrementalSync", "Skipping Box Set : " + boxset["Name"], 1) - - #### PROCESS TV SHOWS #### - views = ReadEmbyDB().getCollections("tvshows") - for view in views: - allEmbyTvShows = ReadEmbyDB().getTvShows(view.get('id'),itemList) - count = 1 - total = len(allEmbyTvShows) + 1 - for item in allEmbyTvShows: - if(pDialog != None): - progressTitle = "Incremental Sync "+ " (" + str(count) + " of " + str(total) + ")" - percentage = int(((float(count) / float(total)) * 100)) - pDialog.update(percentage, "Emby for Kodi - Incremental Sync Tv", progressTitle) - count = count + 1 - if utils.settings('syncEmptyShows') == "true" or (item.get('IsFolder') and item.get('RecursiveItemCount') != 0): - kodiId = WriteKodiVideoDB().addOrUpdateTvShowToKodiLibrary(item["Id"],connection, cursor, view.get('title')) - - - #### PROCESS OTHERS BY THE ITEMLIST ###### - count = 1 - total = len(itemList) + 1 - for item in itemList: - - if(pDialog != None): - progressTitle = "Incremental Sync "+ " (" + str(count) + " of " + str(total) + ")" - percentage = int(((float(count) / float(total)) * 100)) - pDialog.update(percentage, "Emby for Kodi - Incremental Sync Items", progressTitle) - count = count + 1 - - MBitem = ReadEmbyDB().getItem(item) - itemType = MBitem.get('Type', "") - - #### PROCESS EPISODES ###### - if "Episode" in itemType: - - #get the tv show - cursor.execute("SELECT kodi_id FROM emby WHERE media_type='tvshow' AND emby_id=?", (MBitem.get("SeriesId"),)) - result = cursor.fetchone() - if result: - kodi_show_id = result[0] - else: - kodi_show_id = None - - if kodi_show_id: - WriteKodiVideoDB().addOrUpdateEpisodeToKodiLibrary(MBitem["Id"], kodi_show_id, connection, cursor) - else: - #tv show doesn't exist - #perform full tvshow sync instead so both the show and episodes get added - self.TvShowsFullSync(connection,cursor,None) - - elif "Season" in itemType: - - #get the tv show - cursor.execute("SELECT kodi_id FROM emby WHERE media_type='tvshow' AND emby_id=?", (MBitem.get("SeriesId"),)) - result = cursor.fetchone() - if result: - kodi_show_id = result[0] - # update season - WriteKodiVideoDB().updateSeasons(MBitem["SeriesId"], kodi_show_id, connection, cursor) - - #### PROCESS BOXSETS ###### - elif "BoxSet" in itemType: - boxsetMovies = ReadEmbyDB().getMoviesInBoxSet(boxset["Id"]) - WriteKodiVideoDB().addBoxsetToKodiLibrary(boxset,connection, cursor) - - for boxsetMovie in boxsetMovies: - WriteKodiVideoDB().updateBoxsetToKodiLibrary(boxsetMovie,boxset, connection, cursor) - - #### PROCESS MUSICVIDEOS #### - elif "MusicVideo" in itemType: - if not MBitem.get('IsFolder'): - WriteKodiVideoDB().addOrUpdateMusicVideoToKodiLibrary(MBitem["Id"],connection, cursor) - - ### commit all changes to database ### - self.dbCommit(connection) - cursor.close() - - ### PROCESS MUSIC LIBRARY ### - if performMusicSync: - connection = utils.KodiSQL("music") - cursor = connection.cursor() - for item in itemList: - MBitem = ReadEmbyDB().getItem(item) - itemType = MBitem.get('Type', "") - - if "MusicArtist" in itemType: - WriteKodiMusicDB().addOrUpdateArtistToKodiLibrary(MBitem, connection, cursor) - if "MusicAlbum" in itemType: - WriteKodiMusicDB().addOrUpdateAlbumToKodiLibrary(MBitem, connection, cursor) - if "Audio" in itemType: - WriteKodiMusicDB().addOrUpdateSongToKodiLibrary(MBitem, connection, cursor) - self.dbCommit(connection) - cursor.close() - - finally: - if(pDialog != None): - pDialog.close() - - #self.updateLibrary("video") - WINDOW.setProperty("SyncDatabaseRunning", "false") - # tell any widgets to refresh because the content has changed - WINDOW.setProperty("widgetreload", datetime.now().strftime('%Y-%m-%d %H:%M:%S')) - - def removefromDB(self, itemList, deleteEmbyItem = False): - - dbSyncIndication = utils.settings("dbSyncIndication") == "true" - - #show the progress dialog - pDialog = None - if (dbSyncIndication and xbmc.Player().isPlaying() == False): - pDialog = xbmcgui.DialogProgressBG() - pDialog.create('Emby for Kodi', 'Incremental Sync') - self.logMsg("Doing LibraryChanged : Show Progress removefromDB()", 0); - - # Delete from Kodi before Emby - # To be able to get mediaType - doUtils = DownloadUtils() - video = {} - music = [] - - # Database connection to myVideosXX.db - connectionvideo = utils.KodiSQL() - cursorvideo = connectionvideo.cursor() - # Database connection to myMusicXX.db - connectionmusic = utils.KodiSQL("music") - cursormusic = connectionmusic.cursor() - - count = 1 - total = len(itemList) + 1 - for item in itemList: - - if(pDialog != None): - progressTitle = "Incremental Sync "+ " (" + str(count) + " of " + str(total) + ")" - percentage = int(((float(count) / float(total)) * 100)) - pDialog.update(percentage, "Emby for Kodi - Incremental Sync Delete ", progressTitle) - count = count + 1 - - # Sort by type for database deletion - try: # Search video database - self.logMsg("Check video database.", 1) - cursorvideo.execute("SELECT media_type FROM emby WHERE emby_id = ?", (item,)) - mediatype = cursorvideo.fetchone()[0] - video[item] = mediatype - #video.append(itemtype) - except: - self.logMsg("Check music database.", 1) - try: # Search music database - cursormusic.execute("SELECT media_type FROM emby WHERE emby_id = ?", (item,)) - cursormusic.fetchone()[0] - music.append(item) - except: self.logMsg("Item %s is not found in Kodi database." % item, 1) - - if len(video) > 0: - connection = connectionvideo - cursor = cursorvideo - # Process video library - count = 1 - total = len(video) + 1 - for item in video: - - if(pDialog != None): - progressTitle = "Incremental Sync "+ " (" + str(count) + " of " + str(total) + ")" - percentage = int(((float(count) / float(total)) * 100)) - pDialog.update(percentage, "Emby for Kodi - Incremental Sync Delete ", progressTitle) - count = count + 1 - - type = video[item] - self.logMsg("Doing LibraryChanged: Items Removed: Calling deleteItemFromKodiLibrary: %s" % item, 1) - - if "episode" in type: - # Get the TV Show Id for reference later - showId = ReadKodiDB().getShowIdByEmbyId(item, connection, cursor) - self.logMsg("ShowId: %s" % showId, 1) - WriteKodiVideoDB().deleteItemFromKodiLibrary(item, connection, cursor) - # Verification - if "episode" in type: - showTotalCount = ReadKodiDB().getShowTotalCount(showId, connection, cursor) - self.logMsg("ShowTotalCount: %s" % showTotalCount, 1) - # If there are no episodes left - if showTotalCount == 0 or showTotalCount == None: - # Delete show - embyId = ReadKodiDB().getEmbyIdByKodiId(showId, "tvshow", connection, cursor) - self.logMsg("Message: Doing LibraryChanged: Deleting show: %s" % embyId, 1) - WriteKodiVideoDB().deleteItemFromKodiLibrary(embyId, connection, cursor) - - self.dbCommit(connection) - # Close connection - cursorvideo.close() - - if len(music) > 0: - connection = connectionmusic - cursor = cursormusic - #Process music library - if utils.settings('enableMusicSync') == "true": - - for item in music: - self.logMsg("Message : Doing LibraryChanged : Items Removed : Calling deleteItemFromKodiLibrary (musiclibrary): " + item, 0) - WriteKodiMusicDB().deleteItemFromKodiLibrary(item, connection, cursor) - - self.dbCommit(connection) - # Close connection - cursormusic.close() - - if deleteEmbyItem: - for item in itemList: - url = "{server}/mediabrowser/Items/%s" % item - self.logMsg('Deleting via URL: %s' % url) - doUtils.downloadUrl(url, type = "DELETE") - xbmc.executebuiltin("Container.Refresh") - - if(pDialog != None): - pDialog.close() - - def setUserdata(self, listItems): - - dbSyncIndication = utils.settings("dbSyncIndication") == "true" - musicenabled = utils.settings('enableMusicSync') == "true" - - #show the progress dialog - pDialog = None - if (dbSyncIndication and xbmc.Player().isPlaying() == False): - pDialog = xbmcgui.DialogProgressBG() - pDialog.create('Emby for Kodi', 'Incremental Sync') - self.logMsg("Doing LibraryChanged : Show Progress setUserdata()", 0); - - # We need to sort between video and music database - video = [] - music = [] - # Database connection to myVideosXX.db - connectionvideo = utils.KodiSQL() - cursorvideo = connectionvideo.cursor() - # Database connection to myMusicXX.db - connectionmusic = utils.KodiSQL('music') - cursormusic = connectionmusic.cursor() - - count = 1 - total = len(listItems) + 1 - for userdata in listItems: - # Sort between video and music - itemId = userdata['ItemId'] - - if(pDialog != None): - progressTitle = "Incremental Sync "+ " (" + str(count) + " of " + str(total) + ")" - percentage = int(((float(count) / float(total)) * 100)) - pDialog.update(percentage, "Emby for Kodi - Incremental Sync User Data ", progressTitle) - count = count + 1 - - cursorvideo.execute("SELECT media_type FROM emby WHERE emby_id = ?", (itemId,)) - try: # Search video database - self.logMsg("Check video database.", 2) - mediatype = cursorvideo.fetchone()[0] - video.append(userdata) - except: - if musicenabled: - cursormusic.execute("SELECT media_type FROM emby WHERE emby_id = ?", (itemId,)) - try: # Search music database - self.logMsg("Check the music database.", 2) - mediatype = cursormusic.fetchone()[0] - music.append(userdata) - except: self.logMsg("Item %s is not found in Kodi database." % itemId, 1) - else: - self.logMsg("Item %s is not found in Kodi database." % itemId, 1) - - if len(video) > 0: - connection = connectionvideo - cursor = cursorvideo - # Process the userdata update for video library - count = 1 - total = len(video) + 1 - for userdata in video: - if(pDialog != None): - progressTitle = "Incremental Sync "+ " (" + str(count) + " of " + str(total) + ")" - percentage = int(((float(count) / float(total)) * 100)) - pDialog.update(percentage, "Emby for Kodi - Incremental Sync User Data ", progressTitle) - count = count + 1 - WriteKodiVideoDB().updateUserdata(userdata, connection, cursor) - - self.dbCommit(connection) - #self.updateLibrary("video") - # Close connection - cursorvideo.close() - - if len(music) > 0: - connection = connectionmusic - cursor = cursormusic - #Process music library - count = 1 - total = len(video) + 1 - # Process the userdata update for music library - if musicenabled: - for userdata in music: - if(pDialog != None): - progressTitle = "Incremental Sync "+ " (" + str(count) + " of " + str(total) + ")" - percentage = int(((float(count) / float(total)) * 100)) - pDialog.update(percentage, "Emby for Kodi - Incremental Sync User Data ", progressTitle) - count = count + 1 - WriteKodiMusicDB().updateUserdata(userdata, connection, cursor) - - self.dbCommit(connection) - #xbmc.executebuiltin("UpdateLibrary(music)") - # Close connection - cursormusic.close() - - if(pDialog != None): - pDialog.close() - - def remove_items(self, itemsRemoved): - # websocket client - if(len(itemsRemoved) > 0): - self.logMsg("Doing LibraryChanged : Processing Deleted : " + str(itemsRemoved), 0) - self.removeItems.extend(itemsRemoved) - - def update_items(self, itemsToUpdate): - # websocket client - if(len(itemsToUpdate) > 0): - self.logMsg("Doing LibraryChanged : Processing Added and Updated : " + str(itemsToUpdate), 0) - self.updateItems.extend(itemsToUpdate) - - def user_data_update(self, userDataList): - # websocket client - if(len(userDataList) > 0): - self.logMsg("Doing LibraryChanged : Processing User Data Changed : " + str(userDataList), 0) - self.userdataItems.extend(userDataList) - - def dbCommit(self, connection): - # Central commit, will verify if Kodi database - kodidb_scan = utils.window('kodiScan') == "true" - - while kodidb_scan: - - self.logMsg("Kodi scan running. Waiting...", 1) - kodidb_scan = utils.window('kodiScan') == "true" - - if self.KodiMonitor.waitForAbort(1): - # Abort was requested while waiting. We should exit - self.logMsg("Commit unsuccessful.", 1) - break - else: - connection.commit() - self.logMsg("Commit successful.", 1) - - def updateLibrary(self, type): - - self.logMsg("Updating %s library." % type, 1) - utils.window('kodiScan', value="true") - xbmc.executebuiltin('UpdateLibrary(%s)' % type) - - def ShouldStop(self): - - if(xbmc.abortRequested): - return True - - if(WINDOW.getProperty("SyncDatabaseShouldStop") == "true"): - return True - - return False - - def checkDBVersion(self, currVersion, minVersion): - currMajor, currMinor, currPatch = currVersion.split(".") - minMajor, minMinor, minPatch = minVersion.split(".") - if currMajor > minMajor: - return True - elif currMajor == minMajor and currMinor > minMinor: - return True - elif currMajor == minMajor and currMinor == minMinor and currPatch >= minPatch: - return True - else: - return False - - def run(self): - - try: - self.run_internal() - except Exception as e: - xbmcgui.Dialog().ok("Emby for Kodi", "Library sync thread has crashed!", "You will need to restart Kodi.", "Please report this on the forum, we will need your log.") - raise - - def run_internal(self): - - startupComplete = False - kodiProfile = xbmc.translatePath("special://profile") - - self.logMsg("--- Starting Library Sync Thread ---", 0) - - while not self.KodiMonitor.abortRequested(): - - # In the event the server goes offline after - # the thread has already been started. - while self.suspendClient == True: - # The service.py will change self.suspendClient to False - if self.KodiMonitor.waitForAbort(5): - # Abort was requested while waiting. We should exit - break - - # Check if the version of Emby for Kodi the DB was created with is recent enough - controled by Window property set at top of service _INIT_ - - # START TEMPORARY CODE - # Only get in here for a while, can be removed later - if utils.settings("dbCreatedWithVersion")=="" and utils.settings("SyncInstallRunDone") == "true": - self.logMsg("Unknown DB version", 0) - return_value = xbmcgui.Dialog().yesno("DB Version", "Can't detect version of Emby for Kodi the DB was created with.\nWas it at least version " + utils.window('minDBVersion') + "?") - if return_value == 0: - utils.settings("dbCreatedWithVersion","0.0.0") - self.logMsg("DB version out of date according to user", 0) - else: - utils.settings("dbCreatedWithVersion", utils.window('minDBVersion')) - self.logMsg("DB version okay according to user", 0) - # END TEMPORARY CODE - - if (utils.settings("SyncInstallRunDone") == "true" and self.checkDBVersion(utils.settings("dbCreatedWithVersion"), utils.window('minDBVersion'))==False and utils.window('minDBVersionCheck') != "true"): - self.logMsg("DB version out of date according to check", 0) - return_value = xbmcgui.Dialog().yesno("DB Version", "Detected the DB needs to be recreated for\nthis version of Emby for Kodi.\nProceed?") - if return_value == 0: - self.logMsg("DB version out of date !!! USER IGNORED !!!", 0) - xbmcgui.Dialog().ok("Emby for Kodi","Emby for Kodi may not work\ncorrectly until the database is reset.\n") - utils.window('minDBVersionCheck', value="true") - else: - utils.reset() - - # Library sync - if not startupComplete: - - # Verify the database for videos - videodb = utils.getKodiVideoDBPath() - if not xbmcvfs.exists(videodb): - # Database does not exists. - self.logMsg("The current Kodi version is incompatible with the Emby for Kodi add-on. Please visit here, to see currently supported Kodi versions: https://github.com/MediaBrowser/Emby.Kodi/wiki", 0) - xbmcgui.Dialog().ok("Emby Warning", "Cancelling the database syncing process. Current Kodi version: %s is unsupported. Please verify your logs for more info." % xbmc.getInfoLabel('System.BuildVersion')) - break - - # Run full sync - self.logMsg("DB Version: " + utils.settings("dbCreatedWithVersion"), 0) - self.logMsg("Doing_Db_Sync: syncDatabase (Started)", 1) - startTime = datetime.now() - libSync = self.FullLibrarySync() - elapsedTime = datetime.now() - startTime - self.logMsg("Doing_Db_Sync: syncDatabase (Finished in: %s) %s" % (str(elapsedTime).split('.')[0], libSync), 1) - - if libSync: - startupComplete = True - - # Set via Kodi Monitor event - if utils.window('OnWakeSync') == "true" and utils.window('Server_online') == "true": - utils.window("OnWakeSync", clear=True) - if utils.window("SyncDatabaseRunning") != "true": - self.logMsg("Doing_Db_Sync Post Resume: syncDatabase (Started)", 0) - libSync = self.FullLibrarySync() - self.logMsg("Doing_Db_Sync Post Resume: syncDatabase (Finished) " + str(libSync), 0) - - - doSaveLastSync = False - - if len(self.updateItems) > 0 and utils.window('kodiScan') != "true": - # Add or update items - self.logMsg("Processing items: %s" % (str(self.updateItems)), 1) - listItems = self.updateItems - self.updateItems = [] - self.IncrementalSync(listItems) - self.forceUpdate = True - doSaveLastSync = True - - if len(self.userdataItems) > 0 and utils.window('kodiScan') != "true": - # Process userdata changes only - self.logMsg("Processing items: %s" % (str(self.userdataItems)), 1) - listItems = self.userdataItems - self.userdataItems = [] - self.setUserdata(listItems) - self.forceUpdate = True - doSaveLastSync = True - - if len(self.removeItems) > 0 and utils.window('kodiScan') != "true": - # Remove item from Kodi library - self.logMsg("Removing items: %s" % self.removeItems, 1) - listItems = self.removeItems - self.removeItems = [] - self.removefromDB(listItems) - self.forceUpdate = True - doSaveLastSync = True - - if doSaveLastSync == True: - self.SaveLastSync() - - if self.forceUpdate and not self.updateItems and not self.userdataItems and not self.removeItems: - # Force update Kodi library - self.forceUpdate = False - self.updateLibrary("video") - - if utils.window("kodiProfile_emby") != kodiProfile: - # Profile change happened, terminate this thread - self.logMsg("Kodi profile was: %s and changed to: %s. Terminating Library thread." % (kodiProfile, utils.window("kodiProfile_emby")), 1) - break - - if self.KodiMonitor.waitForAbort(1): - # Abort was requested while waiting. We should exit - break - - self.logMsg("--- Library Sync Thread stopped ---", 0) - - def suspendClient(self): - self.suspendClient = True - self.logMsg("--- Library Sync Thread paused ---", 0) - - def resumeClient(self): - self.suspendClient = False - self.logMsg("--- Library Sync Thread resumed ---", 0) \ No newline at end of file diff --git a/resources/lib/PlayUtils.py b/resources/lib/PlayUtils.py deleted file mode 100644 index e8b9be58..00000000 --- a/resources/lib/PlayUtils.py +++ /dev/null @@ -1,364 +0,0 @@ -# -*- coding: utf-8 -*- - -################################################################################################# - -import xbmc -import xbmcgui -import xbmcvfs - -from ClientInformation import ClientInformation -import Utils as utils - -################################################################################################# - -class PlayUtils(): - - clientInfo = ClientInformation() - addonName = clientInfo.getAddonName() - - def logMsg(self, msg, lvl=1): - - className = self.__class__.__name__ - utils.logMsg("%s %s" % (self.addonName, className), msg, int(lvl)) - - def getPlayUrl(self, server, id, result): - - if self.isDirectPlay(result,True): - # Try direct play - playurl = self.directPlay(result) - if playurl: - self.logMsg("File is direct playing.", 1) - utils.window("%splaymethod" % playurl.encode('utf-8'), value="DirectPlay") - - elif self.isDirectStream(result): - # Try direct stream - playurl = self.directStream(result, server, id) - if playurl: - self.logMsg("File is direct streaming.", 1) - utils.window("%splaymethod" % playurl, value="DirectStream") - - elif self.isTranscoding(result): - # Try transcoding - playurl = self.transcoding(result, server, id) - if playurl: - self.logMsg("File is transcoding.", 1) - utils.window("%splaymethod" % playurl, value="Transcode") - - else: # Error - utils.window("playurlFalse", value="true") - return - - return playurl.encode('utf-8') - - - def isDirectPlay(self, result, dialog = False): - # Requirements for Direct play: - # FileSystem, Accessible path - if utils.settings('playFromStream') == "true": - # User forcing to play via HTTP instead of SMB - self.logMsg("Can't direct play: Play from HTTP is enabled.", 1) - return False - - # Avoid H265 1080p - if (utils.settings('transcodeH265') == "true" and - result['MediaSources'][0]['Name'].startswith("1080P/H265")): - self.logMsg("Option to transcode 1080P/H265 enabled.", 1) - return False - - canDirectPlay = result['MediaSources'][0]['SupportsDirectPlay'] - # Make sure it's supported by server - if not canDirectPlay: - self.logMsg("Can't direct play: Server does not allow or support it.", 1) - return False - - location = result['LocationType'] - # File needs to be "FileSystem" - if 'FileSystem' in location: - # Verify if path is accessible - if self.fileExists(result): - return True - else: - self.logMsg("Unable to direct play. Verify the following path is accessible by the device: %s. You might also need to add SMB credentials in the add-on settings." % result['MediaSources'][0]['Path'], 1) - if dialog: - - failCount = int(utils.settings('directSteamFailedCount')) - self.logMsg("Direct Play failCount: %s." % failCount, 1) - - if failCount < 2: - # Let user know that direct play failed - utils.settings('directSteamFailedCount', value=str(failCount + 1)) - xbmcgui.Dialog().notification("Emby server", "Unable to direct play. Verify your log for more information.", icon="special://home/addons/plugin.video.emby/icon.png", sound=False) - elif utils.settings('playFromStream') != "true": - # Permanently set direct stream as true - utils.settings('playFromStream', value="true") - xbmcgui.Dialog().notification("Emby server", "Enabled play from HTTP in add-on settings.", icon="special://home/addons/plugin.video.emby/icon.png", sound=False) - - return False - - def directPlay(self, result): - - try: - playurl = result['MediaSources'][0]['Path'] - except KeyError: - playurl = result['Path'] - - if 'VideoType' in result: - # Specific format modification - if 'Dvd' in result['VideoType']: - playurl = "%s/VIDEO_TS/VIDEO_TS.IFO" % playurl - elif 'BluRay' in result['VideoType']: - playurl = "%s/BDMV/index.bdmv" % playurl - - # Network - SMB protocol - if "\\\\" in playurl: - smbuser = utils.settings('smbusername') - smbpass = utils.settings('smbpassword') - # Network share - if smbuser: - playurl = playurl.replace("\\\\", "smb://%s:%s@" % (smbuser, smbpass)) - else: - playurl = playurl.replace("\\\\", "smb://") - playurl = playurl.replace("\\", "/") - - if "apple.com" in playurl: - USER_AGENT = "QuickTime/7.7.4" - playurl += "?|User-Agent=%s" % USER_AGENT - - return playurl - - - def isDirectStream(self, result): - # Requirements for Direct stream: - # FileSystem or Remote, BitRate, supported encoding - - # Avoid H265 1080p - if (utils.settings('transcodeH265') == "true" and - result['MediaSources'][0]['Name'].startswith("1080P/H265")): - self.logMsg("Option to transcode 1080P/H265 enabled.", 1) - return False - - canDirectStream = result['MediaSources'][0]['SupportsDirectStream'] - # Make sure it's supported by server - if not canDirectStream: - return False - - location = result['LocationType'] - # File can be FileSystem or Remote, not Virtual - if 'Virtual' in location: - self.logMsg("File location is virtual. Can't proceed.", 1) - return False - - # Verify BitRate - if not self.isNetworkQualitySufficient(result): - self.logMsg("The network speed is insufficient to playback the file.", 1) - return False - - return True - - def directStream(self, result, server, id, type = "Video"): - - if result['Path'].endswith('.strm'): - # Allow strm loading when direct streaming - playurl = self.directPlay(result) - return playurl - - if "ThemeVideo" in type: - playurl = "%s/mediabrowser/Videos/%s/stream?static=true" % (server, id) - - elif "Video" in type: - playurl = "%s/mediabrowser/Videos/%s/stream?static=true" % (server, id) - - elif "Audio" in type: - playurl = "%s/mediabrowser/Audio/%s/stream.mp3" % (server, id) - - return playurl - - - def isTranscoding(self, result): - # Last resort, no requirements - # BitRate - canTranscode = result['MediaSources'][0]['SupportsTranscoding'] - # Make sure it's supported by server - if not canTranscode: - return False - - location = result['LocationType'] - # File can be FileSystem or Remote, not Virtual - if 'Virtual' in location: - return False - - return True - - def transcoding(self, result, server, id): - - if result['Path'].endswith('.strm'): - # Allow strm loading when transcoding - playurl = self.directPlay(result) - return playurl - - # Play transcoding - deviceId = self.clientInfo.getMachineId() - playurl = "%s/mediabrowser/Videos/%s/master.m3u8?mediaSourceId=%s" % (server, id, id) - playurl = "%s&VideoCodec=h264&AudioCodec=ac3&MaxAudioChannels=6&deviceId=%s&VideoBitrate=%s" % (playurl, deviceId, self.getVideoBitRate()*1000) - - playurl = self.audioSubsPref(playurl, result.get('MediaSources')) - self.logMsg("Playurl: %s" % playurl, 1) - - return playurl - - - def isNetworkQualitySufficient(self, result): - # Works out if the network quality can play directly or if transcoding is needed - settingsVideoBitRate = self.getVideoBitRate() - settingsVideoBitRate = settingsVideoBitRate * 1000 - - try: - mediaSources = result['MediaSources'] - sourceBitRate = int(mediaSources[0]['Bitrate']) - except KeyError: - self.logMsg("Bitrate value is missing.", 1) - else: - self.logMsg("The video quality selected is: %s, the video bitrate required to direct stream is: %s." % (settingsVideoBitRate, sourceBitRate), 1) - if settingsVideoBitRate < sourceBitRate: - return False - - return True - - def getVideoBitRate(self): - # get the addon video quality - videoQuality = utils.settings('videoBitRate') - bitrate = { - - '0': 664, - '1': 996, - '2': 1320, - '3': 2000, - '4': 3200, - '5': 4700, - '6': 6200, - '7': 7700, - '8': 9200, - '9': 10700, - '10': 12200, - '11': 13700, - '12': 15200, - '13': 16700, - '14': 18200, - '15': 20000, - '16': 40000, - '17': 100000, - '18': 1000000 - } - - # max bit rate supported by server (max signed 32bit integer) - return bitrate.get(videoQuality, 2147483) - - def fileExists(self, result): - - if 'Path' not in result: - # File has no path in server - return False - - # Convert Emby path to a path we can verify - path = self.directPlay(result) - - try: - pathexists = xbmcvfs.exists(path) - except: - pathexists = False - - # Verify the device has access to the direct path - if pathexists: - # Local or Network path - self.logMsg("Path exists.", 2) - return True - elif ":" not in path: - # Give benefit of the doubt for nfs. - self.logMsg("Can't verify path (assumed NFS). Still try direct play.", 2) - return True - else: - self.logMsg("Path is detected as follow: %s. Try direct streaming." % path, 2) - return False - - def audioSubsPref(self, url, mediaSources): - # For transcoding only - # Present the list of audio to select from - audioStreamsList = {} - audioStreams = [] - audioStreamsChannelsList = {} - subtitleStreamsList = {} - subtitleStreams = ['No subtitles'] - selectAudioIndex = "" - selectSubsIndex = "" - playurlprefs = "%s" % url - - mediaStream = mediaSources[0].get('MediaStreams') - for stream in mediaStream: - # Since Emby returns all possible tracks together, have to sort them. - index = stream['Index'] - type = stream['Type'] - - if 'Audio' in type: - codec = stream['Codec'] - channelLayout = stream['ChannelLayout'] - - try: - track = "%s - %s - %s %s" % (index, stream['Language'], codec, channelLayout) - except: - track = "%s - %s %s" % (index, codec, channelLayout) - - audioStreamsChannelsList[index] = stream['Channels'] - audioStreamsList[track] = index - audioStreams.append(track) - - elif 'Subtitle' in type: - try: - track = "%s - %s" % (index, stream['Language']) - except: - track = "%s - %s" % (index, stream['Codec']) - - default = stream['IsDefault'] - forced = stream['IsForced'] - if default: - track = "%s - Default" % track - if forced: - track = "%s - Forced" % track - - subtitleStreamsList[track] = index - subtitleStreams.append(track) - - - if len(audioStreams) > 1: - resp = xbmcgui.Dialog().select("Choose the audio stream", audioStreams) - if resp > -1: - # User selected audio - selected = audioStreams[resp] - selectAudioIndex = audioStreamsList[selected] - playurlprefs += "&AudioStreamIndex=%s" % selectAudioIndex - else: # User backed out of selection - playurlprefs += "&AudioStreamIndex=%s" % mediaSources[0]['DefaultAudioStreamIndex'] - else: # There's only one audiotrack. - selectAudioIndex = audioStreamsList[audioStreams[0]] - playurlprefs += "&AudioStreamIndex=%s" % selectAudioIndex - - if len(subtitleStreams) > 1: - resp = xbmcgui.Dialog().select("Choose the subtitle stream", subtitleStreams) - if resp == 0: - # User selected no subtitles - pass - elif resp > -1: - # User selected subtitles - selected = subtitleStreams[resp] - selectSubsIndex = subtitleStreamsList[selected] - playurlprefs += "&SubtitleStreamIndex=%s" % selectSubsIndex - else: # User backed out of selection - playurlprefs += "&SubtitleStreamIndex=%s" % mediaSources[0].get('DefaultSubtitleStreamIndex', "") - - # Get number of channels for selected audio track - audioChannels = audioStreamsChannelsList.get(selectAudioIndex, 0) - if audioChannels > 2: - playurlprefs += "&AudioBitrate=384000" - else: - playurlprefs += "&AudioBitrate=192000" - - return playurlprefs \ No newline at end of file diff --git a/resources/lib/PlaybackUtils.py b/resources/lib/PlaybackUtils.py deleted file mode 100644 index 1172e52b..00000000 --- a/resources/lib/PlaybackUtils.py +++ /dev/null @@ -1,462 +0,0 @@ -# -*- coding: utf-8 -*- - -################################################################################################# - -import datetime -import json as json -import sys - -import xbmc -import xbmcaddon -import xbmcplugin -import xbmcgui - -from API import API -from DownloadUtils import DownloadUtils -from PlayUtils import PlayUtils -from ClientInformation import ClientInformation -import Utils as utils - -################################################################################################# - -class PlaybackUtils(): - - clientInfo = ClientInformation() - doUtils = DownloadUtils() - api = API() - - addon = xbmcaddon.Addon() - language = addon.getLocalizedString - addonName = clientInfo.getAddonName() - - def logMsg(self, msg, lvl=1): - - className = self.__class__.__name__ - utils.logMsg("%s %s" % (self.addonName, className), msg, int(lvl)) - - def PLAY(self, result, setup = "service"): - - self.logMsg("PLAY Called", 1) - - api = self.api - doUtils = self.doUtils - username = utils.window('currUser') - server = utils.window('server%s' % username) - - id = result['Id'] - userdata = result['UserData'] - # Get the playurl - direct play, direct stream or transcoding - playurl = PlayUtils().getPlayUrl(server, id, result) - listItem = xbmcgui.ListItem() - - if utils.window('playurlFalse') == "true": - # Playurl failed - set in PlayUtils.py - utils.window('playurlFalse', clear=True) - self.logMsg("Failed to retrieve the playback path/url or dialog was cancelled.", 1) - return xbmcplugin.setResolvedUrl(int(sys.argv[1]), False, listItem) - - - ############### -- SETUP MAIN ITEM ################ - - # Set listitem and properties for main item - self.logMsg("Returned playurl: %s" % playurl, 1) - listItem.setPath(playurl) - self.setProperties(playurl, result, listItem) - - mainArt = API().getArtwork(result, "Primary") - listItem.setThumbnailImage(mainArt) - listItem.setIconImage(mainArt) - - - ############### ORGANIZE CURRENT PLAYLIST ################ - - homeScreen = xbmc.getCondVisibility('Window.IsActive(home)') - playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO) - startPos = max(playlist.getposition(), 0) # Can return -1 - sizePlaylist = playlist.size() - - propertiesPlayback = utils.window('propertiesPlayback') == "true" - introsPlaylist = False - dummyPlaylist = False - currentPosition = startPos - - self.logMsg("Playlist start position: %s" % startPos, 2) - self.logMsg("Playlist plugin position: %s" % currentPosition, 2) - self.logMsg("Playlist size: %s" % sizePlaylist, 2) - - - ############### RESUME POINT ################ - - # Resume point for widget only - timeInfo = api.getTimeInfo(result) - jumpBackSec = int(utils.settings('resumeJumpBack')) - seekTime = round(float(timeInfo.get('ResumeTime')), 6) - if seekTime > jumpBackSec: - # To avoid negative bookmark - seekTime = seekTime - jumpBackSec - - # Show the additional resume dialog if launched from a widget - if homeScreen and seekTime: - # Dialog presentation - displayTime = str(datetime.timedelta(seconds=(int(seekTime)))) - display_list = ["%s %s" % (self.language(30106), displayTime), self.language(30107)] - resume_result = xbmcgui.Dialog().select(self.language(30105), display_list) - - if resume_result == 0: - # User selected to resume, append resume point to listitem - listItem.setProperty('StartOffset', str(seekTime)) - - elif resume_result > 0: - # User selected to start from beginning - seekTime = 0 - - else: # User cancelled the dialog - self.logMsg("User cancelled resume dialog.", 1) - return xbmcplugin.setResolvedUrl(int(sys.argv[1]), False, listItem) - - # We need to ensure we add the intro and additional parts only once. - # Otherwise we get a loop. - if not propertiesPlayback: - - utils.window('propertiesPlayback', value="true") - self.logMsg("Setting up properties in playlist.") - - ############### -- CHECK FOR INTROS ################ - - if utils.settings('disableCinema') == "false" and not seekTime: - # if we have any play them when the movie/show is not being resumed - url = "{server}/mediabrowser/Users/{UserId}/Items/%s/Intros?format=json&ImageTypeLimit=1&Fields=Etag" % id - intros = doUtils.downloadUrl(url) - - if intros['TotalRecordCount'] != 0: - getTrailers = True - - if utils.settings('askCinema') == "true": - resp = xbmcgui.Dialog().yesno("Emby Cinema Mode", "Play trailers?") - if not resp: - # User selected to not play trailers - getTrailers = False - self.logMsg("Skip trailers.", 1) - - if getTrailers: - for intro in intros['Items']: - # The server randomly returns intros, process them. - introId = intro['Id'] - - introPlayurl = PlayUtils().getPlayUrl(server, introId, intro) - introListItem = xbmcgui.ListItem() - self.logMsg("Adding Intro: %s" % introPlayurl, 1) - - # Set listitem and properties for intros - self.setProperties(introPlayurl, intro, introListItem) - self.setListItemProps(server, introId, introListItem, intro) - - playlist.add(introPlayurl, introListItem, index=currentPosition) - introsPlaylist = True - currentPosition += 1 - - - ############### -- ADD MAIN ITEM ONLY FOR HOMESCREEN ############### - - if homeScreen and not sizePlaylist: - # Extend our current playlist with the actual item to play only if there's no playlist first - self.logMsg("Adding main item to playlist.", 1) - self.setListItemProps(server, id, listItem, result) - playlist.add(playurl, listItem, index=currentPosition) - - # Ensure that additional parts are played after the main item - currentPosition += 1 - - - ############### -- CHECK FOR ADDITIONAL PARTS ################ - - if result.get('PartCount'): - # Only add to the playlist after intros have played - partcount = result['PartCount'] - url = "{server}/mediabrowser/Videos/%s/AdditionalParts" % id - parts = doUtils.downloadUrl(url) - - for part in parts['Items']: - - partId = part['Id'] - additionalPlayurl = PlayUtils().getPlayUrl(server, partId, part) - additionalListItem = xbmcgui.ListItem() - self.logMsg("Adding additional part: %s" % partcount, 1) - - # Set listitem and properties for each additional parts - self.setProperties(additionalPlayurl, part, additionalListItem) - self.setListItemProps(server, partId, additionalListItem, part) - - playlist.add(additionalPlayurl, additionalListItem, index=currentPosition) - currentPosition += 1 - - - ############### ADD DUMMY TO PLAYLIST ################# - - if (not homeScreen and introsPlaylist) or (homeScreen and sizePlaylist > 0): - # Playlist will fail on the current position. Adding dummy url - dummyPlaylist = True - self.logMsg("Adding dummy url to counter the setResolvedUrl error.", 2) - playlist.add(playurl, index=startPos) - currentPosition += 1 - - - # We just skipped adding properties. Reset flag for next time. - elif propertiesPlayback: - self.logMsg("Resetting properties playback flag.", 2) - utils.window('propertiesPlayback', clear=True) - - - self.verifyPlaylist() - - ############### PLAYBACK ################ - - if not homeScreen and not introsPlaylist: - - self.logMsg("Processed as a single item.", 1) - xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, listItem) - - elif 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) - xbmcplugin.setResolvedUrl(int(sys.argv[1]), False, listItem) - - else: - self.logMsg("Play as a regular item.", 1) - xbmc.Player().play(playlist, startpos=startPos) - - - def verifyPlaylist(self): - - playlistitems = '{"jsonrpc": "2.0", "method": "Playlist.GetItems", "params": { "playlistid": 1 }, "id": 1}' - items = xbmc.executeJSONRPC(playlistitems) - self.logMsg(items, 2) - - def removeFromPlaylist(self, pos): - - playlistremove = '{"jsonrpc": "2.0", "method": "Playlist.Remove", "params": { "playlistid": 1, "position": %d }, "id": 1}' % pos - result = xbmc.executeJSONRPC(playlistremove) - self.logMsg(result, 1) - - - def externalSubs(self, id, playurl, mediaSources): - - username = utils.window('currUser') - server = utils.window('server%s' % username) - externalsubs = [] - mapping = {} - - mediaStream = mediaSources[0].get('MediaStreams') - kodiindex = 0 - for stream in mediaStream: - - index = stream['Index'] - # Since Emby returns all possible tracks together, have to pull only external subtitles. - # IsTextSubtitleStream if true, is available to download from emby. - if "Subtitle" in stream['Type'] and stream['IsExternal'] and stream['IsTextSubtitleStream']: - - playmethod = utils.window("%splaymethod" % playurl) - - if "DirectPlay" in playmethod: - # Direct play, get direct path - url = PlayUtils().directPlay(stream) - elif "DirectStream" in playmethod: # Direct stream - url = "%s/Videos/%s/%s/Subtitles/%s/Stream.srt" % (server, id, id, index) - - # map external subtitles for mapping - mapping[kodiindex] = index - externalsubs.append(url) - kodiindex += 1 - - mapping = json.dumps(mapping) - utils.window('%sIndexMapping' % playurl, value=mapping) - - return externalsubs - - - def setProperties(self, playurl, result, listItem): - # Set runtimeticks, type, refresh_id and item_id - id = result.get('Id') - type = result.get('Type', "") - - utils.window("%sruntimeticks" % playurl, value=str(result.get('RunTimeTicks'))) - utils.window("%stype" % playurl, value=type) - utils.window("%sitem_id" % playurl, value=id) - - if type == "Episode": - utils.window("%srefresh_id" % playurl, value=result.get('SeriesId')) - else: - utils.window("%srefresh_id" % playurl, value=id) - - if utils.window("%splaymethod" % playurl) != "Transcode": - # Only for direct play and direct stream - # Append external subtitles to stream - subtitleList = self.externalSubs(id, playurl, result['MediaSources']) - listItem.setSubtitles(subtitleList) - - def setArt(self, list, name, path): - - if name in ("thumb", "fanart_image", "small_poster", "tiny_poster", "medium_landscape", "medium_poster", "small_fanartimage", "medium_fanartimage", "fanart_noindicators"): - list.setProperty(name, path) - else: - list.setArt({name:path}) - - return list - - def setListItemProps(self, server, id, listItem, result): - # Set up item and item info - api = self.api - - type = result.get('Type') - people = api.getPeople(result) - studios = api.getStudios(result) - - metadata = { - - 'title': result.get('Name', "Missing name"), - 'year': result.get('ProductionYear'), - 'plot': api.getOverview(result), - 'director': people.get('Director'), - 'writer': people.get('Writer'), - 'mpaa': api.getMpaa(result), - 'genre': api.getGenre(result), - 'studio': " / ".join(studios), - 'aired': api.getPremiereDate(result), - 'rating': result.get('CommunityRating'), - 'votes': result.get('VoteCount') - } - - if "Episode" in type: - # Only for tv shows - thumbId = result.get('SeriesId') - season = result.get('ParentIndexNumber', -1) - episode = result.get('IndexNumber', -1) - show = result.get('SeriesName', "") - - metadata['TVShowTitle'] = show - metadata['season'] = season - metadata['episode'] = episode - - listItem.setProperty('IsPlayable', 'true') - listItem.setProperty('IsFolder', 'false') - listItem.setLabel(metadata['title']) - listItem.setInfo('video', infoLabels=metadata) - - # Set artwork for listitem - self.setArt(listItem,'poster', API().getArtwork(result, "Primary")) - self.setArt(listItem,'tvshow.poster', API().getArtwork(result, "SeriesPrimary")) - self.setArt(listItem,'clearart', API().getArtwork(result, "Art")) - self.setArt(listItem,'tvshow.clearart', API().getArtwork(result, "Art")) - self.setArt(listItem,'clearlogo', API().getArtwork(result, "Logo")) - self.setArt(listItem,'tvshow.clearlogo', API().getArtwork(result, "Logo")) - self.setArt(listItem,'discart', API().getArtwork(result, "Disc")) - self.setArt(listItem,'fanart_image', API().getArtwork(result, "Backdrop")) - self.setArt(listItem,'landscape', API().getArtwork(result, "Thumb")) - - def seekToPosition(self, seekTo): - # Set a loop to wait for positive confirmation of playback - count = 0 - while not xbmc.Player().isPlaying(): - count += 1 - if count >= 10: - return - else: - xbmc.sleep(500) - - # Jump to seek position - count = 0 - while xbmc.Player().getTime() < (seekToTime - 5) and count < 11: # only try 10 times - count += 1 - xbmc.Player().seekTime(seekTo) - xbmc.sleep(100) - - def PLAYAllItems(self, items, startPositionTicks): - - self.logMsg("== ENTER: PLAYAllItems ==") - self.logMsg("Items: %s" % items) - - doUtils = self.doUtils - - playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO) - playlist.clear() - started = False - - for itemId in items: - self.logMsg("Adding Item to playlist: %s" % itemId, 1) - url = "{server}/mediabrowser/Users/{UserId}/Items/%s?format=json" % itemId - result = doUtils.downloadUrl(url) - - addition = self.addPlaylistItem(playlist, result) - if not started and addition: - started = True - self.logMsg("Starting Playback Pre", 1) - xbmc.Player().play(playlist) - - if not started: - self.logMsg("Starting Playback Post", 1) - xbmc.Player().play(playlist) - - # Seek to position - if startPositionTicks: - seekTime = startPositionTicks / 10000000.0 - self.seekToPosition(seekTime) - - def AddToPlaylist(self, itemIds): - - self.logMsg("== ENTER: PLAYAllItems ==") - - doUtils = self.doUtils - playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO) - - for itemId in itemIds: - self.logMsg("Adding Item to Playlist: %s" % itemId) - url = "{server}/mediabrowser/Users/{UserId}/Items/%s?format=json" % itemId - result = doUtils.downloadUrl(url) - - self.addPlaylistItem(playlist, result) - - return playlist - - def addPlaylistItem(self, playlist, item): - - id = item['Id'] - username = utils.window('currUser') - server = utils.window('server%s' % username) - - playurl = PlayUtils().getPlayUrl(server, id, item) - - if utils.window('playurlFalse') == "true": - # Playurl failed - set in PlayUtils.py - utils.window('playurlFalse', clear=True) - self.logMsg("Failed to retrieve the playback path/url or dialog was cancelled.", 1) - return - - self.logMsg("Playurl: %s" % playurl) - - thumb = API().getArtwork(item, "Primary") - listItem = xbmcgui.ListItem(path=playurl, iconImage=thumb, thumbnailImage=thumb) - self.setListItemProps(server, id, listItem, item) - self.setProperties(playurl, item, listItem) - - playlist.add(playurl, listItem) - - # Not currently being used - '''def PLAYAllEpisodes(self, items): - WINDOW = xbmcgui.Window(10000) - - username = WINDOW.getProperty('currUser') - userid = WINDOW.getProperty('userId%s' % username) - server = WINDOW.getProperty('server%s' % username) - - playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO) - playlist.clear() - - for item in items: - - item_url = "{server}/mediabrowser/Users/{UserId}/Items/%s?format=json&ImageTypeLimit=1" % item["Id"] - jsonData = self.downloadUtils.downloadUrl(item_url) - - item_data = jsonData - self.addPlaylistItem(playlist, item_data, server, userid) - - xbmc.Player().play(playlist)''' \ No newline at end of file diff --git a/resources/lib/Player.py b/resources/lib/Player.py deleted file mode 100644 index 0b760a88..00000000 --- a/resources/lib/Player.py +++ /dev/null @@ -1,440 +0,0 @@ -# -*- coding: utf-8 -*- - -################################################################################################# - -import json as json - -import xbmc -import xbmcgui - -from DownloadUtils import DownloadUtils -from WebSocketClient import WebSocketThread -from ClientInformation import ClientInformation -from LibrarySync import LibrarySync -import Utils as utils - -################################################################################################# - -class Player( xbmc.Player ): - - # Borg - multiple instances, shared state - _shared_state = {} - - xbmcplayer = xbmc.Player() - doUtils = DownloadUtils() - clientInfo = ClientInformation() - ws = WebSocketThread() - librarySync = LibrarySync() - - addonName = clientInfo.getAddonName() - - played_information = {} - playStats = {} - currentFile = None - - def __init__(self, *args): - - self.__dict__ = self._shared_state - 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, int(lvl)) - - def GetPlayStats(self): - return self.playStats - - def onPlayBackStarted( self ): - # Will be called when xbmc starts playing a file - xbmcplayer = self.xbmcplayer - self.stopAll() - - # Get current file - try: - currentFile = xbmcplayer.getPlayingFile() - xbmc.sleep(300) - except: - currentFile = "" - count = 0 - while not currentFile: - xbmc.sleep(100) - try: - currentFile = xbmcplayer.getPlayingFile() - except: pass - - if count == 5: # try 5 times - self.logMsg("Cancelling playback report...", 1) - break - else: count += 1 - - - if currentFile: - - self.currentFile = currentFile - - # We may need to wait for info to be set in kodi monitor - itemId = utils.window("%sitem_id" % currentFile) - tryCount = 0 - while not itemId: - - xbmc.sleep(200) - itemId = utils.window("%sitem_id" % currentFile) - if tryCount == 20: # try 20 times or about 10 seconds - self.logMsg("Could not find itemId, cancelling playback report...", 1) - break - else: tryCount += 1 - - else: - self.logMsg("ONPLAYBACK_STARTED: %s ITEMID: %s" % (currentFile, itemId), 0) - - # Only proceed if an itemId was found. - runtime = utils.window("%sruntimeticks" % currentFile) - refresh_id = utils.window("%srefresh_id" % currentFile) - playMethod = utils.window("%splaymethod" % currentFile) - itemType = utils.window("%stype" % currentFile) - seekTime = xbmcplayer.getTime() - - - # Get playback volume - volume_query = '{"jsonrpc": "2.0", "method": "Application.GetProperties", "params": {"properties": ["volume","muted"]}, "id": 1}' - result = xbmc.executeJSONRPC(volume_query) - result = json.loads(result) - result = result.get('result') - - volume = result.get('volume') - muted = result.get('muted') - - # Postdata structure to send to Emby server - url = "{server}/mediabrowser/Sessions/Playing" - postdata = { - - 'QueueableMediaTypes': "Video", - 'CanSeek': True, - 'ItemId': itemId, - 'MediaSourceId': itemId, - 'PlayMethod': playMethod, - 'VolumeLevel': volume, - 'PositionTicks': int(seekTime * 10000000), - 'IsMuted': muted - } - - # Get the current audio track and subtitles - if playMethod == "Transcode": - # property set in PlayUtils.py - postdata['AudioStreamIndex'] = utils.window("%sAudioStreamIndex" % currentFile) - postdata['SubtitleStreamIndex'] = utils.window("%sSubtitleStreamIndex" % currentFile) - - else: - # Get the current kodi audio and subtitles and convert to Emby equivalent - track_query = '{"jsonrpc": "2.0", "method": "Player.GetProperties", "params": {"playerid": 1,"properties": ["currentsubtitle","currentaudiostream","subtitleenabled"]} , "id": 1}' - result = xbmc.executeJSONRPC(track_query) - result = json.loads(result) - result = result.get('result') - - try: # Audio tracks - indexAudio = result['currentaudiostream']['index'] - except (KeyError, TypeError): - indexAudio = 0 - - try: # Subtitles tracks - indexSubs = result['currentsubtitle']['index'] - except (KeyError, TypeError): - indexSubs = 0 - - try: # If subtitles are enabled - subsEnabled = result['subtitleenabled'] - except (KeyError, TypeError): - subsEnabled = "" - - # Postdata for the audio - postdata['AudioStreamIndex'] = indexAudio + 1 - - # Postdata for the subtitles - if subsEnabled and len(xbmc.Player().getAvailableSubtitleStreams()) > 0: - - # Number of audiotracks to help get Emby Index - audioTracks = len(xbmc.Player().getAvailableAudioStreams()) - mapping = utils.window("%sIndexMapping" % currentFile) - - if mapping: # Set in PlaybackUtils.py - - self.logMsg("Mapping for external subtitles index: %s" % mapping, 2) - externalIndex = json.loads(mapping) - - if externalIndex.get(str(indexSubs)): - # If the current subtitle is in the mapping - postdata['SubtitleStreamIndex'] = externalIndex[str(indexSubs)] - else: - # Internal subtitle currently selected - postdata['SubtitleStreamIndex'] = indexSubs - len(externalIndex) + audioTracks + 1 - - else: # Direct paths enabled scenario or no external subtitles set - postdata['SubtitleStreamIndex'] = indexSubs + audioTracks + 1 - else: - postdata['SubtitleStreamIndex'] = "" - - - # Post playback to server - self.logMsg("Sending POST play started: %s." % postdata, 2) - self.doUtils.downloadUrl(url, postBody=postdata, type="POST") - - # Ensure we do have a runtime - try: - runtime = int(runtime) - except ValueError: - runtime = xbmcplayer.getTotalTime() - self.logMsg("Runtime is missing, grabbing runtime from Kodi player: %s" % runtime, 1) - - # Save data map for updates and position calls - data = { - - 'runtime': runtime, - 'item_id': itemId, - 'refresh_id': refresh_id, - 'currentfile': currentFile, - 'AudioStreamIndex': postdata['AudioStreamIndex'], - 'SubtitleStreamIndex': postdata['SubtitleStreamIndex'], - 'playmethod': playMethod, - 'Type': itemType, - 'currentPosition': int(seekTime) - } - - self.played_information[currentFile] = data - self.logMsg("ADDING_FILE: %s" % self.played_information, 1) - - # log some playback stats - '''if(itemType != None): - if(self.playStats.get(itemType) != None): - count = self.playStats.get(itemType) + 1 - self.playStats[itemType] = count - else: - self.playStats[itemType] = 1 - - if(playMethod != None): - if(self.playStats.get(playMethod) != None): - count = self.playStats.get(playMethod) + 1 - self.playStats[playMethod] = count - else: - self.playStats[playMethod] = 1''' - - def reportPlayback(self): - - self.logMsg("reportPlayback Called", 2) - xbmcplayer = self.xbmcplayer - - # Get current file - currentFile = self.currentFile - data = self.played_information.get(currentFile) - - # only report playback if emby has initiated the playback (item_id has value) - if data: - # Get playback information - itemId = data['item_id'] - audioindex = data['AudioStreamIndex'] - subtitleindex = data['SubtitleStreamIndex'] - playTime = data['currentPosition'] - playMethod = data['playmethod'] - paused = data.get('paused', False) - - - # Get playback volume - volume_query = '{"jsonrpc": "2.0", "method": "Application.GetProperties", "params": {"properties": ["volume","muted"]}, "id": 1}' - result = xbmc.executeJSONRPC(volume_query) - result = json.loads(result) - result = result.get('result') - - volume = result.get('volume') - muted = result.get('muted') - - - # Postdata for the websocketclient report - postdata = { - - 'QueueableMediaTypes': "Video", - 'CanSeek': True, - 'ItemId': itemId, - 'MediaSourceId': itemId, - 'PlayMethod': playMethod, - 'PositionTicks': int(playTime * 10000000), - 'IsPaused': paused, - 'VolumeLevel': volume, - 'IsMuted': muted - } - - if playMethod == "Transcode": - # Track can't be changed, keep reporting the same index - postdata['AudioStreamIndex'] = audioindex - postdata['AudioStreamIndex'] = subtitleindex - - else: - # Get current audio and subtitles track - track_query = '{"jsonrpc": "2.0", "method": "Player.GetProperties", "params": {"playerid":1,"properties": ["currentsubtitle","currentaudiostream","subtitleenabled"]} , "id": 1}' - result = xbmc.executeJSONRPC(track_query) - result = json.loads(result) - result = result.get('result') - - try: # Audio tracks - indexAudio = result['currentaudiostream']['index'] - except (KeyError, TypeError): - indexAudio = 0 - - try: # Subtitles tracks - indexSubs = result['currentsubtitle']['index'] - except (KeyError, TypeError): - indexSubs = 0 - - try: # If subtitles are enabled - subsEnabled = result['subtitleenabled'] - except (KeyError, TypeError): - subsEnabled = "" - - # Postdata for the audio - data['AudioStreamIndex'], postdata['AudioStreamIndex'] = [indexAudio + 1] * 2 - - # Postdata for the subtitles - if subsEnabled and len(xbmc.Player().getAvailableSubtitleStreams()) > 0: - - # Number of audiotracks to help get Emby Index - audioTracks = len(xbmc.Player().getAvailableAudioStreams()) - mapping = utils.window("%sIndexMapping" % currentFile) - - if mapping: # Set in PlaybackUtils.py - - self.logMsg("Mapping for external subtitles index: %s" % mapping, 2) - externalIndex = json.loads(mapping) - - if externalIndex.get(str(indexSubs)): - # If the current subtitle is in the mapping - data['SubtitleStreamIndex'], postdata['SubtitleStreamIndex'] = [externalIndex[str(indexSubs)]] * 2 - else: - # Internal subtitle currently selected - data['SubtitleStreamIndex'], postdata['SubtitleStreamIndex'] = [indexSubs - len(externalIndex) + audioTracks + 1] * 2 - - else: # Direct paths enabled scenario or no external subtitles set - data['SubtitleStreamIndex'], postdata['SubtitleStreamIndex'] = [indexSubs + audioTracks + 1] * 2 - else: - data['SubtitleStreamIndex'], postdata['SubtitleStreamIndex'] = [""] * 2 - - # Report progress via websocketclient - postdata = json.dumps(postdata) - self.logMsg("Report: %s" % postdata, 2) - self.ws.sendProgressUpdate(postdata) - - def onPlayBackPaused( self ): - - currentFile = self.currentFile - self.logMsg("PLAYBACK_PAUSED: %s" % currentFile, 2) - - if self.played_information.get(currentFile): - self.played_information[currentFile]['paused'] = True - - self.reportPlayback() - - def onPlayBackResumed( self ): - - currentFile = self.currentFile - self.logMsg("PLAYBACK_RESUMED: %s" % currentFile, 2) - - if self.played_information.get(currentFile): - self.played_information[currentFile]['paused'] = False - - self.reportPlayback() - - def onPlayBackSeek( self, time, seekOffset ): - # Make position when seeking a bit more accurate - currentFile = self.currentFile - self.logMsg("PLAYBACK_SEEK: %s" % currentFile, 2) - - if self.played_information.get(currentFile): - position = self.xbmcplayer.getTime() - self.played_information[currentFile]['currentPosition'] = position - - self.reportPlayback() - - def onPlayBackStopped( self ): - # Will be called when user stops xbmc playing a file - self.logMsg("ONPLAYBACK_STOPPED", 2) - self.stopAll() - - def onPlayBackEnded( self ): - # Will be called when xbmc stops playing a file - self.logMsg("ONPLAYBACK_ENDED", 2) - self.stopAll() - - def stopAll(self): - - if not self.played_information: - return - - self.logMsg("Played_information: %s" % self.played_information, 1) - # Process each items - for item in self.played_information: - - data = self.played_information.get(item) - if data: - - self.logMsg("Item path: %s" % item, 2) - self.logMsg("Item data: %s" % data, 2) - - runtime = data['runtime'] - currentPosition = data['currentPosition'] - itemId = data['item_id'] - refresh_id = data['refresh_id'] - currentFile = data['currentfile'] - type = data['Type'] - playMethod = data['playmethod'] - - if currentPosition and runtime: - percentComplete = (currentPosition * 10000000) / int(runtime) - markPlayedAt = float(utils.settings('markPlayed')) / 100 - - self.logMsg("Percent complete: %s Mark played at: %s" % (percentComplete, markPlayedAt), 1) - # Prevent manually mark as watched in Kodi monitor > WriteKodiVideoDB().UpdatePlaycountFromKodi() - utils.window('SkipWatched%s' % itemId, "true") - - self.stopPlayback(data) - offerDelete = utils.settings('offerDelete') == "true" - offerTypeDelete = False - - if type == "Episode" and utils.settings('offerDeleteTV') == "true": - offerTypeDelete = True - - elif type == "Movie" and utils.settings('offerDeleteMovies') == "true": - offerTypeDelete = True - - if percentComplete >= markPlayedAt and offerDelete and offerTypeDelete: - # Make the bigger setting be able to disable option easily. - self.logMsg("Offering deletion for: %s." % itemId, 1) - return_value = xbmcgui.Dialog().yesno("Offer Delete", "Delete %s" % currentFile.split("/")[-1], "on Emby Server?") - if return_value: - # Delete Kodi entry before Emby - listItem = [itemId] - LibrarySync().removefromDB(listItem, True) - - # Stop transcoding - if playMethod == "Transcode": - self.logMsg("Transcoding for %s terminated." % itemId, 1) - deviceId = self.clientInfo.getMachineId() - url = "{server}/mediabrowser/Videos/ActiveEncodings?DeviceId=%s" % deviceId - self.doUtils.downloadUrl(url, type="DELETE") - - self.played_information.clear() - - def stopPlayback(self, data): - - self.logMsg("stopPlayback called", 2) - - itemId = data['item_id'] - currentPosition = data['currentPosition'] - positionTicks = int(currentPosition * 10000000) - - url = "{server}/mediabrowser/Sessions/Playing/Stopped" - postdata = { - - 'ItemId': itemId, - 'MediaSourceId': itemId, - 'PositionTicks': positionTicks - } - - self.doUtils.downloadUrl(url, postBody=postdata, type="POST") \ No newline at end of file diff --git a/resources/lib/UserClient.py b/resources/lib/UserClient.py deleted file mode 100644 index f95f9291..00000000 --- a/resources/lib/UserClient.py +++ /dev/null @@ -1,460 +0,0 @@ -################################################################################################# -# UserClient thread -################################################################################################# - -import xbmc -import xbmcgui -import xbmcaddon -import xbmcvfs - -import threading -import hashlib -import json as json - -import KodiMonitor -import Utils as utils -from ClientInformation import ClientInformation -from DownloadUtils import DownloadUtils -from Player import Player -from API import API - - -class UserClient(threading.Thread): - - # Borg - multiple instances, shared state - _shared_state = {} - - clientInfo = ClientInformation() - doUtils = DownloadUtils() - KodiMonitor = KodiMonitor.Kodi_Monitor() - - addonName = clientInfo.getAddonName() - addon = xbmcaddon.Addon() - WINDOW = xbmcgui.Window(10000) - - stopClient = False - logLevel = int(addon.getSetting('logLevel')) - auth = True - retry = 0 - - currUser = None - currUserId = None - currServer = None - currToken = None - HasAccess = True - AdditionalUser = [] - - def __init__(self, *args): - - self.__dict__ = self._shared_state - threading.Thread.__init__(self, *args) - - def logMsg(self, msg, lvl=1): - - className = self.__class__.__name__ - utils.logMsg("%s %s" % (self.addonName, className), str(msg), int(lvl)) - - def getUsername(self): - - username = utils.settings('username') - - if (username == ""): - self.logMsg("No username saved.", 2) - return "" - - return username - - def getAdditionalUsers(self): - - additionalUsers = utils.settings('additionalUsers') - - if additionalUsers: - self.AdditionalUser = additionalUsers.split(',') - - def getLogLevel(self): - - try: - logLevel = int(utils.settings('logLevel')) - except: - logLevel = 0 - - return logLevel - - def getUserId(self): - - username = self.getUsername() - w_userId = self.WINDOW.getProperty('userId%s' % username) - s_userId = utils.settings('userId%s' % username) - - # Verify the window property - if (w_userId != ""): - self.logMsg("Returning userId from WINDOW for username: %s UserId: %s" % (username, w_userId), 2) - return w_userId - # Verify the settings - elif (s_userId != ""): - self.logMsg("Returning userId from SETTINGS for username: %s userId: %s" % (username, s_userId), 2) - return s_userId - # No userId found - else: - self.logMsg("No userId saved for username: %s." % username) - return - - def getServer(self, prefix=True): - - alternate = utils.settings('altip') == "true" - - # For https support - HTTPS = utils.settings('https') - host = utils.settings('ipaddress') - port = utils.settings('port') - # Alternate host - if alternate: - HTTPS = utils.settings('secondhttps') - host = utils.settings('secondipaddress') - port = utils.settings('secondport') - - server = host + ":" + port - - if host == "": - self.logMsg("No server information saved.", 2) - return "" - - # If https is true - if prefix and (HTTPS == "true"): - server = "https://%s" % server - return server - # If https is false - elif prefix and (HTTPS == "false"): - server = "http://%s" % server - return server - # If only the host:port is required - elif (prefix == False): - return server - - def getToken(self): - - username = self.getUsername() - w_token = self.WINDOW.getProperty('accessToken%s' % username) - s_token = utils.settings('accessToken') - - # Verify the window property - if (w_token != ""): - self.logMsg("Returning accessToken from WINDOW for username: %s accessToken: %s" % (username, w_token), 2) - return w_token - # Verify the settings - elif (s_token != ""): - self.logMsg("Returning accessToken from SETTINGS for username: %s accessToken: %s" % (username, s_token), 2) - self.WINDOW.setProperty('accessToken%s' % username, s_token) - return s_token - else: - self.logMsg("No token found.") - return "" - - def getSSLverify(self): - # Verify host certificate - s_sslverify = utils.settings('sslverify') - if utils.settings('altip') == "true": - s_sslverify = utils.settings('secondsslverify') - - if s_sslverify == "true": - return True - else: - return False - - def getSSL(self): - # Client side certificate - s_cert = utils.settings('sslcert') - if utils.settings('altip') == "true": - s_cert = utils.settings('secondsslcert') - - if s_cert == "None": - return None - else: - return s_cert - - def setUserPref(self): - - player = Player() - server = self.getServer() - userId = self.getUserId() - - url = "{server}/mediabrowser/Users/{UserId}?format=json" - result = self.doUtils.downloadUrl(url) - - # Set user image for skin display - self.WINDOW.setProperty("EmbyUserImage",API().getUserArtwork(result,"Primary")) - - # Load the resume point from Emby and set as setting - url = "{server}/mediabrowser/System/Configuration?format=json" - result = self.doUtils.downloadUrl(url) - - utils.settings('markPlayed', value=str(result['MaxResumePct'])) - - return True - - def getPublicUsers(self): - - server = self.getServer() - - # Get public Users - url = "%s/mediabrowser/Users/Public?format=json" % server - result = self.doUtils.downloadUrl(url, authenticate=False) - - users = [] - - if (result != ""): - users = result - else: - # Server connection failed - return False - - return users - - def hasAccess(self): - - url = "{server}/mediabrowser/Users" - result = self.doUtils.downloadUrl(url) - - if result is False: - # Access is restricted - self.logMsg("Access is restricted.") - self.HasAccess = False - return - elif self.WINDOW.getProperty('Server_online') != "true": - # Server connection failed - return - - if self.WINDOW.getProperty("Server_status") == "restricted": - self.logMsg("Access is granted.") - self.HasAccess = True - self.WINDOW.setProperty("Server_status", "") - xbmcgui.Dialog().notification("Emby server", "Access is enabled.") - return - - def loadCurrUser(self, authenticated=False): - - WINDOW = self.WINDOW - doUtils = self.doUtils - username = self.getUsername() - - # Only to be used if token exists - self.currUserId = self.getUserId() - self.currServer = self.getServer() - self.currToken = self.getToken() - self.ssl = self.getSSLverify() - self.sslcert = self.getSSL() - - # Test the validity of current token - if authenticated == False: - url = "%s/mediabrowser/Users/%s" % (self.currServer, self.currUserId) - WINDOW.setProperty("currUser", username) - WINDOW.setProperty("accessToken%s" % username, self.currToken) - result = doUtils.downloadUrl(url) - if result == 401: - # Token is no longer valid - self.resetClient() - return False - - # Set to windows property - WINDOW.setProperty("currUser", username) - WINDOW.setProperty("accessToken%s" % username, self.currToken) - WINDOW.setProperty("server%s" % username, self.currServer) - WINDOW.setProperty("server_%s" % username, self.getServer(prefix=False)) - WINDOW.setProperty("userId%s" % username, self.currUserId) - - # Set DownloadUtils values - doUtils.setUsername(username) - doUtils.setUserId(self.currUserId) - doUtils.setServer(self.currServer) - doUtils.setToken(self.currToken) - doUtils.setSSL(self.ssl, self.sslcert) - # parental control - let's verify if access is restricted - self.hasAccess() - # Start DownloadUtils session - doUtils.startSession() - self.getAdditionalUsers() - - self.currUser = username - # Set user preferences in settings - self.setUserPref() - - def authenticate(self): - - WINDOW = self.WINDOW - addon = self.addon - - username = self.getUsername() - server = self.getServer() - addondir = xbmc.translatePath(self.addon.getAddonInfo('profile')).decode('utf-8') - hasSettings = xbmcvfs.exists("%ssettings.xml" % addondir) - - # If there's no settings.xml - if (hasSettings == 0): - self.logMsg("No settings.xml found.") - self.auth = False - return - # If no user information - if (server == "") or (username == ""): - self.logMsg("Missing server information.") - self.auth = False - return - # If there's a token - if (self.getToken() != ""): - result = self.loadCurrUser() - - if result == False: - pass - else: - self.logMsg("Current user: %s" % self.currUser, 0) - self.logMsg("Current userId: %s" % self.currUserId, 0) - self.logMsg("Current accessToken: %s" % self.currToken, 0) - return - - users = self.getPublicUsers() - password = "" - - # Find user in list - for user in users: - name = user[u'Name'] - userHasPassword = False - - if (unicode(username, 'utf-8') in name): - # Verify if user has a password - if (user.get("HasPassword") == True): - userHasPassword = True - # If user has password - if (userHasPassword): - password = xbmcgui.Dialog().input("Enter password for user: %s" % username, option=xbmcgui.ALPHANUM_HIDE_INPUT) - # If password dialog is cancelled - if (password == ""): - self.logMsg("No password entered.", 0) - self.WINDOW.setProperty("Server_status", "Stop") - self.auth = False - return - break - else: - # Manual login, user is hidden - password = xbmcgui.Dialog().input("Enter password for user: %s" % username, option=xbmcgui.ALPHANUM_HIDE_INPUT) - - sha1 = hashlib.sha1(password) - sha1 = sha1.hexdigest() - - # Authenticate username and password - url = "%s/mediabrowser/Users/AuthenticateByName?format=json" % server - data = {'username': username, 'password': sha1} - self.logMsg(data, 2) - - result = self.doUtils.downloadUrl(url, postBody=data, type="POST", authenticate=False) - - accessToken = None - try: - self.logMsg("Auth_Reponse: %s" % result, 1) - accessToken = result[u'AccessToken'] - except: - pass - - if (result != None and accessToken != None): - self.currUser = username - xbmcgui.Dialog().notification("Emby server", "Welcome %s!" % self.currUser) - userId = result[u'User'][u'Id'] - utils.settings("accessToken", accessToken) - utils.settings("userId%s" % username, userId) - self.logMsg("User Authenticated: %s" % accessToken) - self.loadCurrUser(authenticated=True) - self.WINDOW.setProperty("Server_status", "") - self.retry = 0 - return - else: - self.logMsg("User authentication failed.") - utils.settings("accessToken", "") - utils.settings("userId%s" % username, "") - xbmcgui.Dialog().ok("Error connecting", "Invalid username or password.") - - # Give two attempts at entering password - self.retry += 1 - if self.retry == 2: - self.logMsg("Too many retries. You can retry by selecting the option in the addon settings.") - self.WINDOW.setProperty("Server_status", "Stop") - xbmcgui.Dialog().ok("Error connecting", "Failed to authenticate too many times. You can retry by selecting the option in the addon settings.") - - self.auth = False - return - - def resetClient(self): - - username = self.getUsername() - self.logMsg("Reset UserClient authentication.", 1) - if (self.currToken != None): - # In case of 401, removed saved token - utils.settings("accessToken", "") - self.WINDOW.setProperty("accessToken%s" % username, "") - self.currToken = None - self.logMsg("User token has been removed.", 1) - - self.auth = True - self.currUser = None - return - - - def run(self): - - self.logMsg("|---- Starting UserClient ----|", 0) - - while not self.KodiMonitor.abortRequested(): - - # Verify the log level - currLogLevel = self.getLogLevel() - if self.logLevel != currLogLevel: - # Set new log level - self.logLevel = currLogLevel - self.logMsg("New Log Level: %s" % currLogLevel, 0) - self.WINDOW.setProperty('getLogLevel', str(currLogLevel)) - - if (self.WINDOW.getProperty("Server_status") != ""): - status = self.WINDOW.getProperty("Server_status") - - if status == "restricted": - # Parental control is restricting access - self.HasAccess = False - - elif status == "401": - self.WINDOW.setProperty("Server_status", "Auth") - # Revoked token - self.resetClient() - - if self.auth and (self.currUser == None): - status = self.WINDOW.getProperty("Server_status") - - if (status == "") or (status == "Auth"): - self.auth = False - self.authenticate() - - if (self.auth == False) and (self.currUser == None): - # Only if there's information found to login - server = self.getServer() - username = self.getUsername() - status = self.WINDOW.getProperty("Server_status") - - # If user didn't enter a password when prompted - if status == "Stop": - pass - - elif (server != "") and (username != ""): - self.logMsg("Server found: %s" % server) - self.logMsg("Username found: %s" % username) - self.auth = True - - # If stopping the client didn't work - if self.stopClient == True: - break - - if self.KodiMonitor.waitForAbort(1): - # Abort was requested while waiting. We should exit - break - - self.doUtils.stopSession() - self.logMsg("|---- UserClient Stopped ----|", 0) - - def stopClient(self): - # As last resort - self.stopClient = True \ No newline at end of file diff --git a/resources/lib/Utils.py b/resources/lib/Utils.py deleted file mode 100644 index 73c49c95..00000000 --- a/resources/lib/Utils.py +++ /dev/null @@ -1,425 +0,0 @@ -################################################################################################# -# utils -################################################################################################# - -import xbmc -import xbmcgui -import xbmcaddon -import xbmcvfs -import json -import os -import cProfile -import pstats -import time -import inspect -import sqlite3 -import string -import unicodedata -import xml.etree.ElementTree as etree - -from API import API -from PlayUtils import PlayUtils -from DownloadUtils import DownloadUtils - -downloadUtils = DownloadUtils() -addon = xbmcaddon.Addon() -language = addon.getLocalizedString - - -def logMsg(title, msg, level = 1): - - WINDOW = xbmcgui.Window(10000) - # Get the logLevel set in UserClient - logLevel = int(WINDOW.getProperty('getLogLevel')) - - if(logLevel >= level): - if(logLevel == 2): # inspect.stack() is expensive - try: - xbmc.log(title + " -> " + inspect.stack()[1][3] + " : " + str(msg)) - except UnicodeEncodeError: - xbmc.log(title + " -> " + inspect.stack()[1][3] + " : " + str(msg.encode('utf-8'))) - else: - try: - xbmc.log(title + " -> " + str(msg)) - except UnicodeEncodeError: - xbmc.log(title + " -> " + str(msg.encode('utf-8'))) - -def convertEncoding(data): - #nasty hack to make sure we have a unicode string - try: - return data.decode('utf-8') - except: - return data - -def KodiSQL(type="video"): - - if type == "music": - dbPath = getKodiMusicDBPath() - elif type == "texture": - dbPath = xbmc.translatePath("special://database/Textures13.db").decode('utf-8') - else: - dbPath = getKodiVideoDBPath() - - connection = sqlite3.connect(dbPath) - return connection - -def getKodiVideoDBPath(): - - kodibuild = xbmc.getInfoLabel('System.BuildVersion')[:2] - dbVersion = { - - "13": 78, # Gotham - "14": 90, # Helix - "15": 93, # Isengard - "16": 99 # Jarvis - } - - dbPath = xbmc.translatePath( - "special://database/MyVideos%s.db" - % dbVersion.get(kodibuild, "")).decode('utf-8') - return dbPath - -def getKodiMusicDBPath(): - - kodibuild = xbmc.getInfoLabel('System.BuildVersion')[:2] - dbVersion = { - - "13": 46, # Gotham - "14": 48, # Helix - "15": 52, # Isengard - "16": 56 # Jarvis - } - - dbPath = xbmc.translatePath( - "special://database/MyMusic%s.db" - % dbVersion.get(kodibuild, "")).decode('utf-8') - return dbPath - -def prettifyXml(elem): - rough_string = etree.tostring(elem, "utf-8") - reparsed = minidom.parseString(rough_string) - return reparsed.toprettyxml(indent="\t") - -def startProfiling(): - pr = cProfile.Profile() - pr.enable() - return pr - -def stopProfiling(pr, profileName): - pr.disable() - ps = pstats.Stats(pr) - - addondir = xbmc.translatePath(xbmcaddon.Addon(id='plugin.video.emby').getAddonInfo('profile')) - - fileTimeStamp = time.strftime("%Y-%m-%d %H-%M-%S") - tabFileNamepath = os.path.join(addondir, "profiles") - tabFileName = os.path.join(addondir, "profiles" , profileName + "_profile_(" + fileTimeStamp + ").tab") - - if not xbmcvfs.exists(tabFileNamepath): - xbmcvfs.mkdir(tabFileNamepath) - - f = open(tabFileName, 'wb') - f.write("NumbCalls\tTotalTime\tCumulativeTime\tFunctionName\tFileName\r\n") - for (key, value) in ps.stats.items(): - (filename, count, func_name) = key - (ccalls, ncalls, total_time, cumulative_time, callers) = value - try: - f.write(str(ncalls) + "\t" + "{:10.4f}".format(total_time) + "\t" + "{:10.4f}".format(cumulative_time) + "\t" + func_name + "\t" + filename + "\r\n") - except ValueError: - f.write(str(ncalls) + "\t" + "{0}".format(total_time) + "\t" + "{0}".format(cumulative_time) + "\t" + func_name + "\t" + filename + "\r\n") - f.close() - -def indent(elem, level=0): - # Prettify xml trees - i = "\n" + level*" " - if len(elem): - if not elem.text or not elem.text.strip(): - elem.text = i + " " - if not elem.tail or not elem.tail.strip(): - elem.tail = i - for elem in elem: - indent(elem, level+1) - if not elem.tail or not elem.tail.strip(): - elem.tail = i - else: - if level and (not elem.tail or not elem.tail.strip()): - elem.tail = i - -def createSources(): - # To make Master lock compatible - path = xbmc.translatePath("special://profile/").decode("utf-8") - xmlpath = "%ssources.xml" % path - - if xbmcvfs.exists(xmlpath): - # Modify the existing file - try: - xmlparse = etree.parse(xmlpath) - except: - root = etree.Element('sources') - else: - root = xmlparse.getroot() - - video = root.find('video') - if video is None: - video = etree.SubElement(root, 'video') - else: - # We need to create the file - root = etree.Element('sources') - video = etree.SubElement(root, 'video') - - - # Add elements - etree.SubElement(video, 'default', attrib={'pathversion': "1"}) - - # First dummy source - source_one = etree.SubElement(video, 'source') - etree.SubElement(source_one, 'name').text = "Emby" - etree.SubElement(source_one, 'path', attrib={'pathversion': "1"}).text = ( - - "smb://embydummy/dummypath1/" - ) - etree.SubElement(source_one, 'allowsharing').text = "true" - - # Second dummy source - source_two = etree.SubElement(video, 'source') - etree.SubElement(source_two, 'name').text = "Emby" - etree.SubElement(source_two, 'path', attrib={'pathversion': "1"}).text = ( - - "smb://embydummy/dummypath2/" - ) - etree.SubElement(source_two, 'allowsharing').text = "true" - - try: - indent(root) - except:pass - etree.ElementTree(root).write(xmlpath) - -def pathsubstitution(add=True): - - path = xbmc.translatePath('special://userdata').decode('utf-8') - xmlpath = "%sadvancedsettings.xml" % path - xmlpathexists = xbmcvfs.exists(xmlpath) - - # original address - originalServer = settings('ipaddress') - originalPort = settings('port') - originalHttp = settings('https') == "true" - - if originalHttp: - originalHttp = "https" - else: - originalHttp = "http" - - # Process add or deletion - if add: - # second address - secondServer = settings('secondipaddress') - secondPort = settings('secondport') - secondHttp = settings('secondhttps') == "true" - - if secondHttp: - secondHttp = "https" - else: - secondHttp = "http" - - logMsg("EMBY", "Original address: %s://%s:%s, alternate is: %s://%s:%s" % (originalHttp, originalServer, originalPort, secondHttp, secondServer, secondPort), 1) - - if xmlpathexists: - # we need to modify the file. - try: - xmlparse = etree.parse(xmlpath) - except: # Document is blank - root = etree.Element('advancedsettings') - else: - root = xmlparse.getroot() - - pathsubs = root.find('pathsubstitution') - if pathsubs is None: - pathsubs = etree.SubElement(root, 'pathsubstitution') - else: - # we need to create the file. - root = etree.Element('advancedsettings') - pathsubs = etree.SubElement(root, 'pathsubstitution') - - substitute = etree.SubElement(pathsubs, 'substitute') - # From original address - etree.SubElement(substitute, 'from').text = "%s://%s:%s" % (originalHttp, originalServer, originalPort) - # To secondary address - etree.SubElement(substitute, 'to').text = "%s://%s:%s" % (secondHttp, secondServer, secondPort) - - etree.ElementTree(root).write(xmlpath) - settings('pathsub', "true") - - else: # delete the path substitution, we don't need it anymore. - logMsg("EMBY", "Alternate address is disabled, removing path substitution for: %s://%s:%s" % (originalHttp, originalServer, originalPort), 1) - - xmlparse = etree.parse(xmlpath) - root = xmlparse.getroot() - - iterator = root.getiterator("pathsubstitution") - - for substitutes in iterator: - for substitute in substitutes: - frominsert = substitute.find(".//from").text == "%s://%s:%s" % (originalHttp, originalServer, originalPort) - - if frominsert: - # Found a match, in case there's more than one substitution. - substitutes.remove(substitute) - - etree.ElementTree(root).write(xmlpath) - settings('pathsub', "false") - - -def settings(setting, value = None): - # Get or add addon setting - addon = xbmcaddon.Addon() - if value: - addon.setSetting(setting, value) - else: - return addon.getSetting(setting) - -def window(property, value = None, clear = False): - # Get or set window property - WINDOW = xbmcgui.Window(10000) - if clear: - WINDOW.clearProperty(property) - elif value: - WINDOW.setProperty(property, value) - else: - return WINDOW.getProperty(property) - -def normalize_string(text): - # For theme media, do not modify unless - # modified in TV Tunes - text = text.replace(":", "") - text = text.replace("/", "-") - text = text.replace("\\", "-") - text = text.replace("<", "") - text = text.replace(">", "") - text = text.replace("*", "") - text = text.replace("?", "") - text = text.replace('|', "") - text = text.strip() - # Remove dots from the last character as windows can not have directories - # with dots at the end - text = text.rstrip('.') - text = unicodedata.normalize('NFKD', unicode(text, 'utf-8')).encode('ascii', 'ignore') - - return text - -def normalize_nodes(text): - # For video nodes - text = text.replace(":", "") - text = text.replace("/", "-") - text = text.replace("\\", "-") - text = text.replace("<", "") - text = text.replace(">", "") - text = text.replace("*", "") - text = text.replace("?", "") - text = text.replace('|', "") - text = text.replace('(', "") - text = text.replace(')', "") - text = text.strip() - # Remove dots from the last character as windows can not have directories - # with dots at the end - text = text.rstrip('.') - text = unicodedata.normalize('NFKD', unicode(text, 'utf-8')).encode('ascii', 'ignore') - - return text - -def reloadProfile(): - # Useful to reload the add-on without restarting Kodi. - profile = xbmc.getInfoLabel('System.ProfileName') - xbmc.executebuiltin("LoadProfile(%s)" % profile) - - -def reset(): - - WINDOW = xbmcgui.Window( 10000 ) - return_value = xbmcgui.Dialog().yesno("Warning", "Are you sure you want to reset your local Kodi database?") - - if return_value == 0: - return - - # Because the settings dialog could be open - # it seems to override settings so we need to close it before we reset settings. - xbmc.executebuiltin("Dialog.Close(all,true)") - - #cleanup video nodes - import shutil - path = "special://profile/library/video/" - if xbmcvfs.exists(path): - allDirs, allFiles = xbmcvfs.listdir(path) - for dir in allDirs: - if dir.startswith("Emby "): - shutil.rmtree(xbmc.translatePath("special://profile/library/video/" + dir)) - for file in allFiles: - if file.startswith("emby"): - xbmcvfs.delete(path + file) - - settings('SyncInstallRunDone', "false") - - # Ask if user information should be deleted too. - return_user = xbmcgui.Dialog().yesno("Warning", "Reset all Emby Addon settings?") - if return_user == 1: - WINDOW.setProperty('deletesettings', "true") - 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) - - # first stop any db sync - WINDOW.setProperty("SyncDatabaseShouldStop", "true") - - count = 0 - while(WINDOW.getProperty("SyncDatabaseRunning") == "true"): - xbmc.log("Sync Running, will wait : " + str(count)) - count += 1 - if(count > 10): - dialog = xbmcgui.Dialog() - dialog.ok('Warning', 'Could not stop DB sync, you should try again.') - return - xbmc.sleep(1000) - - # delete video db table data - print "Doing Video DB Reset" - 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) - cursor.execute("DROP TABLE IF EXISTS emby") - connection.commit() - cursor.close() - - if settings('enableMusicSync') == "true": - # delete video db table data - print "Doing Music DB Reset" - 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) - cursor.execute("DROP TABLE IF EXISTS emby") - connection.commit() - cursor.close() - - - # reset the install run flag - #settings('SyncInstallRunDone', "false") - #WINDOW.setProperty("SyncInstallRunDone", "false") - - dialog = xbmcgui.Dialog() - # Reload would work instead of restart since the add-on is a service. - #dialog.ok('Emby Reset', 'Database reset has completed, Kodi will now restart to apply the changes.') - #WINDOW.clearProperty("SyncDatabaseShouldStop") - #reloadProfile() - dialog.ok('Emby Reset', 'Database reset has completed, Kodi will now restart to apply the changes.') - xbmc.executebuiltin("RestartApp") \ No newline at end of file diff --git a/resources/lib/VideoNodes.py b/resources/lib/VideoNodes.py deleted file mode 100644 index 056ee13f..00000000 --- a/resources/lib/VideoNodes.py +++ /dev/null @@ -1,466 +0,0 @@ -################################################################################################# -# VideoNodes - utils to create video nodes listings in kodi for the emby addon -################################################################################################# - - -import xbmc -import xbmcgui -import xbmcaddon -import xbmcvfs -import json -import os -import shutil -#import common elementree because cElementree has issues with kodi -import xml.etree.ElementTree as etree - -import Utils as utils - -from ReadEmbyDB import ReadEmbyDB -WINDOW = xbmcgui.Window(10000) - -addonSettings = xbmcaddon.Addon() -language = addonSettings.getLocalizedString - -class VideoNodes(): - - - def buildVideoNodeForView(self, tagname, type, windowPropId): - #this method will build a video node for a particular Emby view (= tag in kodi) - #we set some window props here to for easy future reference and to be used in skins (for easy access only) - tagname_normalized = utils.normalize_nodes(tagname.encode('utf-8')) - - libraryPath = xbmc.translatePath("special://profile/library/video/Emby - %s/" %tagname_normalized) - kodiVersion = 14 - if xbmc.getInfoLabel("System.BuildVersion").startswith("15") or xbmc.getInfoLabel("System.BuildVersion").startswith("16"): - kodiVersion = 15 - - #create tag node - index - xbmcvfs.mkdir(libraryPath) - nodefile = os.path.join(libraryPath, "index.xml") - root = etree.Element("node", {"order":"0"}) - etree.SubElement(root, "label").text = tagname - etree.SubElement(root, "icon").text = "special://home/addons/plugin.video.emby/icon.png" - path = "library://video/Emby - %s/" %tagname_normalized - WINDOW.setProperty("Emby.nodes.%s.index" %str(windowPropId),path) - try: - etree.ElementTree(root).write(nodefile, xml_declaration=True) - except: - etree.ElementTree(root).write(nodefile) - - #create tag node - all items - nodefile = os.path.join(libraryPath, tagname_normalized + "_all.xml") - root = etree.Element("node", {"order":"1", "type":"filter"}) - etree.SubElement(root, "label").text = tagname - etree.SubElement(root, "match").text = "all" - etree.SubElement(root, "content").text = type - etree.SubElement(root, "icon").text = "special://home/addons/plugin.video.emby/icon.png" - etree.SubElement(root, "order", {"direction":"ascending"}).text = "sorttitle" - Rule = etree.SubElement(root, "rule", {"field":"tag","operator":"is"}) - WINDOW.setProperty("Emby.nodes.%s.title" %str(windowPropId),tagname) - path = "library://video/Emby - %s/%s_all.xml"%(tagname_normalized,tagname_normalized) - WINDOW.setProperty("Emby.nodes.%s.path" %str(windowPropId),"ActivateWindow(Video,%s,return)"%path) - WINDOW.setProperty("Emby.nodes.%s.content" %str(windowPropId),path) - WINDOW.setProperty("Emby.nodes.%s.type" %str(windowPropId),type) - etree.SubElement(Rule, "value").text = tagname - try: - etree.ElementTree(root).write(nodefile, xml_declaration=True) - except: - etree.ElementTree(root).write(nodefile) - - #create tag node - recent items - nodefile = os.path.join(libraryPath, tagname_normalized + "_recent.xml") - root = etree.Element("node", {"order":"2", "type":"filter"}) - if type == "tvshows": - label = language(30170) - else: - label = language(30174) - etree.SubElement(root, "label").text = label - etree.SubElement(root, "match").text = "all" - etree.SubElement(root, "content").text = type - etree.SubElement(root, "icon").text = "special://home/addons/plugin.video.emby/icon.png" - Rule = etree.SubElement(root, "rule", {"field":"tag","operator":"is"}) - etree.SubElement(Rule, "value").text = tagname - etree.SubElement(root, "order", {"direction":"descending"}).text = "dateadded" - #set limit to 25 --> currently hardcoded --> TODO: add a setting for this ? - etree.SubElement(root, "limit").text = "25" - #exclude watched items --> currently hardcoded --> TODO: add a setting for this ? - Rule2 = etree.SubElement(root, "rule", {"field":"playcount","operator":"is"}) - etree.SubElement(Rule2, "value").text = "0" - WINDOW.setProperty("Emby.nodes.%s.recent.title" %str(windowPropId),label) - path = "library://video/Emby - %s/%s_recent.xml"%(tagname_normalized,tagname_normalized) - WINDOW.setProperty("Emby.nodes.%s.recent.path" %str(windowPropId),"ActivateWindow(Video,%s,return)"%path) - WINDOW.setProperty("Emby.nodes.%s.recent.content" %str(windowPropId),path) - try: - etree.ElementTree(root).write(nodefile, xml_declaration=True) - except: - etree.ElementTree(root).write(nodefile) - - #create tag node - inprogress items - nodefile = os.path.join(libraryPath, tagname_normalized + "_progress.xml") - root = etree.Element("node", {"order":"3", "type":"filter"}) - if type == "tvshows": - label = language(30171) - else: - label = language(30177) - etree.SubElement(root, "label").text = label - etree.SubElement(root, "match").text = "all" - etree.SubElement(root, "content").text = type - etree.SubElement(root, "icon").text = "special://home/addons/plugin.video.emby/icon.png" - Rule = etree.SubElement(root, "rule", {"field":"tag","operator":"is"}) - etree.SubElement(Rule, "value").text = tagname - #set limit to 25 --> currently hardcoded --> TODO: add a setting for this ? - etree.SubElement(root, "limit").text = "25" - Rule2 = etree.SubElement(root, "rule", {"field":"inprogress","operator":"true"}) - WINDOW.setProperty("Emby.nodes.%s.inprogress.title" %str(windowPropId),label) - path = "library://video/Emby - %s/%s_progress.xml"%(tagname_normalized,tagname_normalized) - WINDOW.setProperty("Emby.nodes.%s.inprogress.path" %str(windowPropId),"ActivateWindow(Video,%s,return)"%path) - WINDOW.setProperty("Emby.nodes.%s.inprogress.content" %str(windowPropId),path) - try: - etree.ElementTree(root).write(nodefile, xml_declaration=True) - except: - etree.ElementTree(root).write(nodefile) - - #some movies-only nodes - if type == "movies": - - #unwatched movies - nodefile = os.path.join(libraryPath, tagname_normalized + "_unwatched.xml") - root = etree.Element("node", {"order":"4", "type":"filter"}) - label = language(30189) - etree.SubElement(root, "label").text = label - etree.SubElement(root, "match").text = "all" - etree.SubElement(root, "content").text = "movies" - etree.SubElement(root, "icon").text = "special://home/addons/plugin.video.emby/icon.png" - Rule = etree.SubElement(root, "rule", {"field":"tag","operator":"is"}) - etree.SubElement(Rule, "value").text = tagname - Rule = etree.SubElement(root, "rule", {"field":"playcount","operator":"is"}) - etree.SubElement(Rule, "value").text = "0" - etree.SubElement(root, "order", {"direction":"ascending"}).text = "sorttitle" - Rule2 = etree.SubElement(root, "rule", {"field":"playcount","operator":"is"}) - etree.SubElement(Rule2, "value").text = "0" - WINDOW.setProperty("Emby.nodes.%s.unwatched.title" %str(windowPropId),label) - path = "library://video/Emby - %s/%s_unwatched.xml"%(tagname_normalized,tagname_normalized) - WINDOW.setProperty("Emby.nodes.%s.unwatched.path" %str(windowPropId),"ActivateWindow(Video,%s,return)"%path) - WINDOW.setProperty("Emby.nodes.%s.unwatched.content" %str(windowPropId),path) - try: - etree.ElementTree(root).write(nodefile, xml_declaration=True) - except: - etree.ElementTree(root).write(nodefile) - - #sets - nodefile = os.path.join(libraryPath, tagname_normalized + "_sets.xml") - root = etree.Element("node", {"order":"9", "type":"filter"}) - label = xbmc.getLocalizedString(20434) - etree.SubElement(root, "label").text = label - etree.SubElement(root, "match").text = "all" - etree.SubElement(root, "group").text = "sets" - etree.SubElement(root, "content").text = type - etree.SubElement(root, "icon").text = "special://home/addons/plugin.video.emby/icon.png" - etree.SubElement(root, "order", {"direction":"ascending"}).text = "sorttitle" - Rule = etree.SubElement(root, "rule", {"field":"tag","operator":"is"}) - etree.SubElement(Rule, "value").text = tagname - WINDOW.setProperty("Emby.nodes.%s.sets.title" %str(windowPropId),label) - path = "library://video/Emby - %s/%s_sets.xml"%(tagname_normalized,tagname_normalized) - WINDOW.setProperty("Emby.nodes.%s.sets.path" %str(windowPropId),"ActivateWindow(Video,%s,return)"%path) - WINDOW.setProperty("Emby.nodes.%s.sets.content" %str(windowPropId),path) - try: - etree.ElementTree(root).write(nodefile, xml_declaration=True) - except: - etree.ElementTree(root).write(nodefile) - - #create tag node - genres - nodefile = os.path.join(libraryPath, tagname_normalized + "_genres.xml") - root = etree.Element("node", {"order":"9", "type":"filter"}) - label = xbmc.getLocalizedString(135) - etree.SubElement(root, "label").text = label - etree.SubElement(root, "match").text = "all" - etree.SubElement(root, "group").text = "genres" - etree.SubElement(root, "content").text = type - etree.SubElement(root, "icon").text = "special://home/addons/plugin.video.emby/icon.png" - etree.SubElement(root, "order", {"direction":"ascending"}).text = "sorttitle" - Rule = etree.SubElement(root, "rule", {"field":"tag","operator":"is"}) - etree.SubElement(Rule, "value").text = tagname - WINDOW.setProperty("Emby.nodes.%s.genres.title" %str(windowPropId),label) - path = "library://video/Emby - %s/%s_genres.xml"%(tagname_normalized,tagname_normalized) - WINDOW.setProperty("Emby.nodes.%s.genres.path" %str(windowPropId),"ActivateWindow(Video,%s,return)"%path) - WINDOW.setProperty("Emby.nodes.%s.genres.content" %str(windowPropId),path) - - try: - etree.ElementTree(root).write(nodefile, xml_declaration=True) - except: - etree.ElementTree(root).write(nodefile) - - #create tag node - random items - nodefile = os.path.join(libraryPath, tagname_normalized + "_random.xml") - root = etree.Element("node", {"order":"10", "type":"filter"}) - label = language(30229) - etree.SubElement(root, "label").text = label - etree.SubElement(root, "match").text = "all" - etree.SubElement(root, "content").text = type - etree.SubElement(root, "icon").text = "special://home/addons/plugin.video.emby/icon.png" - Rule = etree.SubElement(root, "rule", {"field":"tag","operator":"is"}) - etree.SubElement(Rule, "value").text = tagname - #set limit to 25 --> currently hardcoded --> TODO: add a setting for this ? - etree.SubElement(root, "limit").text = "25" - etree.SubElement(root, "order", {"direction":"ascending"}).text = "random" - WINDOW.setProperty("Emby.nodes.%s.random.title" %str(windowPropId),label) - path = "library://video/Emby - %s/%s_random.xml"%(tagname_normalized,tagname_normalized) - WINDOW.setProperty("Emby.nodes.%s.random.path" %str(windowPropId),"ActivateWindow(Video,%s,return)"%path) - WINDOW.setProperty("Emby.nodes.%s.random.content" %str(windowPropId),path) - try: - etree.ElementTree(root).write(nodefile, xml_declaration=True) - except: - etree.ElementTree(root).write(nodefile) - - #create tag node - recommended items - nodefile = os.path.join(libraryPath, tagname_normalized + "_recommended.xml") - root = etree.Element("node", {"order":"10", "type":"filter"}) - label = language(30230) - etree.SubElement(root, "label").text = label - etree.SubElement(root, "match").text = "all" - etree.SubElement(root, "content").text = type - etree.SubElement(root, "icon").text = "special://home/addons/plugin.video.emby/icon.png" - Rule = etree.SubElement(root, "rule", {"field":"tag","operator":"is"}) - etree.SubElement(Rule, "value").text = tagname - Rule2 = etree.SubElement(root, "rule", {"field":"playcount","operator":"is"}) - etree.SubElement(Rule2, "value").text = "0" - Rule3 = etree.SubElement(root, "rule", {"field":"rating","operator":"greaterthan"}) - etree.SubElement(Rule3, "value").text = "7" - #set limit to 25 --> currently hardcoded --> TODO: add a setting for this ? - etree.SubElement(root, "limit").text = "25" - etree.SubElement(root, "order", {"direction":"descending"}).text = "rating" - WINDOW.setProperty("Emby.nodes.%s.random.title" %str(windowPropId),label) - path = "library://video/Emby - %s/%s_recommended.xml"%(tagname_normalized,tagname_normalized) - WINDOW.setProperty("Emby.nodes.%s.recommended.path" %str(windowPropId),"ActivateWindow(Video,%s,return)"%path) - WINDOW.setProperty("Emby.nodes.%s.recommended.content" %str(windowPropId),path) - try: - etree.ElementTree(root).write(nodefile, xml_declaration=True) - except: - etree.ElementTree(root).write(nodefile) - - #### TAGS ONLY FOR TV SHOWS COLLECTIONS #### - if type == "tvshows": - - #as from kodi isengard you can use tags for episodes to filter - #for below isengard we still use the plugin's entrypoint to build a listing - if kodiVersion == 15: - #create tag node - recent episodes - nodefile = os.path.join(libraryPath, tagname_normalized + "_recent_episodes.xml") - root = etree.Element("node", {"order":"3", "type":"filter"}) - label = language(30175) - etree.SubElement(root, "label").text = label - etree.SubElement(root, "match").text = "all" - etree.SubElement(root, "content").text = "episodes" - etree.SubElement(root, "icon").text = "special://home/addons/plugin.video.emby/icon.png" - etree.SubElement(root, "order", {"direction":"descending"}).text = "dateadded" - Rule = etree.SubElement(root, "rule", {"field":"tag","operator":"is"}) - etree.SubElement(Rule, "value").text = tagname - #set limit to 25 --> currently hardcoded --> TODO: add a setting for this ? - etree.SubElement(root, "limit").text = "25" - #exclude watched items --> currently hardcoded --> TODO: add a setting for this ? - Rule2 = etree.SubElement(root, "rule", {"field":"playcount","operator":"is"}) - etree.SubElement(Rule2, "value").text = "0" - WINDOW.setProperty("Emby.nodes.%s.recentepisodes.title" %str(windowPropId),label) - path = "library://video/Emby - %s/%s_recent_episodes.xml"%(tagname_normalized,tagname_normalized) - WINDOW.setProperty("Emby.nodes.%s.recentepisodes.path" %str(windowPropId),"ActivateWindow(Video,%s,return)"%path) - WINDOW.setProperty("Emby.nodes.%s.recentepisodes.content" %str(windowPropId),path) - try: - etree.ElementTree(root).write(nodefile, xml_declaration=True) - except: - etree.ElementTree(root).write(nodefile) - - #create tag node - inprogress episodes - nodefile = os.path.join(libraryPath, tagname_normalized + "_progress_episodes.xml") - root = etree.Element("node", {"order":"4", "type":"filter"}) - label = language(30178) - etree.SubElement(root, "label").text = label - etree.SubElement(root, "match").text = "all" - etree.SubElement(root, "content").text = "episodes" - etree.SubElement(root, "icon").text = "special://home/addons/plugin.video.emby/icon.png" - Rule = etree.SubElement(root, "rule", {"field":"tag","operator":"is"}) - etree.SubElement(Rule, "value").text = tagname - #set limit to 25 --> currently hardcoded --> TODO: add a setting for this ? - etree.SubElement(root, "limit").text = "25" - Rule2 = etree.SubElement(root, "rule", {"field":"inprogress","operator":"true"}) - WINDOW.setProperty("Emby.nodes.%s.inprogressepisodes.title" %str(windowPropId),label) - path = "library://video/Emby - %s/%s_progress_episodes.xml"%(tagname_normalized,tagname_normalized) - WINDOW.setProperty("Emby.nodes.%s.inprogressepisodes.path" %str(windowPropId),"ActivateWindow(Video,%s,return)"%path) - WINDOW.setProperty("Emby.nodes.%s.inprogressepisodes.content" %str(windowPropId),path) - try: - etree.ElementTree(root).write(nodefile, xml_declaration=True) - except: - etree.ElementTree(root).write(nodefile) - - if kodiVersion == 14: - #create tag node - recent episodes - nodefile = os.path.join(libraryPath, tagname_normalized + "_recent_episodes.xml") - root = etree.Element("node", {"order":"4", "type":"folder"}) - label = language(30175) - etree.SubElement(root, "label").text = label - etree.SubElement(root, "content").text = "episodes" - etree.SubElement(root, "icon").text = "special://home/addons/plugin.video.emby/icon.png" - path = "plugin://plugin.video.emby/?id=%s&mode=recentepisodes&limit=25" %tagname - etree.SubElement(root, "path").text = path - WINDOW.setProperty("Emby.nodes.%s.recentepisodes.title" %str(windowPropId),label) - path = "library://video/Emby - %s/%s_recent_episodes.xml"%(tagname_normalized,tagname_normalized) - WINDOW.setProperty("Emby.nodes.%s.recentepisodes.path" %str(windowPropId),"ActivateWindow(Video,%s,return)"%path) - WINDOW.setProperty("Emby.nodes.%s.recentepisodes.content" %str(windowPropId),path) - try: - etree.ElementTree(root).write(nodefile, xml_declaration=True) - except: - etree.ElementTree(root).write(nodefile) - - #create tag node - inprogress items - nodefile = os.path.join(libraryPath, tagname_normalized + "_progress_episodes.xml") - root = etree.Element("node", {"order":"5", "type":"folder"}) - label = language(30178) - etree.SubElement(root, "label").text = label - etree.SubElement(root, "content").text = "episodes" - etree.SubElement(root, "icon").text = "special://home/addons/plugin.video.emby/icon.png" - path = "plugin://plugin.video.emby/?id=%s&mode=inprogressepisodes&limit=25" %tagname - etree.SubElement(root, "path").text = path - WINDOW.setProperty("Emby.nodes.%s.inprogressepisodes.title" %str(windowPropId),label) - path = "library://video/Emby - %s/%s_progress_episodes.xml"%(tagname_normalized,tagname_normalized) - WINDOW.setProperty("Emby.nodes.%s.inprogressepisodes.path" %str(windowPropId),"ActivateWindow(Video,%s,return)"%path) - WINDOW.setProperty("Emby.nodes.%s.inprogressepisodes.content" %str(windowPropId),path) - try: - etree.ElementTree(root).write(nodefile, xml_declaration=True) - except: - etree.ElementTree(root).write(nodefile) - - #create tag node - nextup items - #for nextup we always use the dynamic content approach with the plugin's entrypoint because it involves a custom query - nodefile = os.path.join(libraryPath, tagname_normalized + "_nextup_episodes.xml") - root = etree.Element("node", {"order":"6", "type":"folder"}) - label = language(30179) - etree.SubElement(root, "label").text = label - etree.SubElement(root, "content").text = "episodes" - path = "plugin://plugin.video.emby/?id=%s&mode=nextup&limit=25" %tagname - etree.SubElement(root, "path").text = path - etree.SubElement(root, "icon").text = "special://home/addons/plugin.video.emby/icon.png" - WINDOW.setProperty("Emby.nodes.%s.nextepisodes.title" %str(windowPropId),label) - path = "library://video/Emby - %s/%s_nextup_episodes.xml"%(tagname_normalized,tagname_normalized) - WINDOW.setProperty("Emby.nodes.%s.nextepisodes.path" %str(windowPropId),"ActivateWindow(Video,%s,return)"%path) - WINDOW.setProperty("Emby.nodes.%s.nextepisodes.content" %str(windowPropId),path) - try: - etree.ElementTree(root).write(nodefile, xml_declaration=True) - except: - etree.ElementTree(root).write(nodefile) - - def buildVideoNodesListing(self): - - try: - - # the library path doesn't exist on all systems - if not xbmcvfs.exists("special://profile/library/"): - xbmcvfs.mkdir("special://profile/library") - if not xbmcvfs.exists("special://profile/library/video/"): - #we need to copy over the default items - shutil.copytree(xbmc.translatePath("special://xbmc/system/library/video"), xbmc.translatePath("special://profile/library/video")) - - #always cleanup existing Emby video nodes first because we don't want old stuff to stay in there - path = "special://profile/library/video/" - if xbmcvfs.exists(path): - allDirs, allFiles = xbmcvfs.listdir(path) - for dir in allDirs: - if dir.startswith("Emby "): - shutil.rmtree(xbmc.translatePath("special://profile/library/video/" + dir)) - for file in allFiles: - if file.startswith("emby"): - xbmcvfs.delete(path + file) - - #we build up a listing and set window props for all nodes we created - #the window props will be used by the main entry point to quickly build up the listing and can be used in skins (like titan) too for quick reference - #comment marcelveldt: please leave the window props as-is because I will be referencing them in titan skin... - totalNodesCount = 0 - - #build the listing for all views - views_movies = ReadEmbyDB().getCollections("movies") - if views_movies: - for view in views_movies: - title = view.get('title') - content = view.get('content') - if content == "mixed": - title = "%s - Movies" % title - self.buildVideoNodeForView(title, "movies", totalNodesCount) - totalNodesCount +=1 - - views_shows = ReadEmbyDB().getCollections("tvshows") - if views_shows: - for view in views_shows: - title = view.get('title') - content = view.get('content') - if content == "mixed": - title = "%s - TV Shows" % title - self.buildVideoNodeForView(title, "tvshows", totalNodesCount) - totalNodesCount +=1 - - #create tag node for emby channels - nodefile = os.path.join(xbmc.translatePath("special://profile/library/video"), "emby_channels.xml") - root = etree.Element("node", {"order":"1", "type":"folder"}) - label = language(30173) - etree.SubElement(root, "label").text = label - etree.SubElement(root, "content").text = "movies" - etree.SubElement(root, "path").text = "plugin://plugin.video.emby/?id=0&mode=channels" - etree.SubElement(root, "icon").text = "special://home/addons/plugin.video.emby/icon.png" - WINDOW.setProperty("Emby.nodes.%s.title" %str(totalNodesCount),label) - WINDOW.setProperty("Emby.nodes.%s.type" %str(totalNodesCount),"channels") - path = "library://video/emby_channels.xml" - WINDOW.setProperty("Emby.nodes.%s.path" %str(totalNodesCount),"ActivateWindow(Video,%s,return)"%path) - WINDOW.setProperty("Emby.nodes.%s.content" %str(totalNodesCount),path) - totalNodesCount +=1 - try: - etree.ElementTree(root).write(nodefile, xml_declaration=True) - except: - etree.ElementTree(root).write(nodefile) - - #create tag node - favorite shows - nodefile = os.path.join(xbmc.translatePath("special://profile/library/video"),"emby_favorite_shows.xml") - root = etree.Element("node", {"order":"1", "type":"filter"}) - label = language(30181) - etree.SubElement(root, "label").text = label - etree.SubElement(root, "match").text = "all" - etree.SubElement(root, "content").text = "tvshows" - etree.SubElement(root, "icon").text = "special://home/addons/plugin.video.emby/icon.png" - etree.SubElement(root, "order", {"direction":"ascending"}).text = "sorttitle" - Rule = etree.SubElement(root, "rule", {"field":"tag","operator":"is"}) - etree.SubElement(Rule, "value").text = "Favorite tvshows" #do not localize the tagname itself - WINDOW.setProperty("Emby.nodes.%s.title" %str(totalNodesCount),label) - WINDOW.setProperty("Emby.nodes.%s.type" %str(totalNodesCount),"favourites") - path = "library://video/emby_favorite_shows.xml" - WINDOW.setProperty("Emby.nodes.%s.path" %str(totalNodesCount),"ActivateWindow(Video,%s,return)"%path) - WINDOW.setProperty("Emby.nodes.%s.content" %str(totalNodesCount),path) - totalNodesCount +=1 - try: - etree.ElementTree(root).write(nodefile, xml_declaration=True) - except: - etree.ElementTree(root).write(nodefile) - - #create tag node - favorite movies - nodefile = os.path.join(xbmc.translatePath("special://profile/library/video"),"emby_favorite_movies.xml") - root = etree.Element("node", {"order":"1", "type":"filter"}) - label = language(30180) - etree.SubElement(root, "label").text = label - etree.SubElement(root, "match").text = "all" - etree.SubElement(root, "content").text = "movies" - etree.SubElement(root, "icon").text = "special://home/addons/plugin.video.emby/icon.png" - etree.SubElement(root, "order", {"direction":"ascending"}).text = "sorttitle" - Rule = etree.SubElement(root, "rule", {"field":"tag","operator":"is"}) - etree.SubElement(Rule, "value").text = "Favorite movies" #do not localize the tagname itself - WINDOW.setProperty("Emby.nodes.%s.title" %str(totalNodesCount),label) - WINDOW.setProperty("Emby.nodes.%s.type" %str(totalNodesCount),"favourites") - path = "library://video/emby_favorite_movies.xml" - WINDOW.setProperty("Emby.nodes.%s.path" %str(totalNodesCount),"ActivateWindow(Video,%s,return)"%path) - WINDOW.setProperty("Emby.nodes.%s.content" %str(totalNodesCount),path) - totalNodesCount +=1 - try: - etree.ElementTree(root).write(nodefile, xml_declaration=True) - except: - etree.ElementTree(root).write(nodefile) - - WINDOW.setProperty("Emby.nodes.total", str(totalNodesCount)) - - - except Exception as e: - utils.logMsg("Emby addon","Error while creating videonodes listings, restart required ?") - print e \ No newline at end of file