def getAuthHeader(self, authenticate=True): clientInfo = ClientInformation() txt_mac = clientInfo.getMachineId() version = clientInfo.getVersion() deviceName = self.addonSettings.getSetting('deviceName') deviceName = deviceName.replace("\"", "_") if (authenticate == False): authString = "MediaBrowser Client=\"Kodi\",Device=\"" + deviceName + "\",DeviceId=\"" + txt_mac + "\",Version=\"" + version + "\"" headers = { "Accept-encoding": "gzip", "Accept-Charset": "UTF-8,*", "Authorization": authString } return headers else: userid = self.getUserId() authString = "MediaBrowser UserId=\"" + userid + "\",Client=\"Kodi\",Device=\"" + deviceName + "\",DeviceId=\"" + txt_mac + "\",Version=\"" + version + "\"" headers = { "Accept-encoding": "gzip", "Accept-Charset": "UTF-8,*", "Authorization": authString } authToken = self.authenticate() if (authToken != ""): headers["X-MediaBrowser-Token"] = authToken self.logMsg("Authentication Header : " + str(headers)) return headers
def postcapabilities(self): self.logMsg("postcapabilities called") # Set Capabilities mb3Port = self.addonSettings.getSetting('port') mb3Host = self.addonSettings.getSetting('ipaddress') clientInfo = ClientInformation() machineId = clientInfo.getMachineId() # get session id url = "http://" + mb3Host + ":" + mb3Port + "/mediabrowser/Sessions?DeviceId=" + machineId + "&format=json" self.logMsg("Session URL : " + url); jsonData = self.downloadUrl(url) self.logMsg("Session JsonData : " + jsonData) result = json.loads(jsonData) self.logMsg("Session JsonData : " + str(result)) sessionId = result[0].get("Id") self.logMsg("Session Id : " + str(sessionId)) # post capability data playableMediaTypes = "Audio,Video,Photo" supportedCommands = "Play,Playstate,DisplayContent,GoHome,SendString,GoToSettings,DisplayMessage,PlayNext" url = "http://" + mb3Host + ":" + mb3Port + "/mediabrowser/Sessions/Capabilities?Id=" + sessionId + "&PlayableMediaTypes=" + playableMediaTypes + "&SupportedCommands=" + supportedCommands + "&SupportsMediaControl=True" postData = {} #postData["Id"] = sessionId; #postData["PlayableMediaTypes"] = "Video"; #postData["SupportedCommands"] = "MoveUp"; stringdata = json.dumps(postData) self.logMsg("Capabilities URL : " + url); self.logMsg("Capabilities Data : " + stringdata) self.downloadUrl(url, postBody=stringdata, type="POST")
def stopAll(played_information): if(len(played_information) == 0): return addonSettings = xbmcaddon.Addon(id='plugin.video.xbmb3c') xbmc.log ("XBMB3C Service -> played_information : " + str(played_information)) for item_url in played_information: data = played_information.get(item_url) if(data != None): xbmc.log ("XBMB3C Service -> item_url : " + item_url) xbmc.log ("XBMB3C Service -> item_data : " + str(data)) watchedurl = data.get("watchedurl") positionurl = data.get("positionurl") deleteurl = data.get("deleteurl") runtime = data.get("runtime") currentPossition = data.get("currentPossition") item_id = data.get("item_id") if(currentPossition != None and hasData(runtime) and hasData(positionurl) and hasData(watchedurl)): runtimeTicks = int(runtime) xbmc.log ("XBMB3C Service -> runtimeticks:" + str(runtimeTicks)) percentComplete = (currentPossition * 10000000) / runtimeTicks markPlayedAt = float(addonSettings.getSetting("markPlayedAt")) / 100 xbmc.log ("XBMB3C Service -> Percent Complete:" + str(percentComplete) + " Mark Played At:" + str(markPlayedAt)) if (percentComplete > markPlayedAt): gotDeleted = 0 if(deleteurl != None and deleteurl != ""): xbmc.log ("XBMB3C Service -> Offering Delete:" + str(deleteurl)) gotDeleted = deleteItem(deleteurl) if(gotDeleted == 0): setPosition(positionurl + '/Progress?PositionTicks=0', 'POST') if(newWebSocketThread != None): newWebSocketThread.playbackStopped(item_id, str(0)) markWatched(watchedurl) else: #markUnWatched(watchedurl) # this resets the LastPlayedDate and that causes issues with sortby PlayedDate so I removed it for now if(newWebSocketThread != None): newWebSocketThread.playbackStopped(item_id, str(int(currentPossition * 10000000))) setPosition(positionurl + '?PositionTicks=' + str(int(currentPossition * 10000000)), 'DELETE') if(newNextUpThread != None): newNextUpThread.updateNextUp() if(artworkRotationThread != None): artworkRotationThread.updateActionUrls() played_information.clear() # stop transcoding - todo check we are actually transcoding? clientInfo = ClientInformation() txt_mac = clientInfo.getMachineId() url = ("http://%s:%s/mediabrowser/Videos/ActiveEncodings" % (addonSettings.getSetting('ipaddress'), addonSettings.getSetting('port'))) url = url + '?DeviceId=' + txt_mac stopTranscoding(url)
def postcapabilities(self): self.logMsg("postcapabilities called") # Set Capabilities mb3Port = self.addonSettings.getSetting('port') mb3Host = self.addonSettings.getSetting('ipaddress') clientInfo = ClientInformation() machineId = clientInfo.getMachineId() # get session id url = "http://" + mb3Host + ":" + mb3Port + "/mediabrowser/Sessions?DeviceId=" + machineId + "&format=json" self.logMsg("Session URL : " + url) jsonData = self.downloadUrl(url) self.logMsg("Session JsonData : " + jsonData) result = json.loads(jsonData) self.logMsg("Session JsonData : " + str(result)) sessionId = result[0].get("Id") self.logMsg("Session Id : " + str(sessionId)) # post capability data playableMediaTypes = "Audio,Video,Photo" supportedCommands = "Play,Playstate,DisplayContent,GoHome,SendString,GoToSettings,DisplayMessage,PlayNext" url = "http://" + mb3Host + ":" + mb3Port + "/mediabrowser/Sessions/Capabilities?Id=" + sessionId + "&PlayableMediaTypes=" + playableMediaTypes + "&SupportedCommands=" + supportedCommands + "&SupportsMediaControl=True" postData = {} #postData["Id"] = sessionId; #postData["PlayableMediaTypes"] = "Video"; #postData["SupportedCommands"] = "MoveUp"; stringdata = json.dumps(postData) self.logMsg("Capabilities URL : " + url) self.logMsg("Capabilities Data : " + stringdata) self.downloadUrl(url, postBody=stringdata, type="POST")
def stopAll(self): if(len(self.played_information) == 0): return addonSettings = xbmcaddon.Addon(id='plugin.video.xbmb3c') self.printDebug("XBMB3C Service -> played_information : " + str(self.played_information)) for item_url in self.played_information: data = self.played_information.get(item_url) if(data != None): self.printDebug("XBMB3C Service -> item_url : " + item_url) self.printDebug("XBMB3C Service -> item_data : " + str(data)) deleteurl = data.get("deleteurl") runtime = data.get("runtime") currentPossition = data.get("currentPossition") item_id = data.get("item_id") refresh_id = data.get("refresh_id") currentFile = data.get("currentfile") if(refresh_id != None): BackgroundDataUpdaterThread().updateItem(refresh_id) if(currentPossition != None and self.hasData(runtime)): runtimeTicks = int(runtime) self.printDebug("XBMB3C Service -> runtimeticks:" + str(runtimeTicks)) percentComplete = (currentPossition * 10000000) / runtimeTicks offerDeleteAt = float(addonSettings.getSetting("offerDeleteAt")) / 100 self.printDebug("XBMB3C Service -> Percent Complete:" + str(percentComplete) + " Mark Played At:" + str(offerDeleteAt)) self.stopPlayback(data) if (percentComplete > offerDeleteAt): gotDeleted = 0 if(deleteurl != None and deleteurl != ""): self.printDebug("XBMB3C Service -> Offering Delete:" + str(deleteurl)) gotDeleted = self.deleteItem(deleteurl) # update some of the display info if self.settings.getSetting('useNextUp') == "true": NextUpUpdaterThread().updateNextUp() if self.settings.getSetting('useBackgroundLoader') == "true": ArtworkRotationThread().updateActionUrls() self.played_information.clear() # stop transcoding - todo check we are actually transcoding? clientInfo = ClientInformation() txt_mac = clientInfo.getMachineId() url = ("http://%s:%s/mediabrowser/Videos/ActiveEncodings" % (addonSettings.getSetting('ipaddress'), addonSettings.getSetting('port'))) url = url + '?DeviceId=' + txt_mac self.downloadUtils.downloadUrl(url, type="DELETE")
def getPlayUrl(self, server, id, result): addonSettings = xbmcaddon.Addon(id='plugin.video.xbmb3c') # if the path is local and depending on the video quality play we can direct play it do so- xbmc.log("XBMB3C getPlayUrl") if self.isDirectPlay(result) == True: xbmc.log("XBMB3C getPlayUrl -> Direct Play") playurl = result.get("Path") if playurl != None: #We have a path to play so play it USER_AGENT = 'QuickTime/7.7.4' # If the file it is not a media stub if (result.get("IsPlaceHolder") != True): if (result.get("VideoType") == "Dvd"): playurl = playurl + "/VIDEO_TS/VIDEO_TS.IFO" elif (result.get("VideoType") == "BluRay"): playurl = playurl + "/BDMV/index.bdmv" if addonSettings.getSetting('smbusername') == '': playurl = playurl.replace("\\\\", "smb://") else: playurl = playurl.replace("\\\\", "smb://" + addonSettings.getSetting('smbusername') + ':' + addonSettings.getSetting('smbpassword') + '@') playurl = playurl.replace("\\", "/") if ("apple.com" in playurl): playurl += '?|User-Agent=%s' % USER_AGENT if addonSettings.getSetting('playFromStream') == "true": playurl = 'http://' + server + '/mediabrowser/Videos/' + id + '/stream?static=true' mediaSources = result.get("MediaSources") if(mediaSources != None): if mediaSources[0].get('DefaultAudioStreamIndex') != None: playurl = playurl + "&AudioStreamIndex=" +str(mediaSources[0].get('DefaultAudioStreamIndex')) if mediaSources[0].get('DefaultSubtitleStreamIndex') != None: playurl = playurl + "&SubtitleStreamIndex=" + str(mediaSources[0].get('DefaultAudioStreamIndex')) else: #No path or has a path but not sufficient network so transcode xbmc.log("XBMB3C getPlayUrl -> Transcode") if result.get("Type") == "Audio": playurl = 'http://' + server + '/mediabrowser/Audio/' + id + '/stream.mp3' else: clientInfo = ClientInformation() txt_mac = clientInfo.getMachineId() playurl = 'http://' + server + '/mediabrowser/Videos/' + id + '/master.m3u8?mediaSourceId=' + id playurl = playurl + '&videoCodec=h264' playurl = playurl + '&AudioCodec=aac,ac3' playurl = playurl + '&deviceId=' + txt_mac playurl = playurl + '&VideoBitrate=' + str(int(self.getVideoBitRate()) * 1000) mediaSources = result.get("MediaSources") if(mediaSources != None): if mediaSources[0].get('DefaultAudioStreamIndex') != None: playurl = playurl + "&AudioStreamIndex=" +str(mediaSources[0].get('DefaultAudioStreamIndex')) if mediaSources[0].get('DefaultSubtitleStreamIndex') != None: playurl = playurl + "&SubtitleStreamIndex=" + str(mediaSources[0].get('DefaultSubtitleStreamIndex')) return playurl.encode('utf-8')
def getAuthHeader(): addonSettings = xbmcaddon.Addon(id='plugin.video.xbmb3c') deviceName = addonSettings.getSetting('deviceName') deviceName = deviceName.replace("\"", "_") # might need to url encode this as it is getting added to the header and is user entered data clientInfo = ClientInformation() txt_mac = clientInfo.getMachineId() version = clientInfo.getVersion() userid = xbmcgui.Window( 10000 ).getProperty("userid") authString = "MediaBrowser UserId=\"" + userid + "\",Client=\"XBMC\",Device=\"" + deviceName + "\",DeviceId=\"" + txt_mac + "\",Version=\"" + version + "\"" headers = {'Accept-encoding': 'gzip', 'Authorization' : authString} xbmc.log("XBMB3C Authentication Header : " + str(headers)) return headers
def authenticate(self): WINDOW = xbmcgui.Window( 10000 ) token = WINDOW.getProperty("AccessToken"+self.addonSettings.getSetting('username')) if(token != None and token != ""): self.logMsg("DownloadUtils -> Returning saved AccessToken for user : "******" token: "+ token) return token port = self.addonSettings.getSetting("port") host = self.addonSettings.getSetting("ipaddress") if(host == None or host == "" or port == None or port == ""): return "" url = "http://" + self.addonSettings.getSetting("ipaddress") + ":" + self.addonSettings.getSetting("port") + "/mediabrowser/Users/AuthenticateByName?format=json" clientInfo = ClientInformation() txt_mac = clientInfo.getMachineId() version = clientInfo.getVersion() deviceName = self.addonSettings.getSetting('deviceName') deviceName = deviceName.replace("\"", "_") authString = "Mediabrowser Client=\"XBMC\",Device=\"" + deviceName + "\",DeviceId=\"" + txt_mac + "\",Version=\"" + version + "\"" headers = {'Accept-encoding': 'gzip', 'Authorization' : authString} if self.addonSettings.getSetting('password') !=None and self.addonSettings.getSetting('password') !='': sha1 = hashlib.sha1(self.addonSettings.getSetting('password')) sha1 = sha1.hexdigest() else: sha1 = 'da39a3ee5e6b4b0d3255bfef95601890afd80709' messageData = "username="******"&password="******"POST", authenticate=False) accessToken = None try: result = json.loads(resp) accessToken = result.get("AccessToken") except: pass if(accessToken != None): self.logMsg("User Authenticated : " + accessToken) WINDOW.setProperty("AccessToken"+self.addonSettings.getSetting('username'), accessToken) WINDOW.setProperty("userid", result.get("User").get("Id")) return accessToken else: self.logMsg("User NOT Authenticated") WINDOW.setProperty("AccessToken"+self.addonSettings.getSetting('username'), "") return ""
class WebSocketThread(threading.Thread): _shared_state = {} clientInfo = ClientInformation() KodiMonitor = KodiMonitor.Kodi_Monitor() addonName = clientInfo.getAddonName() client = None keepRunning = True def __init__(self, *args): self.__dict__ = self._shared_state threading.Thread.__init__(self, *args) def logMsg(self, msg, lvl=1): self.className = self.__class__.__name__ utils.logMsg("%s %s" % (self.addonName, self.className), msg, int(lvl)) def sendProgressUpdate(self, data): self.logMsg("sendProgressUpdate", 1) if self.client: try: # Send progress update messageData = { 'MessageType': "ReportPlaybackProgress", 'Data': data } messageString = json.dumps(messageData) self.client.send(messageString) self.logMsg("Message data: %s" % messageString, 2) except Exception, e: self.logMsg("Exception: %s" % e, 1)
def stopAll(self): self.ws.processPendingActions() if(len(self.played_information) == 0): return addonSettings = xbmcaddon.Addon(id='plugin.video.emby') self.logMsg("emby Service -> played_information : " + str(self.played_information)) for item_url in self.played_information: data = self.played_information.get(item_url) if (data is not None): self.logMsg("emby Service -> item_url : " + item_url) self.logMsg("emby Service -> item_data : " + str(data)) runtime = data.get("runtime") currentPosition = data.get("currentPosition") item_id = data.get("item_id") refresh_id = data.get("refresh_id") currentFile = data.get("currentfile") type = data.get("Type") if(currentPosition != None and self.hasData(runtime)): runtimeTicks = int(runtime) self.logMsg("emby Service -> runtimeticks:" + str(runtimeTicks)) percentComplete = (currentPosition * 10000000) / runtimeTicks markPlayedAt = float(90) / 100 self.logMsg("emby Service -> Percent Complete:" + str(percentComplete) + " Mark Played At:" + str(markPlayedAt)) self.stopPlayback(data) if(refresh_id != None): #report updates playcount and resume status to Kodi and MB3 librarySync.updatePlayCount(item_id) self.played_information.clear() # stop transcoding - todo check we are actually transcoding? clientInfo = ClientInformation() txt_mac = clientInfo.getMachineId() url = "{server}/mediabrowser/Videos/ActiveEncodings" url = url + '?DeviceId=' + txt_mac self.doUtils.downloadUrl(url, type="DELETE")
def on_open(self, ws): try: clientInfo = ClientInformation() machineId = clientInfo.getMachineId() version = clientInfo.getVersion() messageData = {} messageData["MessageType"] = "Identity" addonSettings = xbmcaddon.Addon(id='plugin.video.xbmb3c') deviceName = addonSettings.getSetting('deviceName') deviceName = deviceName.replace("\"", "_") messageData["Data"] = "XBMC|" + machineId + "|" + version + "|" + deviceName messageString = json.dumps(messageData) self.logMsg("Opened : " + str(messageString)) ws.send(messageString) except Exception, e: self.logMsg("Exception : " + str(e), level=0)
def on_open(self, ws): try: clientInfo = ClientInformation() machineId = clientInfo.getMachineId() version = clientInfo.getVersion() messageData = {} messageData["MessageType"] = "Identity" addonSettings = xbmcaddon.Addon(id='plugin.video.xbmb3c') deviceName = addonSettings.getSetting('deviceName') deviceName = deviceName.replace("\"", "_") messageData["Data"] = "Kodi|" + machineId + "|" + version + "|" + deviceName messageString = json.dumps(messageData) self.logMsg("Opened : " + str(messageString)) ws.send(messageString) except Exception, e: self.logMsg("Exception : " + str(e), level=0)
def on_open(self, ws): clientInfo = ClientInformation() machineId = clientInfo.getMachineId() version = clientInfo.getVersion() messageData = {} messageData["MessageType"] = "Identity" addonSettings = xbmcaddon.Addon(id='plugin.video.xbmb3c') deviceName = addonSettings.getSetting('deviceName') deviceName = deviceName.replace("\"", "_") messageData["Data"] = "Kodi|" + machineId + "|" + version + "|" + deviceName messageString = json.dumps(messageData) self.logMsg("Opened : " + str(messageString)) ws.send(messageString) # Set Capabilities xbmc.log("postcapabilities_called") downloadUtils = DownloadUtils() downloadUtils.postcapabilities()
def on_open(self, ws): clientInfo = ClientInformation() machineId = clientInfo.getMachineId() version = clientInfo.getVersion() messageData = {} messageData["MessageType"] = "Identity" addonSettings = xbmcaddon.Addon(id='plugin.video.xbmb3c') deviceName = addonSettings.getSetting('deviceName') deviceName = deviceName.replace("\"", "_") messageData[ "Data"] = "Kodi|" + machineId + "|" + version + "|" + deviceName messageString = json.dumps(messageData) self.logMsg("Opened : " + str(messageString)) ws.send(messageString) # Set Capabilities xbmc.log("postcapabilities_called") downloadUtils = DownloadUtils() downloadUtils.postcapabilities()
def getAuthHeader(self, authenticate=True): clientInfo = ClientInformation() txt_mac = clientInfo.getMachineId() version = clientInfo.getVersion() deviceName = self.addonSettings.getSetting('deviceName') deviceName = deviceName.replace("\"", "_") if(authenticate == False): authString = "MediaBrowser Client=\"Kodi\",Device=\"" + deviceName + "\",DeviceId=\"" + txt_mac + "\",Version=\"" + version + "\"" headers = {"Accept-encoding": "gzip", "Accept-Charset" : "UTF-8,*", "Authorization" : authString} return headers else: userid = self.getUserId() authString = "MediaBrowser UserId=\"" + userid + "\",Client=\"Kodi\",Device=\"" + deviceName + "\",DeviceId=\"" + txt_mac + "\",Version=\"" + version + "\"" headers = {"Accept-encoding": "gzip", "Accept-Charset" : "UTF-8,*", "Authorization" : authString} authToken = self.authenticate() if(authToken != ""): headers["X-MediaBrowser-Token"] = authToken self.logMsg("Authentication Header : " + str(headers)) return headers
def run(self): WINDOW = xbmcgui.Window(10000) logLevel = int(WINDOW.getProperty('getLogLevel')) username = WINDOW.getProperty('currUser') server = WINDOW.getProperty('server%s' % username) token = WINDOW.getProperty('accessToken%s' % username) deviceId = ClientInformation().getMachineId() '''if (logLevel == 2): websocket.enableTrace(True)''' # Get the appropriate prefix for websocket if "https" in server: server = server.replace('https', 'wss') else: server = server.replace('http', 'ws') websocketUrl = "%s?api_key=%s&deviceId=%s" % (server, token, deviceId) self.logMsg("websocket URL: %s" % websocketUrl) self.client = websocket.WebSocketApp(websocketUrl, on_message=self.on_message, on_error=self.on_error, on_close=self.on_close) self.client.on_open = self.on_open while self.keepRunning: self.client.run_forever() if self.keepRunning: self.logMsg("Client Needs To Restart", 2) if self.KodiMonitor.waitForAbort(5): break self.logMsg("Thread Exited", 1)
def authenticate(self, retreive=True): WINDOW = xbmcgui.Window(10000) self.addonSettings = xbmcaddon.Addon(id='plugin.video.xbmb3c') username = self.addonSettings.getSetting('username') token = WINDOW.getProperty("AccessToken" + username) if(token != None and token != ""): self.logMsg("DownloadUtils -> Returning saved (WINDOW) AccessToken for user:"******" token:" + token) return token token = self.addonSettings.getSetting("AccessToken" + username) if(token != None and token != ""): WINDOW.setProperty("AccessToken" + username, token) self.logMsg("DownloadUtils -> Returning saved (SETTINGS) AccessToken for user:"******" token:" + token) return token port = self.addonSettings.getSetting("port") host = self.addonSettings.getSetting("ipaddress") if(host == None or host == "" or host == "<none>" or port == None or port == ""): return "" if(retreive == False): return "" url = "http://" + host + ":" + port + "/mediabrowser/Users/AuthenticateByName?format=json" clientInfo = ClientInformation() txt_mac = clientInfo.getMachineId() version = clientInfo.getVersion() # get user info jsonData = self.downloadUrl("http://" + host + ":" + port + "/mediabrowser/Users/Public?format=json", authenticate=False) users = [] if(jsonData != ""): users = json.loads(jsonData) userHasPassword = False for user in users: name = user.get("Name") if(username == name): if(user.get("HasPassword") == True): userHasPassword = True break password = "" if(userHasPassword): password = xbmcgui.Dialog().input("Enter Password for user : "******""): sha1 = hashlib.sha1(password) sha1 = sha1.hexdigest() else: sha1 = 'da39a3ee5e6b4b0d3255bfef95601890afd80709' messageData = "username="******"&password="******"POST", authenticate=False) result = None accessToken = None try: xbmc.log("Auth_Reponce: " + str(resp)) result = json.loads(resp) accessToken = result.get("AccessToken") except: pass if(result != None and accessToken != None): userID = result.get("User").get("Id") self.logMsg("User Authenticated : " + accessToken) WINDOW.setProperty("AccessToken" + username, accessToken) WINDOW.setProperty("userid" + username, userID) self.addonSettings.setSetting("AccessToken" + username, accessToken) self.addonSettings.setSetting("userid" + username, userID) return accessToken else: self.logMsg("User NOT Authenticated") WINDOW.setProperty("AccessToken" + username, "") WINDOW.setProperty("userid" + username, "") self.addonSettings.setSetting("AccessToken" + username, "") self.addonSettings.setSetting("userid" + username, "") return ""
class Player( xbmc.Player ): # Borg - multiple instances, shared state _shared_state = {} xbmcplayer = xbmc.Player() doUtils = DownloadUtils() clientInfo = ClientInformation() ws = WebSocketThread() addonName = clientInfo.getAddonName() addonId = clientInfo.getAddonId() addon = xbmcaddon.Addon(id=addonId) WINDOW = xbmcgui.Window(10000) logLevel = 0 played_information = {} settings = None playStats = {} def __init__( self, *args ): self.__dict__ = self._shared_state self.logMsg("Starting playback monitor service", 1) def logMsg(self, msg, lvl=1): self.className = self.__class__.__name__ utils.logMsg("%s %s" % (self.addonName, self.className), msg, int(lvl)) def hasData(self, data): if(data == None or len(data) == 0 or data == "None"): return False else: return True def stopAll(self): self.ws.processPendingActions() if(len(self.played_information) == 0): return addonSettings = xbmcaddon.Addon(id='plugin.video.emby') self.logMsg("emby Service -> played_information : " + str(self.played_information)) for item_url in self.played_information: data = self.played_information.get(item_url) if (data is not None): self.logMsg("emby Service -> item_url : " + item_url) self.logMsg("emby Service -> item_data : " + str(data)) runtime = data.get("runtime") currentPosition = data.get("currentPosition") item_id = data.get("item_id") refresh_id = data.get("refresh_id") currentFile = data.get("currentfile") type = data.get("Type") if(currentPosition != None and self.hasData(runtime)): runtimeTicks = int(runtime) self.logMsg("emby Service -> runtimeticks:" + str(runtimeTicks)) percentComplete = (currentPosition * 10000000) / runtimeTicks markPlayedAt = float(90) / 100 self.logMsg("emby Service -> Percent Complete:" + str(percentComplete) + " Mark Played At:" + str(markPlayedAt)) self.stopPlayback(data) if(refresh_id != None): #report updates playcount and resume status to Kodi and MB3 librarySync.updatePlayCount(item_id) self.played_information.clear() # stop transcoding - todo check we are actually transcoding? clientInfo = ClientInformation() txt_mac = clientInfo.getMachineId() url = "{server}/mediabrowser/Videos/ActiveEncodings" url = url + '?DeviceId=' + txt_mac self.doUtils.downloadUrl(url, type="DELETE") def stopPlayback(self, data): self.logMsg("stopPlayback called", 2) item_id = data.get("item_id") audioindex = data.get("AudioStreamIndex") subtitleindex = data.get("SubtitleStreamIndex") playMethod = data.get("playmethod") currentPosition = data.get("currentPosition") positionTicks = int(currentPosition * 10000000) url = "{server}/mediabrowser/Sessions/Playing/Stopped" postdata = { 'QueueableMediaTypes': "Video", 'CanSeek': True, 'ItemId': item_id, 'MediaSourceId': item_id, 'PlayMethod': playMethod, 'PositionTicks': positionTicks } if audioindex: postdata['AudioStreamIndex'] = audioindex if subtitleindex: postdata['SubtitleStreamIndex'] = subtitleindex self.doUtils.downloadUrl(url, postBody=postdata, type="POST") def reportPlayback(self): self.logMsg("reportPlayback Called", 2) xbmcplayer = self.xbmcplayer currentFile = xbmcplayer.getPlayingFile() data = self.played_information.get(currentFile) # only report playback if emby has initiated the playback (item_id has value) if (data is not None) and (data.get("item_id") is not None): # Get playback information item_id = data.get("item_id") audioindex = data.get("AudioStreamIndex") subtitleindex = data.get("SubtitleStreamIndex") playTime = data.get("currentPosition") playMethod = data.get("playmethod") paused = data.get("paused") if paused is None: paused = False #url = "{server}/mediabrowser/Sessions/Playing/Progress" postdata = { 'QueueableMediaTypes': "Video", 'CanSeek': True, 'ItemId': item_id, 'MediaSourceId': item_id, 'IsPaused': paused, 'PlayMethod': playMethod } if playTime: postdata['PositionTicks'] = int(playTime * 10000000) if audioindex: postdata['AudioStreamIndex'] = audioindex if subtitleindex: postdata['SubtitleStreamIndex'] = subtitleindex postdata = json.dumps(postdata) self.logMsg("Report: %s" % postdata) self.ws.sendProgressUpdate(postdata) def onPlayBackPaused( self ): currentFile = xbmc.Player().getPlayingFile() self.logMsg("PLAYBACK_PAUSED : " + currentFile,2) if(self.played_information.get(currentFile) != None): self.played_information[currentFile]["paused"] = "true" self.reportPlayback() def onPlayBackResumed( self ): currentFile = xbmc.Player().getPlayingFile() self.logMsg("PLAYBACK_RESUMED : " + currentFile,2) if(self.played_information.get(currentFile) != None): self.played_information[currentFile]["paused"] = "false" self.reportPlayback() def onPlayBackSeek( self, time, seekOffset ): self.logMsg("PLAYBACK_SEEK",2) self.reportPlayback() def onPlayBackStarted( self ): # Will be called when xbmc starts playing a file WINDOW = self.WINDOW xbmcplayer = self.xbmcplayer self.stopAll() if xbmcplayer.isPlaying(): currentFile = xbmcplayer.getPlayingFile() self.logMsg("onPlayBackStarted: %s" % currentFile, 0) # we may need to wait until the info is available item_id = WINDOW.getProperty(currentFile + "item_id") tryCount = 0 while(item_id == None or item_id == ""): xbmc.sleep(500) item_id = WINDOW.getProperty(currentFile + "item_id") tryCount += 1 if(tryCount == 20): # try 20 times or about 10 seconds return xbmc.sleep(500) # grab all the info about this item from the stored windows props # only ever use the win props here, use the data map in all other places runtime = WINDOW.getProperty(currentFile + "runtimeticks") refresh_id = WINDOW.getProperty(currentFile + "refresh_id") audioindex = WINDOW.getProperty(currentFile + "AudioStreamIndex") subtitleindex = WINDOW.getProperty(currentFile + "SubtitleStreamIndex") playMethod = WINDOW.getProperty(currentFile + "playmethod") itemType = WINDOW.getProperty(currentFile + "type") seekTime = WINDOW.getProperty(currentFile + "seektime") username = WINDOW.getProperty('currUser') sessionId = WINDOW.getProperty('sessionId%s' % username) if seekTime != "": PlaybackUtils().seekToPosition(int(seekTime)) if (not item_id) or (len(item_id) == 0): self.logMsg("onPlayBackStarted: No info for current playing file", 0) return url = "{server}/mediabrowser/Sessions/Playing" postdata = { 'QueueableMediaTypes': "Video", 'CanSeek': True, 'ItemId': item_id, 'MediaSourceId': item_id, 'PlayMethod': playMethod } if audioindex: postdata['AudioStreamIndex'] = audioindex if subtitleindex: postdata['SubtitleStreamIndex'] = subtitleindex self.logMsg("Sending POST play started.", 1) #self.logMsg("emby Service -> Sending Post Play Started : " + url, 0) self.doUtils.downloadUrl(url, postBody=postdata, type="POST") # save data map for updates and position calls data = {} data["runtime"] = runtime data["item_id"] = item_id data["refresh_id"] = refresh_id data["currentfile"] = currentFile data["AudioStreamIndex"] = audioindex data["SubtitleStreamIndex"] = subtitleindex data["playmethod"] = playMethod data["Type"] = itemType self.played_information[currentFile] = data self.logMsg("emby Service -> ADDING_FILE : " + currentFile, 0) self.logMsg("emby Service -> ADDING_FILE : " + str(self.played_information), 0) # 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 # reset in progress position self.reportPlayback() def GetPlayStats(self): return self.playStats def onPlayBackEnded( self ): # Will be called when xbmc stops playing a file self.logMsg("onPlayBackEnded", 0) #workaround when strm files are launched through the addon - mark watched when finished playing #TODO --> mark watched when 95% is played of the file WINDOW = xbmcgui.Window( 10000 ) if WINDOW.getProperty("virtualstrm") != "": try: id = WINDOW.getProperty("virtualstrm") type = WINDOW.getProperty("virtualstrmtype") watchedurl = "{server}/mediabrowser/Users/{UserId}/PlayedItems/%s" % id self.doUtils.downloadUrl(watchedurl, postBody="", type="POST") librarySync.updatePlayCount(id) except: pass WINDOW.clearProperty("virtualstrm") self.stopAll() def onPlayBackStopped( self ): # Will be called when user stops xbmc playing a file self.logMsg("onPlayBackStopped", 0) self.stopAll() def autoPlayPlayback(self): currentFile = xbmc.Player().getPlayingFile() data = self.played_information.get(currentFile) # only report playback if emby has initiated the playback (item_id has value) if(data != None and data.get("item_id") != None): addonSettings = xbmcaddon.Addon(id='plugin.video.emby') item_id = data.get("item_id") type = data.get("Type") # if its an episode see if autoplay is enabled if addonSettings.getSetting("autoPlaySeason")=="true" and type=="Episode": WINDOW = xbmcgui.Window( 10000 ) username = WINDOW.getProperty('currUser') userid = WINDOW.getProperty('userId%s' % username) server = WINDOW.getProperty('server%s' % username) # add remaining unplayed episodes if applicable MB3Episode = ReadEmbyDB().getItem(item_id) userData = MB3Episode["UserData"] if userData!=None and userData["Played"]==True: pDialog = xbmcgui.DialogProgress() seasonId = MB3Episode["SeasonId"] url = "{server}/mediabrowser/Users/{UserId}/Items?ParentId=%s&ImageTypeLimit=1&Limit=1&SortBy=SortName&SortOrder=Ascending&Filters=IsUnPlayed&IncludeItemTypes=Episode&IsVirtualUnaired=false&Recursive=true&IsMissing=False&format=json" % seasonId jsonData = self.doUtils.downloadUrl(url) if(jsonData != ""): seasonData = json.loads(jsonData) if seasonData.get("Items") != None: item = seasonData.get("Items")[0] pDialog.create("Auto Play next episode", str(item.get("ParentIndexNumber")) + "x" + str(item.get("IndexNumber")) + ". " + item["Name"] + " found","Cancel to stop automatic play") count = 0 while(pDialog.iscanceled()==False and count < 10): xbmc.sleep(1000) count += 1 progress = count * 10 remainingsecs = 10 - count pDialog.update(progress, str(item.get("ParentIndexNumber")) + "x" + str(item.get("IndexNumber")) + ". " + item["Name"] + " found","Cancel to stop automatic play", str(remainingsecs) + " second(s) until auto dismiss") pDialog.close() if pDialog.iscanceled()==False: playTime = xbmc.Player().getTime() totalTime = xbmc.Player().getTotalTime() while xbmc.Player().isPlaying() and (totalTime-playTime > 2): xbmc.sleep(500) playTime = xbmc.Player().getTime() totalTime = xbmc.Player().getTotalTime() PlaybackUtils().PLAYAllEpisodes(seasonData.get("Items"))
class Player(xbmc.Player): # Borg - multiple instances, shared state _shared_state = {} xbmcplayer = xbmc.Player() clientInfo = ClientInformation() addonName = clientInfo.getAddonName() addonId = clientInfo.getAddonId() addon = xbmcaddon.Addon(id=addonId) episode = None nextUpPage = None stillWatchingPage = None logLevel = 0 totalTime = 0 currenttvshowid = None currentepisodeid = None playedinarow = 1 playbackonended = False fields_base = '"dateadded", "file", "lastplayed","plot", "title", "art", "playcount",' fields_file = fields_base + '"streamdetails", "director", "resume", "runtime",' fields_tvshows = fields_base + '"sorttitle", "mpaa", "premiered", "year", "episode", "watchedepisodes", "votes", "rating", "studio", "season", "genre", "episodeguide", "tag", "originaltitle", "imdbnumber"' fields_episodes = fields_file + '"cast", "productioncode", "rating", "votes", "episode", "showtitle", "tvshowid", "season", "firstaired", "writer", "originaltitle"' def __init__(self, *args): self.__dict__ = self._shared_state self.logMsg("Starting playback monitor service", 1) def logMsg(self, msg, lvl=1): self.className = self.__class__.__name__ utils.logMsg("%s %s" % (self.addonName, self.className), msg, int(lvl)) def json_query(self, query, ret): try: xbmc_request = json.dumps(query) result = xbmc.executeJSONRPC(xbmc_request) result = unicode(result, 'utf-8', errors='ignore') if ret: return json.loads(result)['result'] else: return json.loads(result) except: xbmc_request = json.dumps(query) result = xbmc.executeJSONRPC(xbmc_request) result = unicode(result, 'utf-8', errors='ignore') self.logMsg(json.loads(result), 1) return json.loads(result) def onPlayBackStarted(self): # Will be called when kodi starts playing a file self.playbackonended = False self.episode = None WINDOW = xbmcgui.Window(10000) WINDOW.clearProperty("NextUpNotification.NowPlaying.DBID") WINDOW.clearProperty("NextUpNotification.NowPlaying.Type") # Get the active player result = xbmc.executeJSONRPC( '{"jsonrpc": "2.0", "id": 1, "method": "Player.GetActivePlayers"}') result = unicode(result, 'utf-8', errors='ignore') self.logMsg("Got active player " + result, 2) result = json.loads(result) # Seems to work too fast loop whilst waiting for it to become active while not result["result"]: result = xbmc.executeJSONRPC( '{"jsonrpc": "2.0", "id": 1, "method": "Player.GetActivePlayers"}' ) result = unicode(result, 'utf-8', errors='ignore') self.logMsg("Got active player " + result, 2) result = json.loads(result) if 'result' in result and result["result"][0] is not None: playerid = result["result"][0]["playerid"] # Get details of the playing media self.logMsg("Getting details of now playing media", 1) result = xbmc.executeJSONRPC( '{"jsonrpc": "2.0", "id": 1, "method": "Player.GetItem", "params": {"playerid": ' + str(playerid) + ', "properties": ["showtitle", "tvshowid", "episode", "season", "playcount","genre"] } }' ) result = unicode(result, 'utf-8', errors='ignore') self.logMsg("Got details of now playing media" + result, 2) result = json.loads(result) if 'result' in result: itemtype = result["result"]["item"]["type"] if itemtype == "episode": WINDOW.setProperty("NextUpNotification.NowPlaying.Type", itemtype) tvshowid = result["result"]["item"]["tvshowid"] WINDOW.setProperty("NextUpNotification.NowPlaying.DBID", str(tvshowid)) elif itemtype == "movie": WINDOW.setProperty("NextUpNotification.NowPlaying.Type", itemtype) id = result["result"]["item"]["id"] WINDOW.setProperty("NextUpNotification.NowPlaying.DBID", str(id)) def iStream_fix(self, show_npid, showtitle, episode_np, season_np): # streams from iStream dont provide the showid and epid for above # they come through as tvshowid = -1, but it has episode no and season no and show name # need to insert work around here to get showid from showname, and get epid from season and episode no's # then need to ignore prevcheck self.logMsg('fixing strm, data follows...') self.logMsg('show_npid = ' + str(show_npid)) self.logMsg('showtitle = ' + str(showtitle)) self.logMsg('episode_np = ' + str(episode_np)) self.logMsg('season_np = ' + str(season_np)) show_request_all = { "jsonrpc": "2.0", "method": "VideoLibrary.GetTVShows", "params": { "properties": ["title"] }, "id": "1" } eps_query = { "jsonrpc": "2.0", "method": "VideoLibrary.GetEpisodes", "params": { "properties": [ "season", "episode", "runtime", "resume", "playcount", "tvshowid", "lastplayed", "file" ], "tvshowid": "1" }, "id": "1" } ep_npid = " " redo = True count = 0 while redo and count < 2: # this ensures the section of code only runs twice at most [ only runs once fine ? redo = False count += 1 if show_npid == -1 and showtitle and episode_np and season_np: tmp_shows = self.json_query(show_request_all, True) self.logMsg('tmp_shows = ' + str(tmp_shows)) if 'tvshows' in tmp_shows: for x in tmp_shows['tvshows']: if x['label'] == showtitle: show_npid = x['tvshowid'] eps_query['params']['tvshowid'] = show_npid tmp_eps = self.json_query(eps_query, True) self.logMsg('tmp eps = ' + str(tmp_eps)) if 'episodes' in tmp_eps: for y in tmp_eps['episodes']: if (y['season']) == season_np and ( y['episode']) == episode_np: ep_npid = y['episodeid'] self.logMsg('playing epid stream = ' + str(ep_npid)) return show_npid, ep_npid def findNextEpisode(self, result, currentFile, includeWatched): self.logMsg("Find next episode called", 1) position = 0 for episode in result["result"]["episodes"]: # find position of current episode if self.currentepisodeid == episode["episodeid"]: # found a match so add 1 for the next and get out of here position += 1 break position += 1 # check if it may be a multi-part episode while result["result"]["episodes"][position]["file"] == currentFile: position += 1 # skip already watched episodes? while not includeWatched and result["result"]["episodes"][position][ "playcount"] > 1: position += 1 # now return the episode self.logMsg( "Find next episode found next episode in position: " + str(position), 1) try: episode = result["result"]["episodes"][position] except: # no next episode found episode = None return episode def displayRandomUnwatched(self): currentFile = xbmc.Player().getPlayingFile() # Get the active player result = xbmc.executeJSONRPC( '{"jsonrpc": "2.0", "id": 1, "method": "Player.GetActivePlayers"}') result = unicode(result, 'utf-8', errors='ignore') self.logMsg("Got active player " + result, 2) result = json.loads(result) # Seems to work too fast loop whilst waiting for it to become active while not result["result"]: result = xbmc.executeJSONRPC( '{"jsonrpc": "2.0", "id": 1, "method": "Player.GetActivePlayers"}' ) result = unicode(result, 'utf-8', errors='ignore') self.logMsg("Got active player " + result, 2) result = json.loads(result) if 'result' in result and result["result"][0] is not None: playerid = result["result"][0]["playerid"] # Get details of the playing media self.logMsg("Getting details of playing media", 1) result = xbmc.executeJSONRPC( '{"jsonrpc": "2.0", "id": 1, "method": "Player.GetItem", "params": {"playerid": ' + str(playerid) + ', "properties": ["showtitle", "tvshowid", "episode", "season", "playcount","genre"] } }' ) result = unicode(result, 'utf-8', errors='ignore') self.logMsg("Got details of playing media" + result, 2) result = json.loads(result) if 'result' in result: itemtype = result["result"]["item"]["type"] if itemtype == "episode": # playing an episode so find a random unwatched show from the same genre genres = result["result"]["item"]["genre"] if genres: genretitle = genres[0] self.logMsg( "Looking up tvshow for genre " + genretitle, 2) tvshow = utils.getJSON( 'VideoLibrary.GetTVShows', '{ "sort": { "order": "descending", "method": "random" }, "filter": {"and": [{"operator":"is", "field":"genre", "value":"%s"}, {"operator":"is", "field":"playcount", "value":"0"}]}, "properties": [ %s ],"limits":{"end":1} }' % (genretitle, self.fields_tvshows)) if not tvshow: self.logMsg("Looking up tvshow without genre", 2) tvshow = utils.getJSON( 'VideoLibrary.GetTVShows', '{ "sort": { "order": "descending", "method": "random" }, "filter": {"and": [{"operator":"is", "field":"playcount", "value":"0"}]}, "properties": [ %s ],"limits":{"end":1} }' % self.fields_tvshows) self.logMsg("Got tvshow" + str(tvshow), 2) tvshowid = tvshow[0]["tvshowid"] episode = utils.getJSON( 'VideoLibrary.GetEpisodes', '{ "tvshowid": %d, "sort": {"method":"episode"}, "filter": {"and": [ {"field": "playcount", "operator": "lessthan", "value":"1"}, {"field": "season", "operator": "greaterthan", "value": "0"} ]}, "properties": [ %s ], "limits":{"end":1}}' % (tvshowid, self.fields_episodes)) if episode: self.logMsg( "Got details of next up episode %s" % str(episode), 2) addonSettings = xbmcaddon.Addon( id='service.nextup.notification') unwatchedPage = UnwatchedInfo( "script-nextup-notification-UnwatchedInfo.xml", addonSettings.getAddonInfo('path'), "default", "1080i") unwatchedPage.setItem(episode[0]) self.logMsg("Calling display unwatched", 2) unwatchedPage.show() xbmc.sleep(10000) self.logMsg("Calling close unwatched", 2) unwatchedPage.close() def strm_query(self, result): try: self.logMsg('strm_query start') Myitemtype = result["result"]["item"]["type"] if Myitemtype == "episode": return True Myepisodenumber = result["result"]["item"]["episode"] Myseasonid = result["result"]["item"]["season"] Mytvshowid = result["result"]["item"]["tvshowid"] Myshowtitle = result["result"]["item"]["showtitle"] self.logMsg('strm_query end') if Mytvshowid == -1: return True else: return False except: self.logMsg('strm_query except') return False def autoPlayPlayback(self): currentFile = xbmc.Player().getPlayingFile() # Get the active player result = xbmc.executeJSONRPC( '{"jsonrpc": "2.0", "id": 1, "method": "Player.GetActivePlayers"}') result = unicode(result, 'utf-8', errors='ignore') self.logMsg("Got active player " + result, 2) result = json.loads(result) # Seems to work too fast loop whilst waiting for it to become active while not result["result"]: result = xbmc.executeJSONRPC( '{"jsonrpc": "2.0", "id": 1, "method": "Player.GetActivePlayers"}' ) result = unicode(result, 'utf-8', errors='ignore') self.logMsg("Got active player " + result, 2) result = json.loads(result) if 'result' in result and result["result"][0] is not None: playerid = result["result"][0]["playerid"] # Get details of the playing media self.logMsg("Getting details of playing media", 1) result = xbmc.executeJSONRPC( '{"jsonrpc": "2.0", "id": 1, "method": "Player.GetItem", "params": {"playerid": ' + str(playerid) + ', "properties": ["showtitle", "tvshowid", "episode", "season", "playcount"] } }' ) result = unicode(result, 'utf-8', errors='ignore') self.logMsg("Got details of playing media" + result, 2) result = json.loads(result) if 'result' in result: itemtype = result["result"]["item"]["type"] if self.strm_query(result): addonSettings = xbmcaddon.Addon( id='service.nextup.notification') playMode = addonSettings.getSetting("autoPlayMode") currentepisodenumber = result["result"]["item"]["episode"] currentseasonid = result["result"]["item"]["season"] currentshowtitle = result["result"]["item"]["showtitle"] tvshowid = result["result"]["item"]["tvshowid"] shortplayMode = addonSettings.getSetting("shortPlayMode") shortplayNotification = addonSettings.getSetting( "shortPlayNotification") shortplayLength = int( addonSettings.getSetting("shortPlayLength")) * 60 if (itemtype == "episode"): # Get the next up episode currentepisodeid = result["result"]["item"]["id"] elif tvshowid == -1: # I am a STRM ### tvshowid, episodeid = self.iStream_fix( tvshowid, currentshowtitle, currentepisodenumber, currentseasonid) currentepisodeid = episodeid else: # wtf am i doing here error.. #### self.logMsg("Error: cannot determine if episode", 1) return self.currentepisodeid = currentepisodeid self.logMsg( "Getting details of next up episode for tvshow id: " + str(tvshowid), 1) if self.currenttvshowid != tvshowid: self.currenttvshowid = tvshowid self.playedinarow = 1 result = xbmc.executeJSONRPC( '{"jsonrpc": "2.0", "method": "VideoLibrary.GetEpisodes", "params": {"tvshowid": %d, ' '"properties": [ "title", "playcount", "season", "episode", "showtitle", "plot", ' '"file", "rating", "resume", "tvshowid", "art", "firstaired", "runtime", "writer", ' '"dateadded", "lastplayed" , "streamdetails"], "sort": {"method": "episode"}}, "id": 1}' % tvshowid) if result: result = unicode(result, 'utf-8', errors='ignore') result = json.loads(result) self.logMsg("Got details of next up episode %s" % str(result), 2) xbmc.sleep(100) # Find the next unwatched and the newest added episodes if "result" in result and "episodes" in result["result"]: includeWatched = addonSettings.getSetting( "includeWatched") == "true" episode = self.findNextEpisode(result, currentFile, includeWatched) if episode is None: # no episode get out of here return self.logMsg("episode details %s" % str(episode), 2) self.episode = episode episodeid = episode["episodeid"] if includeWatched: includePlaycount = True else: includePlaycount = episode["playcount"] == 0 if includePlaycount and currentepisodeid != episodeid: # we have a next up episode self.playbackonended = True self.nextUpPage = NextUpInfo( "script-nextup-notification-NextUpInfo.xml", addonSettings.getAddonInfo('path'), "default", "1080i") self.nextUpPage.setItem(episode) self.stillWatchingPage = StillWatchingInfo( "script-nextup-notification-StillWatchingInfo.xml", addonSettings.getAddonInfo('path'), "default", "1080i") self.stillWatchingPage.setItem(episode) playedinarownumber = addonSettings.getSetting( "playedInARow") playTime = xbmc.Player().getTime() self.totalTime = xbmc.Player().getTotalTime() self.logMsg( "played in a row settings %s" % str(playedinarownumber), 2) self.logMsg( "played in a row %s" % str(self.playedinarow), 2) if int(self.playedinarow) <= int(playedinarownumber): self.logMsg( "showing next up page as played in a row is %s" % str(self.playedinarow), 2) if (shortplayNotification == "false") and ( shortplayLength >= self.totalTime) and ( shortplayMode == "true"): self.logMsg( "hiding notification for short videos") else: self.nextUpPage.show() else: self.logMsg( "showing still watching page as played in a row %s" % str(self.playedinarow), 2) if (shortplayNotification == "false") and ( shortplayLength >= self.totalTime) and ( shortplayMode == "true"): self.logMsg( "hiding notification for short videos") else: self.stillWatchingPage.show() while xbmc.Player().isPlaying( ) and not self.nextUpPage.isCancel( ) and not self.nextUpPage.isWatchNow( ) and not self.stillWatchingPage.isStillWatching( ) and not self.stillWatchingPage.isCancel(): xbmc.sleep(100) #try: # playTime = xbmc.Player().getTime() # totalTime = xbmc.Player().getTotalTime() #except: # pass if xbmc.Player().isPlaying(): if shortplayLength >= self.totalTime and shortplayMode == "true": #play short video and don't add to playcount self.playedinarow += 0 self.logMsg( "Continuing short video autoplay - %s") if self.nextUpPage.isWatchNow( ) or self.stillWatchingPage.isStillWatching(): self.playedinarow = 1 shouldPlayDefault = not self.nextUpPage.isCancel( ) else: if int(self.playedinarow) <= int( playedinarownumber): self.nextUpPage.close() shouldPlayDefault = not self.nextUpPage.isCancel( ) shouldPlayNonDefault = self.nextUpPage.isWatchNow( ) else: self.stillWatchingPage.close() shouldPlayDefault = self.stillWatchingPage.isStillWatching( ) shouldPlayNonDefault = self.stillWatchingPage.isStillWatching( ) if self.nextUpPage.isWatchNow( ) or self.stillWatchingPage.isStillWatching(): self.playedinarow = 1 else: self.playedinarow += 1 if (shouldPlayDefault and playMode == "0") or ( shouldPlayNonDefault and playMode == "1"): self.logMsg( "playing media episode id %s" % str(episodeid), 2) # Signal to trakt previous episode watched as playback ended early AddonSignals.sendSignal( "NEXTUPWATCHEDSIGNAL", {'episodeid': self.currentepisodeid}) # Play media xbmc.executeJSONRPC( '{ "jsonrpc": "2.0", "id": 0, "method": "Player.Open", ' '"params": { "item": {"episodeid": ' + str(episode["episodeid"]) + '} } }') def onPlayBackEnded(self): # Will be called when kodi stops playing a file self.logMsg("ONPLAYBACK_ENDED", 2) addonSettings = xbmcaddon.Addon(id='service.nextup.notification') playMode = addonSettings.getSetting("autoPlayMode") playedinarownumber = addonSettings.getSetting("playedInARow") shortplayMode = addonSettings.getSetting("shortPlayMode") shortplayLength = int(addonSettings.getSetting("shortPlayLength")) * 60 if self.playbackonended and self.episode: self.logMsg("playback ended and next up episode to show", 2) if shortplayLength >= self.totalTime and shortplayMode == "true": #play short video and don't add to playcount self.playedinarow += 0 self.logMsg("Continuing short video autoplay - %s") if self.nextUpPage.isWatchNow( ) or self.stillWatchingPage.isStillWatching(): self.playedinarow = 1 shouldPlayDefault = not self.nextUpPage.isCancel() else: if int(self.playedinarow) <= int(playedinarownumber): self.nextUpPage.close() shouldPlayDefault = not self.nextUpPage.isCancel() shouldPlayNonDefault = self.nextUpPage.isWatchNow() else: self.stillWatchingPage.close() shouldPlayDefault = self.stillWatchingPage.isStillWatching( ) shouldPlayNonDefault = self.stillWatchingPage.isStillWatching( ) if self.nextUpPage.isWatchNow( ) or self.stillWatchingPage.isStillWatching(): self.playedinarow = 1 else: self.playedinarow += 1 if (shouldPlayDefault and playMode == "0") or (shouldPlayNonDefault and playMode == "1"): episodeid = self.episode["episodeid"] self.logMsg( "playing media episode onplaybackended id %s" % str(episodeid), 2) # Play media xbmc.executeJSONRPC( '{ "jsonrpc": "2.0", "id": 0, "method": "Player.Open", ' '"params": { "item": {"episodeid": ' + str(episodeid) + '} } }')
class TextureCache(): addonName = ClientInformation().getAddonName() xbmc_host = 'localhost' xbmc_port = None xbmc_username = None xbmc_password = None enableTextureCache = utils.settings('enableTextureCache') == "true" def __init__(self): if not self.xbmc_port and self.enableTextureCache: self.setKodiWebServerDetails() def logMsg(self, msg, lvl=1): className = self.__class__.__name__ utils.logMsg("%s %s" % (self.addonName, className), msg, int(lvl)) def double_urlencode(self, text): text = self.single_urlencode(text) text = self.single_urlencode(text) return text def single_urlencode(self, text): blah = urllib.urlencode({'blahblahblah': text}) blah = blah[13:] return blah def setKodiWebServerDetails(self): # Get the Kodi webserver details - used to set the texture cache json_response = xbmc.executeJSONRPC( '{"jsonrpc":"2.0", "id":1, "method":"Settings.GetSettingValue","params":{"setting":"services.webserver"}, "id":1}' ) jsonobject = json.loads(json_response.decode('utf-8', 'replace')) if (jsonobject.has_key('result')): xbmc_webserver_enabled = jsonobject["result"]["value"] if not xbmc_webserver_enabled: #enable the webserver if not enabled xbmc.executeJSONRPC( '{"jsonrpc":"2.0", "id":1, "method":"Settings.SetSettingValue","params":{"setting":"services.webserverport","value":8080}, "id":1}' ) self.xbmc_port = 8080 xbmc.executeJSONRPC( '{"jsonrpc":"2.0", "id":1, "method":"Settings.SetSettingValue","params":{"setting":"services.webserver","value":true}, "id":1}' ) self.xbmc_port = "kodi" json_response = xbmc.executeJSONRPC( '{"jsonrpc":"2.0", "id":1, "method":"Settings.GetSettingValue","params":{"setting":"services.webserverport"}, "id":1}' ) jsonobject = json.loads(json_response.decode('utf-8', 'replace')) if (jsonobject.has_key('result')): self.xbmc_port = jsonobject["result"]["value"] json_response = xbmc.executeJSONRPC( '{"jsonrpc":"2.0", "id":1, "method":"Settings.GetSettingValue","params":{"setting":"services.webserverusername"}, "id":1}' ) jsonobject = json.loads(json_response.decode('utf-8', 'replace')) if (jsonobject.has_key('result')): self.xbmc_username = jsonobject["result"]["value"] json_response = xbmc.executeJSONRPC( '{"jsonrpc":"2.0", "id":1, "method":"Settings.GetSettingValue","params":{"setting":"services.webserverpassword"}, "id":1}' ) jsonobject = json.loads(json_response.decode('utf-8', 'replace')) if (jsonobject.has_key('result')): self.xbmc_password = jsonobject["result"]["value"] def FullTextureCacheSync(self): #this method can be called from the plugin to sync all Kodi textures to the texture cache. #Warning: this means that every image will be cached locally, this takes diskspace! # Remove all existing textures first path = "special://thumbnails/" if xbmcvfs.exists(path): allDirs, allFiles = xbmcvfs.listdir(path) for dir in allDirs: allDirs, allFiles = xbmcvfs.listdir(path + dir) for file in allFiles: xbmcvfs.delete(os.path.join(path + dir, file)) textureconnection = utils.KodiSQL('texture') texturecursor = textureconnection.cursor() texturecursor.execute( 'SELECT tbl_name FROM sqlite_master WHERE type="table"') rows = texturecursor.fetchall() for row in rows: tableName = row[0] if (tableName != "version"): texturecursor.execute("DELETE FROM " + tableName) textureconnection.commit() texturecursor.close() # Cache all entries in video DB connection = utils.KodiSQL('video') cursor = connection.cursor() cursor.execute("SELECT url FROM art") result = cursor.fetchall() for url in result: self.CacheTexture(url[0]) cursor.close() # Cache all entries in music DB connection = utils.KodiSQL('music') cursor = connection.cursor() cursor.execute("SELECT url FROM art") result = cursor.fetchall() for url in result: self.CacheTexture(url[0]) cursor.close() def addArtwork(self, artwork, kodiId, mediaType, cursor): # Kodi conversion table kodiart = { 'Primary': ["thumb", "poster"], 'Banner': "banner", 'Logo': "clearlogo", 'Art': "clearart", 'Thumb': "landscape", 'Disc': "discart", 'Backdrop': "fanart", 'BoxRear': "poster" } # Artwork is a dictionary for art in artwork: if art == "Backdrop": # Backdrop entry is a list, process extra fanart for artwork downloader (fanart, fanart1, fanart2, etc.) backdrops = artwork[art] backdropsNumber = len(backdrops) cursor.execute( "SELECT url FROM art WHERE media_id = ? AND media_type = ? AND type LIKE ?", ( kodiId, mediaType, "fanart%", )) rows = cursor.fetchall() if len(rows) > backdropsNumber: # More backdrops in database than what we are going to process. Delete extra fanart. cursor.execute( "DELETE FROM art WHERE media_id = ? AND media_type = ? AND type LIKE ?", ( kodiId, mediaType, "fanart_", )) index = "" for backdrop in backdrops: self.addOrUpdateArt(backdrop, kodiId, mediaType, "%s%s" % ("fanart", index), cursor) if backdropsNumber > 1: try: # Will only fail on the first try, str to int. index += 1 except TypeError: index = 1 elif art == "Primary": # Primary art is processed as thumb and poster for Kodi. for artType in kodiart[art]: self.addOrUpdateArt(artwork[art], kodiId, mediaType, artType, cursor) elif kodiart.get(art): # For banner, logo, art, thumb, disc # Only process artwork type that Kodi can use self.addOrUpdateArt(artwork[art], kodiId, mediaType, kodiart[art], cursor) def addOrUpdateArt(self, imageUrl, kodiId, mediaType, imageType, cursor): # Possible that the imageurl is an empty string if imageUrl: cacheimage = False cursor.execute( "SELECT url FROM art WHERE media_id = ? AND media_type = ? AND type = ?", ( kodiId, mediaType, imageType, )) try: # Update the artwork url = cursor.fetchone()[0] except: # Add the artwork cacheimage = True self.logMsg( "Adding Art Link for kodiId: %s (%s)" % (kodiId, imageUrl), 2) query = "INSERT INTO art(media_id, media_type, type, url) values(?, ?, ?, ?)" cursor.execute(query, (kodiId, mediaType, imageType, imageUrl)) else: # Only cache artwork if it changed if url != imageUrl: cacheimage = True # Only for the main backdrop, poster if imageType in ("fanart", "poster"): # Delete current entry before updating with the new one self.deleteCachedArtwork(url) self.logMsg( "Updating Art Link for kodiId: %s (%s) -> (%s)" % (kodiId, url, imageUrl), 1) query = "UPDATE art set url = ? WHERE media_id = ? AND media_type = ? AND type = ?" cursor.execute(query, (imageUrl, kodiId, mediaType, imageType)) # Cache fanart and poster in Kodi texture cache if cacheimage and imageType in ("fanart", "poster"): self.CacheTexture(imageUrl) def CacheTexture(self, url): # Cache a single image url to the texture cache if url and self.enableTextureCache: self.logMsg("Processing: %s" % url, 2) # Add image to texture cache by simply calling it at the http endpoint url = self.double_urlencode(url) try: # Extreme short timeouts so we will have a exception, but we don't need the result so pass response = requests.head('http://%s:%s/image/image://%s' % (self.xbmc_host, self.xbmc_port, url), auth=(self.xbmc_username, self.xbmc_password), timeout=(0.01, 0.01)) except: pass def deleteCachedArtwork(self, url): # Only necessary to remove and apply a new backdrop or poster connection = utils.KodiSQL('texture') cursor = connection.cursor() cursor.execute("SELECT cachedurl FROM texture WHERE url = ?", (url, )) try: cachedurl = cursor.fetchone()[0] except: self.logMsg("Could not find cached url.", 1) else: # Delete thumbnail as well as the entry thumbnails = xbmc.translatePath("special://thumbnails/%s" % cachedurl) self.logMsg("Deleting cached thumbnail: %s" % thumbnails, 1) xbmcvfs.delete(thumbnails) cursor.execute("DELETE FROM texture WHERE url = ?", (url, )) connection.commit() finally: cursor.close()
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
class ConnectionManager(): clientInfo = ClientInformation() uc = UserClient() doUtils = DownloadUtils() addonName = clientInfo.getAddonName() addonId = clientInfo.getAddonId() addon = xbmcaddon.Addon(id=addonId) WINDOW = xbmcgui.Window(10000) logLevel = 0 def __init__(self): self.className = self.__class__.__name__ self.__language__ = self.addon.getLocalizedString def logMsg(self, msg, lvl=1): utils.logMsg("%s %s" % (self.addonName, self.className), msg, int(lvl)) def checkServer(self): self.WINDOW.setProperty("Server_Checked", "True") self.logMsg("Connection Manager Called", 2) addon = self.addon server = self.uc.getServer() if (server != ""): self.logMsg("Server already set", 2) return serverInfo = self.getServerDetails() if (serverInfo == None): self.logMsg("getServerDetails failed", 1) xbmc.executebuiltin('Addon.OpenSettings(%s)' % self.addonId) return prefix,ip,port = serverInfo.split(":") setServer = xbmcgui.Dialog().yesno(self.__language__(30167), "Proceed with the following server?", self.__language__(30169) + serverInfo) if (setServer == 1): self.logMsg("Server selected. Saving information.", 1) addon.setSetting("ipaddress", ip.replace("/", "")) addon.setSetting("port", port) # If https is enabled if (prefix == 'https'): addon.setSetting('https', "true") else: self.logMsg("No server selected.", 1) xbmc.executebuiltin('Addon.OpenSettings(%s)' % self.addonId) return # Get List of public users self.logMsg("Getting user list", 1) server = ip.replace("/", "") + ":" + port url = "%s/mediabrowser/Users/Public?format=json" % serverInfo try: result = self.doUtils.downloadUrl(url, authenticate=False) except Exception, msg: error = "Unable to connect to %s: %s" % (server, msg) self.logMsg(error, 1) return "" if (result == ""): return self.logMsg("jsonData: %s" % result, 2) names = [] userList = [] for user in result: name = user[u'Name'] userList.append(name) if(user[u'HasPassword'] == True): name = name + " (Secure)" names.append(name) self.logMsg("User List: %s" % names, 1) self.logMsg("User List: %s" % userList, 2) return_value = xbmcgui.Dialog().select(self.__language__(30200), names) if (return_value > -1): selected_user = userList[return_value] self.logMsg("Selected User: %s" % selected_user, 1) self.addon.setSetting("username", selected_user) else: self.logMsg("No user selected.", 1) xbmc.executebuiltin('Addon.OpenSettings(%s)' % self.addonId) return # Option to play from http setPlayback = xbmcgui.Dialog().yesno("Playback option", "Play your files using HTTP?") if setPlayback == 1: self.logMsg("Playback will be set using HTTP.", 1) addon.setSetting("playFromStream", "true") else: self.logMsg("Playback will be set using SMB.", 1)
class Player(xbmc.Player): # Borg - multiple instances, shared state _shared_state = {} xbmcplayer = xbmc.Player() clientInfo = ClientInformation() addonName = clientInfo.getAddonName() addonId = clientInfo.getAddonId() addon = xbmcaddon.Addon(id=addonId) logLevel = 0 currenttvshowid = None currentepisodeid = None playedinarow = 1 def __init__(self, *args): self.__dict__ = self._shared_state self.logMsg("Starting playback monitor service", 1) def logMsg(self, msg, lvl=1): self.className = self.__class__.__name__ utils.logMsg("%s %s" % (self.addonName, self.className), msg, int(lvl)) def json_query(self, query, ret): try: xbmc_request = json.dumps(query) result = xbmc.executeJSONRPC(xbmc_request) result = unicode(result, 'utf-8', errors='ignore') if ret: return json.loads(result)['result'] else: return json.loads(result) except: xbmc_request = json.dumps(query) result = xbmc.executeJSONRPC(xbmc_request) result = unicode(result, 'utf-8', errors='ignore') self.logMsg(json.loads(result), 1) return json.loads(result) def iStream_fix(self, show_npid, showtitle, episode_np, season_np): # streams from iStream dont provide the showid and epid for above # they come through as tvshowid = -1, but it has episode no and season no and show name # need to insert work around here to get showid from showname, and get epid from season and episode no's # then need to ignore prevcheck self.logMsg('fixing strm, data follows...') self.logMsg('show_npid = ' + str(show_npid)) self.logMsg('showtitle = ' + str(showtitle)) self.logMsg('episode_np = ' + str(episode_np)) self.logMsg('season_np = ' + str(season_np)) show_request_all = { "jsonrpc": "2.0", "method": "VideoLibrary.GetTVShows", "params": { "properties": ["title"] }, "id": "1" } eps_query = { "jsonrpc": "2.0", "method": "VideoLibrary.GetEpisodes", "params": { "properties": [ "season", "episode", "runtime", "resume", "playcount", "tvshowid", "lastplayed", "file" ], "tvshowid": "1" }, "id": "1" } ep_npid = " " redo = True count = 0 while redo and count < 2: # this ensures the section of code only runs twice at most [ only runs once fine ? redo = False count += 1 if show_npid == -1 and showtitle and episode_np and season_np: prevcheck = False tmp_shows = self.json_query(show_request_all, True) self.logMsg('tmp_shows = ' + str(tmp_shows)) if 'tvshows' in tmp_shows: for x in tmp_shows['tvshows']: if x['label'] == showtitle: show_npid = x['tvshowid'] eps_query['params']['tvshowid'] = show_npid tmp_eps = self.json_query(eps_query, True) self.logMsg('tmp eps = ' + str(tmp_eps)) if 'episodes' in tmp_eps: for y in tmp_eps['episodes']: if (y['season']) == season_np and ( y['episode']) == episode_np: ep_npid = y['episodeid'] self.logMsg('playing epid stream = ' + str(ep_npid)) return show_npid, ep_npid def findNextEpisode(self, result): self.logMsg("Find next episode called", 1) position = 0 for episode in result["result"]["episodes"]: # find position of current episode if self.currentepisodeid == episode["episodeid"]: # found a match so add 1 for the next and get out of here position = position + 1 break else: # no match found continue position = position + 1 # now return the episode self.logMsg( "Find next episode found next episode in position: " + str(position), 1) try: episode = result["result"]["episodes"][position] except: # no next episode found episode = None return episode def autoPlayPlayback(self): currentFile = xbmc.Player().getPlayingFile() # Get the active player result = xbmc.executeJSONRPC( '{"jsonrpc": "2.0", "id": 1, "method": "Player.GetActivePlayers"}') result = unicode(result, 'utf-8', errors='ignore') self.logMsg("Got active player " + result, 2) result = json.loads(result) # Seems to work too fast loop whilst waiting for it to become active while result["result"] == []: result = xbmc.executeJSONRPC( '{"jsonrpc": "2.0", "id": 1, "method": "Player.GetActivePlayers"}' ) result = unicode(result, 'utf-8', errors='ignore') self.logMsg("Got active player " + result, 2) result = json.loads(result) if result.has_key('result') and result["result"][0] != None: playerid = result["result"][0]["playerid"] # Get details of the playing media self.logMsg("Getting details of playing media", 1) result = xbmc.executeJSONRPC( '{"jsonrpc": "2.0", "id": 1, "method": "Player.GetItem", "params": {"playerid": ' + str(playerid) + ', "properties": ["showtitle", "tvshowid", "episode", "season", "playcount"] } }' ) result = unicode(result, 'utf-8', errors='ignore') self.logMsg("Got details of playing media" + result, 2) result = json.loads(result) if result.has_key('result'): type = result["result"]["item"]["type"] if type == "episode": # Get the next up episode addonSettings = xbmcaddon.Addon( id='service.nextup.notification') playMode = addonSettings.getSetting("autoPlayMode") tvshowid = result["result"]["item"]["tvshowid"] currentepisodenumber = result["result"]["item"]["episode"] currentseasonid = result["result"]["item"]["season"] currentshowtitle = result["result"]["item"]["showtitle"] tvshowid = result["result"]["item"]["tvshowid"] # I am a STRM ### if tvshowid == -1: tvshowid, episodeid = self.iStream_fix( tvshowid, currentshowtitle, currentepisodenumber, currentseasonid) currentepisodeid = episodeid else: currentepisodeid = result["result"]["item"]["id"] self.currentepisodeid = currentepisodeid self.logMsg( "Getting details of next up episode for tvshow id: " + str(tvshowid), 1) if self.currenttvshowid != tvshowid: self.currenttvshowid = tvshowid self.playedinarow = 1 includeWatched = addonSettings.getSetting( "includeWatched") == "true" if includeWatched == True: result = xbmc.executeJSONRPC( '{"jsonrpc": "2.0", "method": "VideoLibrary.GetEpisodes", "params": {"tvshowid": %d, "properties": [ "title", "playcount", "season", "episode", "showtitle", "plot", "file", "rating", "resume", "tvshowid", "art", "firstaired", "runtime", "writer", "dateadded", "lastplayed" , "streamdetails"], "sort": {"method": "episode"}}, "id": 1}' % tvshowid) else: result = 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", "firstaired", "runtime", "writer", "dateadded", "lastplayed" , "streamdetails"], "limits":{"start":1,"end":2}}, "id": "1"}' % tvshowid) if result: result = unicode(result, 'utf-8', errors='ignore') result = json.loads(result) self.logMsg( "Got details of next up episode %s" % str(result), 2) xbmc.sleep(100) # Find the next unwatched and the newest added episodes if result.has_key("result") and result[ "result"].has_key("episodes"): if includeWatched == True: episode = self.findNextEpisode(result) else: episode = result["result"]["episodes"][0] if episode == None: # no episode get out of here return self.logMsg("episode details %s" % str(episode), 2) episodeid = episode["episodeid"] includePlaycount = True if includeWatched == True: includePlaycount = True else: includePlaycount = episode["playcount"] == 0 if includePlaycount and currentepisodeid != episodeid: # we have a next up episode nextUpPage = NextUpInfo( "script-nextup-notification-NextUpInfo.xml", addonSettings.getAddonInfo('path'), "default", "1080i") nextUpPage.setItem(episode) stillWatchingPage = StillWatchingInfo( "script-nextup-notification-StillWatchingInfo.xml", addonSettings.getAddonInfo('path'), "default", "1080i") stillWatchingPage.setItem(episode) playTime = xbmc.Player().getTime() totalTime = xbmc.Player().getTotalTime() playedinarownumber = addonSettings.getSetting( "playedInARow") self.logMsg( "played in a row settings %s" % str(playedinarownumber), 2) self.logMsg( "played in a row %s" % str(self.playedinarow), 2) if int(self.playedinarow) <= int( playedinarownumber): self.logMsg( "showing next up page as played in a row is %s" % str(self.playedinarow), 2) nextUpPage.show() else: self.logMsg( "showing still watching page as played in a row %s" % str(self.playedinarow), 2) stillWatchingPage.show() playTime = xbmc.Player().getTime() totalTime = xbmc.Player().getTotalTime() while xbmc.Player().isPlaying() and ( totalTime - playTime > 1 ) and not nextUpPage.isCancel( ) and not nextUpPage.isWatchNow( ) and not stillWatchingPage.isStillWatching( ) and not stillWatchingPage.isCancel(): xbmc.sleep(100) try: playTime = xbmc.Player().getTime() totalTime = xbmc.Player().getTotalTime( ) except: pass if int(self.playedinarow) <= int( playedinarownumber): nextUpPage.close() shouldPlayDefault = not nextUpPage.isCancel( ) shouldPlayNonDefault = nextUpPage.isWatchNow( ) else: stillWatchingPage.close() shouldPlayDefault = stillWatchingPage.isStillWatching( ) shouldPlayNonDefault = stillWatchingPage.isStillWatching( ) if nextUpPage.isWatchNow( ) or stillWatchingPage.isStillWatching(): self.playedinarow = 1 else: self.playedinarow = self.playedinarow + 1 if (shouldPlayDefault and playMode == "0") or (shouldPlayNonDefault and playMode == "1"): self.logMsg( "playing media episode id %s" % str(episodeid), 2) # Play media xbmc.executeJSONRPC( '{ "jsonrpc": "2.0", "id": 0, "method": "Player.Open", "params": { "item": {"episodeid": ' + str(episode["episodeid"]) + '} } }')
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['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
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
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) ############### 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 xbmc.getCondVisibility('Window.IsActive(home)') 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) ############### ORGANIZE CURRENT PLAYLIST ################ # In order, intros, original item requested and any additional part playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO) startPos = max(playlist.getposition(), 0) # Can return -1 sizePlaylist = playlist.size() currentPosition = startPos self.logMsg("Playlist start position: %s" % startPos, 2) self.logMsg("Playlist current position: %s" % currentPosition, 2) self.logMsg("Playlist size: %s" % sizePlaylist, 2) # Properties to ensure we have have proper playlists with additional items. introsPlaylist = False introProperty = utils.window('PlaylistIntroSet') == "true" dummyProperty = utils.window('PlaylistsetDummy') == "true" additionalProperty = utils.window('PlaylistAdditional') == "true" ############### -- CHECK FOR INTROS ################ if utils.settings('disableCinema') == "false" and not introProperty 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: # The server randomly returns one custom intro intro = intros['Items'][0] introId = intro['Id'] introListItem = xbmcgui.ListItem() introPlayurl = PlayUtils().getPlayUrl(server, introId, intro) self.logMsg("Intro play: %s" % introPlayurl, 1) self.setProperties(introPlayurl, intro, introListItem) self.setListItemProps(server, introId, introListItem, intro) introsPlaylist = True utils.window('PlaylistIntroSet', value="true") playlist.add(introPlayurl, introListItem, index=currentPosition) currentPosition += 1 elif introProperty: # Play main item, do not play the intro since we already played it. Reset property for next time. utils.window('PlaylistIntroSet', clear=True) self.logMsg("Clear intro property.", 2) ############### -- 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) if introsPlaylist and not sizePlaylist: # Extend our current playlist with the actual item to play only if there's no playlist first self.logMsg("No playlist detected at the start. Creating playlist with intro and play item.", 1) self.logMsg("Playlist current position: %s" % (currentPosition), 1) playlist.add(playurl, listItem, index=currentPosition) currentPosition += 1 ############### -- CHECK FOR ADDITIONAL PARTS ################ if result.get('PartCount') and not additionalProperty: # Only add to the playlist after intros have played 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() # Set listitem and properties for each additional parts self.logMsg("Adding to playlist: %s position: %s" % (additionalPlayurl, currentPosition), 1) self.setProperties(additionalPlayurl, part, additionalListItem) self.setListItemProps(server, partId, additionalListItem, part) # Add item to playlist, after the main item utils.window('PlaylistAdditional', value="true") playlist.add(additionalPlayurl, additionalListItem, index=currentPosition+1) currentPosition += 1 elif additionalProperty: # Additional parts are already set, reset property for next time utils.window('PlaylistAdditional', clear=True) self.logMsg("Clear additional property", 2) ############### PLAYBACK ################ if setup == "service" or xbmc.getCondVisibility('Window.IsActive(home)'): # Sent via websocketclient.py or default.py but via widgets self.logMsg("Detecting playback happening via service.py or home menu.", 1) self.setListItemProps(server, id, listItem, result) playlistPlayer = False if introsPlaylist and not sizePlaylist: # Extend our current playlist with the actual item to play only if there's no playlist first playlistPlayer = True elif sizePlaylist > 0 and not dummyProperty: # Playlist will fail on the current position. Adding dummy url playlist.add(playurl, index=startPos) self.logMsg("Adding dummy path as replacement for position: %s" % startPos, 2) utils.window('PlaylistsetDummy', value="true") playlistPlayer = True elif dummyProperty: # Already failed, play the item as a single item utils.window('PlaylistsetDummy', clear=True) self.logMsg("Clear dummy property.", 2) if playlistPlayer: self.logMsg("Processed as a playlist.", 1) return xbmc.Player().play(playlist) else: self.logMsg("Processed as a single item.", 1) return xbmc.Player().play(playurl, listItem) elif setup == "default": self.logMsg("Detecting playback happening via default.py.", 1) playlistPlayer = False if sizePlaylist > 0 and not dummyProperty: # Playlist will fail on the current position. Adding dummy url playlist.add(playurl, index=startPos) self.logMsg("Adding dummy path as replacement for position: %s" % startPos, 2) utils.window('PlaylistsetDummy', value="true") playlistPlayer = True elif dummyProperty: # Already failed, play the item as a single item utils.window('PlaylistsetDummy', clear=True) self.logMsg("Clear dummy property.", 2) if playlistPlayer: self.logMsg("Processed as a playlist.", 1) return xbmc.Player().play(playlist, startpos=startPos) else: # Sent via default.py self.logMsg("Processed as a single item.", 1) return xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, listItem) 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.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) playlist.add(playurl, listItem) # Not currently being used '''def PLAYAllEpisodes(self, items):
import xbmc import xbmcgui import xbmcaddon import json import threading from datetime import datetime from DownloadUtils import DownloadUtils from ClientInformation import ClientInformation import urllib import sys import os #define our global download utils downloadUtils = DownloadUtils() clientInfo = ClientInformation() ########################################################################### class PlayUtils(): def getPlayUrl(self, server, id, result): addonSettings = xbmcaddon.Addon(id='plugin.video.xbmb3c') # if the path is local and depending on the video quality play we can direct play it do so- xbmc.log("XBMB3C getPlayUrl") if self.fileExists(result) or self.isDirectPlay(result) == True: xbmc.log("XBMB3C getPlayUrl -> Direct Play") playurl = result.get("Path") if playurl != None: #We have a path to play so play it USER_AGENT = 'QuickTime/7.7.4'
class Service(): newWebSocketThread = None newUserClient = None clientInfo = ClientInformation() addonName = clientInfo.getAddonName() className = None def __init__(self, *args ): self.KodiMonitor = KodiMonitor.Kodi_Monitor() addonName = self.addonName self.logMsg("Starting Monitor", 0) self.logMsg("======== START %s ========" % addonName, 0) self.logMsg("KODI Version: %s" % xbmc.getInfoLabel("System.BuildVersion"), 0) self.logMsg("%s Version: %s" % (addonName, self.clientInfo.getVersion()), 0) def logMsg(self, msg, lvl=1): self.className = self.__class__.__name__ utils.logMsg("%s %s" % (self.addonName, self.className), str(msg), int(lvl)) def ServiceEntryPoint(self): ConnectionManager().checkServer() lastProgressUpdate = datetime.today() startupComplete = False #interval_FullSync = 600 #interval_IncrementalSync = 300 #cur_seconds_fullsync = interval_FullSync #cur_seconds_incrsync = interval_IncrementalSync user = UserClient() player = Player() ws = WebSocketThread() lastFile = None while not self.KodiMonitor.abortRequested(): if self.KodiMonitor.waitForAbort(1): # Abort was requested while waiting. We should exit break if xbmc.Player().isPlaying(): try: playTime = xbmc.Player().getTime() totalTime = xbmc.Player().getTotalTime() currentFile = xbmc.Player().getPlayingFile() if(player.played_information.get(currentFile) != None): player.played_information[currentFile]["currentPosition"] = playTime # send update td = datetime.today() - lastProgressUpdate secDiff = td.seconds if(secDiff > 3): try: player.reportPlayback() except Exception, msg: self.logMsg("Exception reporting progress: %s" % msg) pass lastProgressUpdate = datetime.today() # only try autoplay when there's 20 seconds or less remaining and only once! if (totalTime - playTime <= 20 and (lastFile==None or lastFile!=currentFile)): lastFile = currentFile player.autoPlayPlayback() except Exception, e: self.logMsg("Exception in Playback Monitor Service: %s" % e) pass else:
class Service(): clientInfo = ClientInformation() addonName = clientInfo.getAddonName() WINDOW = xbmcgui.Window(10000) def __init__(self, *args): addonName = self.addonName self.logMsg("Starting NextUp Service", 0) self.logMsg("======== START %s ========" % addonName, 0) self.logMsg( "KODI Version: %s" % xbmc.getInfoLabel("System.BuildVersion"), 0) self.logMsg( "%s Version: %s" % (addonName, self.clientInfo.getVersion()), 0) self.logMsg("Platform: %s" % (self.clientInfo.getPlatform()), 0) def logMsg(self, msg, lvl=1): className = self.__class__.__name__ utils.logMsg("%s %s" % (self.addonName, className), str(msg), int(lvl)) def ServiceEntryPoint(self): lastProgressUpdate = datetime.today() player = Player() lastFile = None while (not xbmc.abortRequested): xbmc.sleep(1000) if xbmc.Player().isPlaying(): try: playTime = xbmc.Player().getTime() totalTime = xbmc.Player().getTotalTime() currentFile = xbmc.Player().getPlayingFile() addonSettings = xbmcaddon.Addon( id='service.nextup.notification') notificationtime = addonSettings.getSetting( "autoPlaySeasonTime") nextUpDisabled = addonSettings.getSetting( "disableNextUp") == "true" if xbmcgui.Window(10000).getProperty( "PseudoTVRunning" ) != "True" and not nextUpDisabled: if (totalTime - playTime <= int(notificationtime) and (lastFile == None or lastFile != currentFile)) and totalTime != 0: lastFile = currentFile self.logMsg( "Calling autoplayback totaltime - playtime is %s" % (totalTime - playTime), 2) player.autoPlayPlayback() self.logMsg("Netflix style autoplay succeeded.", 2) xbmc.sleep(1000) except Exception, e: self.logMsg("Exception in Playback Monitor Service: %s" % e) pass else: xbmc.sleep(5000) self.logMsg("======== STOP %s ========" % self.addonName, 0)
class Player(xbmc.Player): # Borg - multiple instances, shared state _shared_state = {} xbmcplayer = xbmc.Player() clientInfo = ClientInformation() addonName = clientInfo.getAddonName() addonId = clientInfo.getAddonId() addon = xbmcaddon.Addon(id=addonId) logLevel = 0 currenttvshowid = None currentepisodeid = None playedinarow = 1 fields_base = '"dateadded", "file", "lastplayed","plot", "title", "art", "playcount",' fields_file = fields_base + '"streamdetails", "director", "resume", "runtime",' fields_tvshows = fields_base + '"sorttitle", "mpaa", "premiered", "year", "episode", "watchedepisodes", "votes", "rating", "studio", "season", "genre", "episodeguide", "tag", "originaltitle", "imdbnumber"' fields_episodes = fields_file + '"cast", "productioncode", "rating", "votes", "episode", "showtitle", "tvshowid", "season", "firstaired", "writer", "originaltitle"' postplaywindow = None def __init__(self, *args): self.__dict__ = self._shared_state self.logMsg("Starting playback monitor service", 1) def logMsg(self, msg, lvl=1): self.className = self.__class__.__name__ utils.logMsg("%s %s" % (self.addonName, self.className), msg, int(lvl)) def json_query(self, query, ret): try: xbmc_request = json.dumps(query) result = xbmc.executeJSONRPC(xbmc_request) result = unicode(result, 'utf-8', errors='ignore') if ret: return json.loads(result)['result'] else: return json.loads(result) except: xbmc_request = json.dumps(query) result = xbmc.executeJSONRPC(xbmc_request) result = unicode(result, 'utf-8', errors='ignore') self.logMsg(json.loads(result), 1) return json.loads(result) def getNowPlaying(self): # Get the active player result = xbmc.executeJSONRPC( '{"jsonrpc": "2.0", "id": 1, "method": "Player.GetActivePlayers"}') result = unicode(result, 'utf-8', errors='ignore') self.logMsg("Got active player " + result, 2) result = json.loads(result) # Seems to work too fast loop whilst waiting for it to become active while not result["result"]: result = xbmc.executeJSONRPC( '{"jsonrpc": "2.0", "id": 1, "method": "Player.GetActivePlayers"}' ) result = unicode(result, 'utf-8', errors='ignore') self.logMsg("Got active player " + result, 2) result = json.loads(result) if 'result' in result and result["result"][0] is not None: playerid = result["result"][0]["playerid"] # Get details of the playing media self.logMsg("Getting details of now playing media", 1) result = xbmc.executeJSONRPC( '{"jsonrpc": "2.0", "id": 1, "method": "Player.GetItem", "params": {"playerid": ' + str(playerid) + ', "properties": ["showtitle", "tvshowid", "episode", "season", "playcount","genre"] } }' ) result = unicode(result, 'utf-8', errors='ignore') self.logMsg("Got details of now playing media" + result, 2) result = json.loads(result) return result def onPlayBackStarted(self): # Will be called when xbmc starts playing a file addon = xbmcaddon.Addon(id='service.nextup.notification') self.postplaywindow = None WINDOW = xbmcgui.Window(10000) WINDOW.clearProperty("NextUpNotification.NowPlaying.DBID") WINDOW.clearProperty("NextUpNotification.NowPlaying.Type") # Get the active player result = self.getNowPlaying() if 'result' in result: itemtype = result["result"]["item"]["type"] if itemtype == "episode": itemtitle = result["result"]["item"]["showtitle"].encode( 'utf-8') itemtitle = utils.unicodetoascii(itemtitle) WINDOW.setProperty("NextUpNotification.NowPlaying.Type", itemtype) tvshowid = result["result"]["item"]["tvshowid"] WINDOW.setProperty("NextUpNotification.NowPlaying.DBID", str(tvshowid)) if int(tvshowid) == -1: tvshowid = self.showtitle_to_id(title=itemtitle) self.logMsg("Fetched missing tvshowid " + str(tvshowid), 2) WINDOW.setProperty("NextUpNotification.NowPlaying.DBID", str(tvshowid)) elif itemtype == "movie": WINDOW.setProperty("NextUpNotification.NowPlaying.Type", itemtype) id = result["result"]["item"]["id"] WINDOW.setProperty("NextUpNotification.NowPlaying.DBID", str(id)) def showtitle_to_id(self, title): query = { "jsonrpc": "2.0", "method": "VideoLibrary.GetTVShows", "params": { "properties": ["title"] }, "id": "libTvShows" } try: json_result = json.loads( xbmc.executeJSONRPC(json.dumps(query, encoding='utf-8'))) if 'result' in json_result and 'tvshows' in json_result['result']: json_result = json_result['result']['tvshows'] for tvshow in json_result: if tvshow['label'] == title: return tvshow['tvshowid'] return '-1' except Exception: return '-1' def get_episode_id(self, showid, showseason, showepisode): showseason = int(showseason) showepisode = int(showepisode) episodeid = 0 query = { "jsonrpc": "2.0", "method": "VideoLibrary.GetEpisodes", "params": { "properties": ["season", "episode"], "tvshowid": int(showid) }, "id": "1" } try: json_result = json.loads( xbmc.executeJSONRPC(json.dumps(query, encoding='utf-8'))) if 'result' in json_result and 'episodes' in json_result['result']: json_result = json_result['result']['episodes'] for episode in json_result: if episode['season'] == showseason and episode[ 'episode'] == showepisode: if 'episodeid' in episode: episodeid = episode['episodeid'] return episodeid except Exception: return episodeid def onPlayBackEnded(self): self.logMsg("playback ended ", 2) if self.postplaywindow is not None: self.showPostPlay() def findNextEpisode(self, result, currentFile, includeWatched): self.logMsg("Find next episode called", 1) position = 0 for episode in result["result"]["episodes"]: # find position of current episode if self.currentepisodeid == episode["episodeid"]: # found a match so add 1 for the next and get out of here position += 1 break position += 1 # check if it may be a multi-part episode while result["result"]["episodes"][position]["file"] == currentFile: position += 1 # skip already watched episodes? while not includeWatched and result["result"]["episodes"][position][ "playcount"] > 1: position += 1 # now return the episode self.logMsg( "Find next episode found next episode in position: " + str(position), 1) try: episode = result["result"]["episodes"][position] except: # no next episode found episode = None return episode def findCurrentEpisode(self, result, currentFile): self.logMsg("Find current episode called", 1) position = 0 for episode in result["result"]["episodes"]: # find position of current episode if self.currentepisodeid == episode["episodeid"]: # found a match so get out of here break position += 1 # now return the episode self.logMsg( "Find current episode found episode in position: " + str(position), 1) try: episode = result["result"]["episodes"][position] except: # no next episode found episode = None return episode def displayRandomUnwatched(self): # Get the active player result = self.getNowPlaying() if 'result' in result: itemtype = result["result"]["item"]["type"] if itemtype == "episode": # playing an episode so find a random unwatched show from the same genre genres = result["result"]["item"]["genre"] if genres: genretitle = genres[0] self.logMsg("Looking up tvshow for genre " + genretitle, 2) tvshow = utils.getJSON( 'VideoLibrary.GetTVShows', '{ "sort": { "order": "descending", "method": "random" }, "filter": {"and": [{"operator":"is", "field":"genre", "value":"%s"}, {"operator":"is", "field":"playcount", "value":"0"}]}, "properties": [ %s ],"limits":{"end":1} }' % (genretitle, self.fields_tvshows)) if not tvshow: self.logMsg("Looking up tvshow without genre", 2) tvshow = utils.getJSON( 'VideoLibrary.GetTVShows', '{ "sort": { "order": "descending", "method": "random" }, "filter": {"and": [{"operator":"is", "field":"playcount", "value":"0"}]}, "properties": [ %s ],"limits":{"end":1} }' % self.fields_tvshows) self.logMsg("Got tvshow" + str(tvshow), 2) tvshowid = tvshow[0]["tvshowid"] if int(tvshowid) == -1: tvshowid = self.showtitle_to_id(title=itemtitle) self.logMsg("Fetched missing tvshowid " + str(tvshowid), 2) episode = utils.getJSON( 'VideoLibrary.GetEpisodes', '{ "tvshowid": %d, "sort": {"method":"episode"}, "filter": {"and": [ {"field": "playcount", "operator": "lessthan", "value":"1"}, {"field": "season", "operator": "greaterthan", "value": "0"} ]}, "properties": [ %s ], "limits":{"end":1}}' % (tvshowid, self.fields_episodes)) if episode: self.logMsg( "Got details of next up episode %s" % str(episode), 2) addonSettings = xbmcaddon.Addon( id='service.nextup.notification') unwatchedPage = UnwatchedInfo( "script-nextup-notification-UnwatchedInfo.xml", addonSettings.getAddonInfo('path'), "default", "1080i") unwatchedPage.setItem(episode[0]) self.logMsg("Calling display unwatched", 2) unwatchedPage.show() monitor = xbmc.Monitor() monitor.waitForAbort(10) self.logMsg("Calling close unwatched", 2) unwatchedPage.close() del monitor def postPlayPlayback(self): currentFile = xbmc.Player().getPlayingFile() # Get the active player result = self.getNowPlaying() if 'result' in result: itemtype = result["result"]["item"]["type"] addonSettings = xbmcaddon.Addon(id='service.nextup.notification') playMode = addonSettings.getSetting("autoPlayMode") currentepisodenumber = result["result"]["item"]["episode"] currentseasonid = result["result"]["item"]["season"] currentshowtitle = result["result"]["item"]["showtitle"].encode( 'utf-8') currentshowtitle = utils.unicodetoascii(currentshowtitle) tvshowid = result["result"]["item"]["tvshowid"] shortplayMode = addonSettings.getSetting("shortPlayMode") shortplayNotification = addonSettings.getSetting( "shortPlayNotification") shortplayLength = int( addonSettings.getSetting("shortPlayLength")) * 60 # Try to get tvshowid by showtitle from kodidb if tvshowid is -1 like in strm streams which are added to kodi db if int(tvshowid) == -1: tvshowid = self.showtitle_to_id(title=currentshowtitle) self.logMsg("Fetched missing tvshowid " + str(tvshowid), 2) if (itemtype == "episode"): # Get current episodeid currentepisodeid = self.get_episode_id( showid=str(tvshowid), showseason=currentseasonid, showepisode=currentepisodenumber) else: # wtf am i doing here error.. #### self.logMsg("Error: cannot determine if episode", 1) return self.currentepisodeid = currentepisodeid self.logMsg( "Getting details of next up episode for tvshow id: " + str(tvshowid), 1) if self.currenttvshowid != tvshowid: self.currenttvshowid = tvshowid self.playedinarow = 1 result = xbmc.executeJSONRPC( '{"jsonrpc": "2.0", "method": "VideoLibrary.GetEpisodes", "params": {"tvshowid": %d, ' '"properties": [ "title", "playcount", "season", "episode", "showtitle", "plot", ' '"file", "rating", "resume", "tvshowid", "art", "firstaired", "runtime", "writer", ' '"dateadded", "lastplayed" , "streamdetails"], "sort": {"method": "episode"}}, "id": 1}' % tvshowid) if result: result = unicode(result, 'utf-8', errors='ignore') result = json.loads(result) self.logMsg("Got details of next up episode %s" % str(result), 2) xbmc.sleep(100) # Find the next unwatched and the newest added episodes if "result" in result and "episodes" in result["result"]: includeWatched = addonSettings.getSetting( "includeWatched") == "true" episode = self.findNextEpisode(result, currentFile, includeWatched) current_episode = self.findCurrentEpisode(result, currentFile) self.logMsg("episode details %s" % str(episode), 2) episodeid = episode["episodeid"] if current_episode: # we have something to show postPlayPage = PostPlayInfo( "script-nextup-notification-PostPlayInfo.xml", addonSettings.getAddonInfo('path'), "default", "1080i") postPlayPage.setItem(episode) postPlayPage.setPreviousItem(current_episode) upnextitems = self.parse_tvshows_recommended(6) postPlayPage.setUpNextList(upnextitems) playedinarownumber = addonSettings.getSetting( "playedInARow") playTime = xbmc.Player().getTime() totalTime = xbmc.Player().getTotalTime() self.logMsg( "played in a row settings %s" % str(playedinarownumber), 2) self.logMsg("played in a row %s" % str(self.playedinarow), 2) if int(self.playedinarow) <= int(playedinarownumber): if (shortplayNotification == "false") and (shortplayLength >= totalTime) and (shortplayMode == "true"): self.logMsg("hiding notification for short videos") else: postPlayPage.setStillWatching(False) else: if (shortplayNotification == "false") and (shortplayLength >= totalTime) and (shortplayMode == "true"): self.logMsg("hiding notification for short videos") else: postPlayPage.setStillWatching(True) self.postplaywindow = postPlayPage def showPostPlay(self): self.logMsg("showing postplay window") p = self.postplaywindow.doModal() autoplayed = xbmcgui.Window(10000).getProperty( "NextUpNotification.AutoPlayed") self.logMsg("showing postplay window completed autoplayed? " + str(autoplayed)) if autoplayed: self.playedinarow += 1 else: self.playedinarow = 1 del p def parse_tvshows_recommended(self, limit): items = [] prefix = "recommended-episodes" json_query = LIBRARY._fetch_recommended_episodes() if json_query: # First unplayed episode of recent played tvshows self.logMsg("getting next up tvshows " + json_query, 2) json_query = json.loads(json_query) if "result" in json_query and 'tvshows' in json_query['result']: count = -1 for item in json_query['result']['tvshows']: if xbmc.abortRequested: break if count == -1: count += 1 continue json_query2 = xbmcgui.Window(10000).getProperty( prefix + "-data-" + str(item['tvshowid'])) if json_query2: self.logMsg("getting next up episodes " + json_query2, 2) json_query2 = json.loads(json_query2) if "result" in json_query2 and json_query2[ 'result'] is not None and 'episodes' in json_query2[ 'result']: for item2 in json_query2['result']['episodes']: episode = "%.2d" % float(item2['episode']) season = "%.2d" % float(item2['season']) episodeno = "s%se%s" % (season, episode) break plot = item2['plot'] episodeid = str(item2['episodeid']) if len(item['studio']) > 0: studio = item['studio'][0] else: studio = "" if "director" in item2: director = " / ".join(item2['director']) if "writer" in item2: writer = " / ".join(item2['writer']) liz = xbmcgui.ListItem(item2['title']) liz.setPath(item2['file']) liz.setProperty('IsPlayable', 'true') liz.setInfo(type="Video", infoLabels={ "Title": item2['title'], "Episode": item2['episode'], "Season": item2['season'], "Studio": studio, "Premiered": item2['firstaired'], "Plot": plot, "TVshowTitle": item2['showtitle'], "Rating": str(float(item2['rating'])), "MPAA": item['mpaa'], "Playcount": item2['playcount'], "Director": director, "Writer": writer, "mediatype": "episode" }) liz.setProperty("episodeid", episodeid) liz.setProperty("episodeno", episodeno) liz.setProperty("resumetime", str(item2['resume']['position'])) liz.setProperty("totaltime", str(item2['resume']['total'])) liz.setProperty("type", 'episode') liz.setProperty( "fanart_image", item2['art'].get('tvshow.fanart', '')) liz.setProperty("dbid", str(item2['episodeid'])) liz.setArt(item2['art']) liz.setThumbnailImage(item2['art'].get( 'thumb', '')) liz.setIconImage('DefaultTVShows.png') hasVideo = False for key, value in item2['streamdetails'].iteritems( ): for stream in value: if 'video' in key: hasVideo = True liz.addStreamInfo(key, stream) # if duration wasnt in the streaminfo try adding the scraped one if not hasVideo: stream = {'duration': item2['runtime']} liz.addStreamInfo("video", stream) items.append(liz) count += 1 if count == limit: break if count == limit: break del json_query self.logMsg("getting next up episodes completed ", 2) return items def autoPlayPlayback(self): currentFile = xbmc.Player().getPlayingFile() # Get the active player result = self.getNowPlaying() if 'result' in result: itemtype = result["result"]["item"]["type"] addonSettings = xbmcaddon.Addon(id='service.nextup.notification') playMode = addonSettings.getSetting("autoPlayMode") currentepisodenumber = result["result"]["item"]["episode"] currentseasonid = result["result"]["item"]["season"] currentshowtitle = result["result"]["item"]["showtitle"].encode( 'utf-8') currentshowtitle = utils.unicodetoascii(currentshowtitle) tvshowid = result["result"]["item"]["tvshowid"] shortplayMode = addonSettings.getSetting("shortPlayMode") shortplayNotification = addonSettings.getSetting( "shortPlayNotification") shortplayLength = int( addonSettings.getSetting("shortPlayLength")) * 60 showpostplaypreview = addonSettings.getSetting( "showPostPlayPreview") == "true" showpostplay = addonSettings.getSetting("showPostPlay") == "true" shouldshowpostplay = showpostplay and showpostplaypreview # Try to get tvshowid by showtitle from kodidb if tvshowid is -1 like in strm streams which are added to kodi db if int(tvshowid) == -1: tvshowid = self.showtitle_to_id(title=currentshowtitle) self.logMsg("Fetched missing tvshowid " + str(tvshowid), 2) if (itemtype == "episode"): # Get current episodeid currentepisodeid = self.get_episode_id( showid=str(tvshowid), showseason=currentseasonid, showepisode=currentepisodenumber) else: # wtf am i doing here error.. #### self.logMsg("Error: cannot determine if episode", 1) return else: # wtf am i doing here error.. #### self.logMsg("Error: cannot determine if episode", 1) return self.currentepisodeid = currentepisodeid self.logMsg( "Getting details of next up episode for tvshow id: " + str(tvshowid), 1) if self.currenttvshowid != tvshowid: self.currenttvshowid = tvshowid self.playedinarow = 1 result = xbmc.executeJSONRPC( '{"jsonrpc": "2.0", "method": "VideoLibrary.GetEpisodes", "params": {"tvshowid": %d, ' '"properties": [ "title", "playcount", "season", "episode", "showtitle", "plot", ' '"file", "rating", "resume", "tvshowid", "art", "firstaired", "runtime", "writer", ' '"dateadded", "lastplayed" , "streamdetails"], "sort": {"method": "episode"}}, "id": 1}' % tvshowid) if result: result = unicode(result, 'utf-8', errors='ignore') result = json.loads(result) self.logMsg("Got details of next up episode %s" % str(result), 2) xbmc.sleep(100) # Find the next unwatched and the newest added episodes if "result" in result and "episodes" in result["result"]: includeWatched = addonSettings.getSetting( "includeWatched") == "true" episode = self.findNextEpisode(result, currentFile, includeWatched) if episode is None: # no episode get out of here return self.logMsg("episode details %s" % str(episode), 2) episodeid = episode["episodeid"] if includeWatched: includePlaycount = True else: includePlaycount = episode["playcount"] == 0 if includePlaycount and currentepisodeid != episodeid: # we have a next up episode nextUpPage = NextUpInfo( "script-nextup-notification-NextUpInfo.xml", addonSettings.getAddonInfo('path'), "default", "1080i") nextUpPage.setItem(episode) stillWatchingPage = StillWatchingInfo( "script-nextup-notification-StillWatchingInfo.xml", addonSettings.getAddonInfo('path'), "default", "1080i") stillWatchingPage.setItem(episode) playedinarownumber = addonSettings.getSetting( "playedInARow") playTime = xbmc.Player().getTime() totalTime = xbmc.Player().getTotalTime() self.logMsg( "played in a row settings %s" % str(playedinarownumber), 2) self.logMsg("played in a row %s" % str(self.playedinarow), 2) if int(self.playedinarow) <= int(playedinarownumber): self.logMsg( "showing next up page as played in a row is %s" % str(self.playedinarow), 2) if (shortplayNotification == "false") and (shortplayLength >= totalTime) and (shortplayMode == "true"): self.logMsg("hiding notification for short videos") else: nextUpPage.show() else: self.logMsg( "showing still watching page as played in a row %s" % str(self.playedinarow), 2) if (shortplayNotification == "false") and (shortplayLength >= totalTime) and (shortplayMode == "true"): self.logMsg("hiding notification for short videos") else: stillWatchingPage.show() if shouldshowpostplay: self.postPlayPlayback() while xbmc.Player().isPlaying() and ( totalTime - playTime > 1) and not nextUpPage.isCancel( ) and not nextUpPage.isWatchNow( ) and not stillWatchingPage.isStillWatching( ) and not stillWatchingPage.isCancel(): xbmc.sleep(100) try: playTime = xbmc.Player().getTime() totalTime = xbmc.Player().getTotalTime() except: pass if shortplayLength >= totalTime and shortplayMode == "true": #play short video and don't add to playcount self.playedinarow += 0 self.logMsg("Continuing short video autoplay - %s") if nextUpPage.isWatchNow( ) or stillWatchingPage.isStillWatching(): self.playedinarow = 1 shouldPlayDefault = not nextUpPage.isCancel() else: if int(self.playedinarow) <= int(playedinarownumber): nextUpPage.close() shouldPlayDefault = not nextUpPage.isCancel() shouldPlayNonDefault = nextUpPage.isWatchNow() else: stillWatchingPage.close() shouldPlayDefault = stillWatchingPage.isStillWatching( ) shouldPlayNonDefault = stillWatchingPage.isStillWatching( ) if nextUpPage.isWatchNow( ) or stillWatchingPage.isStillWatching(): self.playedinarow = 1 else: self.playedinarow += 1 if (shouldPlayDefault and not shouldshowpostplay and playMode == "0") or ( shouldPlayNonDefault and shouldshowpostplay and playMode == "0") or (shouldPlayNonDefault and playMode == "1"): self.logMsg( "playing media episode id %s" % str(episodeid), 2) # Signal to trakt previous episode watched AddonSignals.sendSignal( "NEXTUPWATCHEDSIGNAL", {'episodeid': self.currentepisodeid}) # if in postplaypreview mode clear the post play window as its not needed now if shouldshowpostplay: self.postplaywindow = None # Play media xbmc.executeJSONRPC( '{ "jsonrpc": "2.0", "id": 0, "method": "Player.Open", ' '"params": { "item": {"episodeid": ' + str(episode["episodeid"]) + '} } }')
def stopAll(self): if(len(self.played_information) == 0): return addonSettings = xbmcaddon.Addon(id='plugin.video.emby') self.printDebug("emby Service -> played_information : " + str(self.played_information)) for item_url in self.played_information: data = self.played_information.get(item_url) if(data != None): self.printDebug("emby Service -> item_url : " + item_url) self.printDebug("emby Service -> item_data : " + str(data)) runtime = data.get("runtime") currentPosition = data.get("currentPosition") item_id = data.get("item_id") refresh_id = data.get("refresh_id") currentFile = data.get("currentfile") type = data.get("Type") if(currentPosition != None and self.hasData(runtime)): runtimeTicks = int(runtime) self.printDebug("emby Service -> runtimeticks:" + str(runtimeTicks)) percentComplete = (currentPosition * 10000000) / runtimeTicks markPlayedAt = float(90) / 100 self.printDebug("emby Service -> Percent Complete:" + str(percentComplete) + " Mark Played At:" + str(markPlayedAt)) self.stopPlayback(data) if(refresh_id != None): #report updates playcount and resume status to Kodi and MB3 librarySync.updatePlayCount(item_id,type) # if its an episode see if autoplay is enabled if addonSettings.getSetting("autoPlaySeason")=="true" and type=="Episode": port = addonSettings.getSetting('port') host = addonSettings.getSetting('ipaddress') server = host + ":" + port userid = self.downloadUtils.getUserId() # add remaining unplayed episodes if applicable MB3Episode = ReadEmbyDB().getItem(item_id) userData = MB3Episode["UserData"] if userData!=None and userData["Played"]==True: pDialog = xbmcgui.DialogProgress() pDialog.create("Auto Play","Further Episode(s) in "+MB3Episode["SeasonName"]+" for "+MB3Episode["SeriesName"]+ " found","Cancel to stop automatic play of remaining episodes") count = 0 while(pDialog.iscanceled==False or count < 10): xbmc.sleep(1000) count += 1 progress = count * 10 remainingsecs = 10 - count pDialog.update(progress,"Further Episode(s) in "+MB3Episode["SeasonName"]+" for "+MB3Episode["SeriesName"]+ " found","Cancel to stop automatic play of remaining episodes", str(remainingsecs) + " second(s) until auto dismiss") if pDialog.iscanceled()==False: seasonId = MB3Episode["SeasonId"] jsonData = self.downloadUtils.downloadUrl("http://" + server + "/mediabrowser/Users/" + userid + "/Items?ParentId=" + seasonId + "&ImageTypeLimit=1&SortBy=SortName&SortOrder=Ascending&Filters=IsUnPlayed&IncludeItemTypes=Episode&IsVirtualUnaired=false&Recursive=true&IsMissing=False&format=json", suppress=False, popup=1 ) if(jsonData != ""): seasonData = json.loads(jsonData) if seasonData.get("Items") != None: PlaybackUtils().PLAYAllEpisodes(seasonData.get("Items")) self.played_information.clear() # stop transcoding - todo check we are actually transcoding? clientInfo = ClientInformation() txt_mac = clientInfo.getMachineId() url = ("http://%s:%s/mediabrowser/Videos/ActiveEncodings" % (addonSettings.getSetting('ipaddress'), addonSettings.getSetting('port'))) url = url + '?DeviceId=' + txt_mac self.downloadUtils.downloadUrl(url, type="DELETE")
class Service(): clientInfo = ClientInformation() addonName = clientInfo.getAddonName() WINDOW = xbmcgui.Window(10000) lastMetricPing = time.time() def __init__(self, *args): addonName = self.addonName self.logMsg("Starting NextUp Service", 0) self.logMsg("======== START %s ========" % addonName, 0) self.logMsg("KODI Version: %s" % xbmc.getInfoLabel("System.BuildVersion"), 0) self.logMsg("%s Version: %s" % (addonName, self.clientInfo.getVersion()), 0) def logMsg(self, msg, lvl=1): className = self.__class__.__name__ utils.logMsg("%s %s" % (self.addonName, className), str(msg), int(lvl)) def ServiceEntryPoint(self): player = Player() monitor = xbmc.Monitor() lastFile = None lastUnwatchedFile = None while not monitor.abortRequested(): # check every 5 sec if monitor.waitForAbort(5): # Abort was requested while waiting. We should exit break if xbmc.Player().isPlaying(): try: playTime = xbmc.Player().getTime() totalTime = xbmc.Player().getTotalTime() currentFile = xbmc.Player().getPlayingFile() addonSettings = xbmcaddon.Addon(id='service.nextup.notification') notificationtime = addonSettings.getSetting("autoPlaySeasonTime") nextUpDisabled = addonSettings.getSetting("disableNextUp") == "true" randomunwatchedtime = addonSettings.getSetting("displayRandomUnwatchedTime") displayrandomunwatched = addonSettings.getSetting("displayRandomUnwatched") == "true" showpostplay = addonSettings.getSetting("showPostPlay") == "true" showpostplaypreview = addonSettings.getSetting("showPostPlayPreview") == "true" if xbmcgui.Window(10000).getProperty("PseudoTVRunning") != "True" and not nextUpDisabled: if (not showpostplay or (showpostplaypreview and showpostplay)) and (totalTime - playTime <= int(notificationtime) and ( lastFile is None or lastFile != currentFile)) and totalTime != 0: lastFile = currentFile self.logMsg("Calling autoplayback totaltime - playtime is %s" % (totalTime - playTime), 2) player.autoPlayPlayback() self.logMsg("Netflix style autoplay succeeded.", 2) if (showpostplay and not showpostplaypreview) and (totalTime - playTime <= 10) and totalTime != 0: self.logMsg("Calling post playback", 2) player.postPlayPlayback() if displayrandomunwatched and (int(playTime) >= int(randomunwatchedtime)) and (int(playTime) < int(int(randomunwatchedtime)+100)) and ( lastUnwatchedFile is None or lastUnwatchedFile != currentFile): self.logMsg("randomunwatchedtime is %s" % (int(randomunwatchedtime)), 2) self.logMsg("Calling display unwatched", 2) lastUnwatchedFile = currentFile player.displayRandomUnwatched() except Exception as e: self.logMsg("Exception in Playback Monitor Service: %s" % e) self.logMsg("======== STOP %s ========" % self.addonName, 0)
def stopAll(played_information): if (len(played_information) == 0): return addonSettings = xbmcaddon.Addon(id='plugin.video.xbmb3c') printDebug("XBMB3C Service -> played_information : " + str(played_information)) for item_url in played_information: data = played_information.get(item_url) if (data != None): printDebug("XBMB3C Service -> item_url : " + item_url) printDebug("XBMB3C Service -> item_data : " + str(data)) watchedurl = data.get("watchedurl") positionurl = data.get("positionurl") deleteurl = data.get("deleteurl") runtime = data.get("runtime") currentPossition = data.get("currentPossition") item_id = data.get("item_id") refresh_id = data.get("refresh_id") currentFile = data.get("currentfile") BackgroundDataUpdaterThread().updateItem(refresh_id) if (currentPossition != None and hasData(runtime) and hasData(positionurl) and hasData(watchedurl)): runtimeTicks = int(runtime) printDebug("XBMB3C Service -> runtimeticks:" + str(runtimeTicks)) percentComplete = (currentPossition * 10000000) / runtimeTicks markPlayedAt = float( addonSettings.getSetting("markPlayedAt")) / 100 printDebug("XBMB3C Service -> Percent Complete:" + str(percentComplete) + " Mark Played At:" + str(markPlayedAt)) stopPlayback(currentFile, str(int(currentPossition * 10000000))) if (percentComplete > markPlayedAt): gotDeleted = 0 if (deleteurl != None and deleteurl != ""): printDebug("XBMB3C Service -> Offering Delete:" + str(deleteurl)) gotDeleted = deleteItem(deleteurl) if (newNextUpThread != None): newNextUpThread.updateNextUp() if (artworkRotationThread != None): artworkRotationThread.updateActionUrls() played_information.clear() # stop transcoding - todo check we are actually transcoding? clientInfo = ClientInformation() txt_mac = clientInfo.getMachineId() url = ("http://%s:%s/mediabrowser/Videos/ActiveEncodings" % (addonSettings.getSetting('ipaddress'), addonSettings.getSetting('port'))) url = url + '?DeviceId=' + txt_mac stopTranscoding(url)
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")
def authenticate(self, retreive=True): WINDOW = xbmcgui.Window(10000) self.addonSettings = xbmcaddon.Addon(id='plugin.video.xbmb3c') username = self.addonSettings.getSetting('username') token = WINDOW.getProperty("AccessToken" + username) if (token != None and token != ""): self.logMsg( "DownloadUtils -> Returning saved (WINDOW) AccessToken for user:"******" token:" + token) return token token = self.addonSettings.getSetting("AccessToken" + username) if (token != None and token != ""): WINDOW.setProperty("AccessToken" + username, token) self.logMsg( "DownloadUtils -> Returning saved (SETTINGS) AccessToken for user:"******" token:" + token) return token port = self.addonSettings.getSetting("port") host = self.addonSettings.getSetting("ipaddress") if (host == None or host == "" or host == "<none>" or port == None or port == ""): return "" if (retreive == False): return "" url = "http://" + host + ":" + port + "/mediabrowser/Users/AuthenticateByName?format=json" clientInfo = ClientInformation() txt_mac = clientInfo.getMachineId() version = clientInfo.getVersion() # get user info jsonData = self.downloadUrl("http://" + host + ":" + port + "/mediabrowser/Users/Public?format=json", authenticate=False) users = [] if (jsonData != ""): users = json.loads(jsonData) userHasPassword = False for user in users: name = user.get("Name") if (username == name): if (user.get("HasPassword") == True): userHasPassword = True break password = "" if (userHasPassword): password = xbmcgui.Dialog().input("Enter Password for user : "******""): sha1 = hashlib.sha1(password) sha1 = sha1.hexdigest() else: sha1 = 'da39a3ee5e6b4b0d3255bfef95601890afd80709' messageData = "username="******"&password="******"POST", authenticate=False) result = None accessToken = None try: xbmc.log("Auth_Reponce: " + str(resp)) result = json.loads(resp) accessToken = result.get("AccessToken") except: pass if (result != None and accessToken != None): userID = result.get("User").get("Id") self.logMsg("User Authenticated : " + accessToken) WINDOW.setProperty("AccessToken" + username, accessToken) WINDOW.setProperty("userid" + username, userID) self.addonSettings.setSetting("AccessToken" + username, accessToken) self.addonSettings.setSetting("userid" + username, userID) return accessToken else: self.logMsg("User NOT Authenticated") WINDOW.setProperty("AccessToken" + username, "") WINDOW.setProperty("userid" + username, "") self.addonSettings.setSetting("AccessToken" + username, "") self.addonSettings.setSetting("userid" + username, "") return ""
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) return else: return elif option == 0: # User selected Add user for adduser in additionalUsername: xbmc.log(str(adduser)) users.remove(adduser) 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)
class WriteKodiMusicDB(): textureCache = TextureCache() kodiversion = int(xbmc.getInfoLabel("System.BuildVersion")[:2]) addonName = ClientInformation().getAddonName() WINDOW = xbmcgui.Window(10000) username = WINDOW.getProperty('currUser') userid = WINDOW.getProperty('userId%s' % username) server = WINDOW.getProperty('server%s' % username) directpath = utils.settings('useDirectPaths') == "true" def logMsg(self, msg, lvl = 1): className = self.__class__.__name__ utils.logMsg("%s %s" % (self.addonName, className), msg, int(lvl)) def addOrUpdateArtistToKodiLibrary(self, MBitem, connection, cursor): # If the item already exist in the local Kodi DB we'll perform a full item update # If the item doesn't exist, we'll add it to the database kodiVersion = self.kodiversion embyId = MBitem["Id"] cursor.execute("SELECT kodi_id FROM emby WHERE emby_id = ?", (embyId,)) try: artistid = cursor.fetchone()[0] except: artistid = None ##### The artist details ##### lastScraped = datetime.now().strftime('%Y-%m-%d %H:%M:%S') dateadded = API().getDateCreated(MBitem) checksum = API().getChecksum(MBitem) name = MBitem['Name'] musicBrainzId = API().getProvider(MBitem, "musicBrainzArtist") genres = " / ".join(MBitem.get('Genres')) bio = API().getOverview(MBitem) # Associate artwork artworks = API().getAllArtwork(MBitem) thumb = artworks['Primary'] backdrops = artworks['Backdrop'] # List if thumb: thumb = "<thumb>%s</thumb>" % thumb if backdrops: fanart = "<fanart>%s</fanart>" % backdrops[0] else: fanart = "" ##### UPDATE THE ARTIST ##### if artistid: self.logMsg("UPDATE artist to Kodi library, Id: %s - Artist: %s" % (embyId, name), 1) if kodiVersion == 16: query = "UPDATE artist SET strArtist = ?, strMusicBrainzArtistID = ?, strGenres = ?, strBiography = ?, strImage = ?, strFanart = ?, lastScraped = ? WHERE idArtist = ?" cursor.execute(query, (name, musicBrainzId, genres, bio, thumb, fanart, lastScraped, artistid)) else: query = "UPDATE artist SET strArtist = ?, strMusicBrainzArtistID = ?, strGenres = ?, strBiography = ?, strImage = ?, strFanart = ?, lastScraped = ?, dateadded = ? WHERE idArtist = ?" cursor.execute(query, (name, musicBrainzId, genres, bio, thumb, fanart, lastScraped, dateadded, artistid)) # Update the checksum in emby table query = "UPDATE emby SET checksum = ? WHERE emby_id = ?" cursor.execute(query, (checksum, embyId)) ##### OR ADD THE ARTIST ##### else: self.logMsg("ADD artist to Kodi library, Id: %s - Artist: %s" % (embyId, name), 1) #safety checks: It looks like Emby supports the same artist multiple times in the database while Kodi doesn't allow that. In case that happens we just merge the artist in the Kodi database. # Safety check 1: does the artist already exist? cursor.execute("SELECT idArtist FROM artist WHERE strArtist = ? COLLATE NOCASE", (name,)) try: artistid = cursor.fetchone()[0] self.logMsg("Artist already exists in Kodi library - appending to existing object, Id: %s - Artist: %s - MusicBrainzId: %s - existing Kodi Id: %s" % (embyId, name, musicBrainzId, str(artistid)), 1) except: pass # Safety check 2: does the MusicBrainzArtistId already exist? cursor.execute("SELECT idArtist FROM artist WHERE strMusicBrainzArtistID = ?", (musicBrainzId,)) try: artistid = cursor.fetchone()[0] self.logMsg("Artist already exists in Kodi library - appending to existing object, Id: %s - Artist: %s - MusicBrainzId: %s - existing Kodi Id: %s" % (embyId, name, musicBrainzId, str(artistid)), 1) except: pass if not artistid: # Create the artist cursor.execute("select coalesce(max(idArtist),0) as artistid from artist") artistid = cursor.fetchone()[0] + 1 if kodiVersion == 16: query = "INSERT INTO artist(idArtist, strArtist, strMusicBrainzArtistID, strGenres, strBiography, strImage, strFanart, lastScraped) values(?, ?, ?, ?, ?, ?, ?, ?)" cursor.execute(query, (artistid, name, musicBrainzId, genres, bio, thumb, fanart, lastScraped)) else: query = "INSERT INTO artist(idArtist, strArtist, strMusicBrainzArtistID, strGenres, strBiography, strImage, strFanart, lastScraped, dateAdded) values(?, ?, ?, ?, ?, ?, ?, ?, ?)" cursor.execute(query, (artistid, name, musicBrainzId, genres, bio, thumb, fanart, lastScraped, dateadded)) # Create the reference in emby table query = "INSERT INTO emby(emby_id, kodi_id, media_type, checksum) values(?, ?, ?, ?)" cursor.execute(query, (embyId, artistid, "artist", checksum)) # Update artwork self.textureCache.addArtwork(artworks, artistid, "artist", cursor) def addOrUpdateAlbumToKodiLibrary(self, MBitem, connection, cursor): kodiVersion = self.kodiversion embyId = MBitem["Id"] # If the item already exist in the local Kodi DB we'll perform a full item update # If the item doesn't exist, we'll add it to the database cursor.execute("SELECT kodi_id FROM emby WHERE emby_id = ?", (embyId,)) try: albumid = cursor.fetchone()[0] except: albumid = None genres = MBitem.get('Genres') ##### The album details ##### lastScraped = datetime.now().strftime('%Y-%m-%d %H:%M:%S') dateadded = API().getDateCreated(MBitem) checksum = API().getChecksum(MBitem) name = MBitem['Name'] musicBrainzId = API().getProvider(MBitem, "musicBrainzAlbum") year = MBitem.get('ProductionYear') genre = " / ".join(genres) bio = API().getOverview(MBitem) MBartists = [] for item in MBitem['AlbumArtists']: MBartists.append(item['Name']) artists = " / ".join(MBartists) # Associate the artwork artworks = API().getAllArtwork(MBitem) thumb = artworks['Primary'] if thumb: thumb = "<thumb>%s</thumb>" % thumb ##### UPDATE THE ALBUM ##### if albumid: self.logMsg("UPDATE album to Kodi library, Id: %s - Title: %s" % (embyId, name), 1) if kodiVersion == 15: # Kodi Isengard query = "UPDATE album SET strAlbum = ?, strMusicBrainzAlbumID = ?, strArtists = ?, iYear = ?, strGenres = ?, strReview = ?, strImage = ?, lastScraped = ?, dateAdded = ?, strReleaseType = ? WHERE idAlbum = ?" cursor.execute(query, (name, musicBrainzId, artists, year, genre, bio, thumb, lastScraped, dateadded, "album", albumid)) elif kodiVersion == 16: query = "UPDATE album SET strAlbum = ?, strMusicBrainzAlbumID = ?, strArtists = ?, iYear = ?, strGenres = ?, strReview = ?, strImage = ?, lastScraped = ?, strReleaseType = ? WHERE idAlbum = ?" cursor.execute(query, (name, musicBrainzId, artists, year, genre, bio, thumb, lastScraped, "album", albumid)) else: # Kodi Gotham and Helix query = "UPDATE album SET strAlbum = ?, strMusicBrainzAlbumID = ?, strArtists = ?, iYear = ?, strGenres = ?, strReview = ?, strImage = ?, lastScraped = ?, dateAdded = ? WHERE idAlbum = ?" cursor.execute(query, (name, musicBrainzId, artists, year, genre, bio, thumb, lastScraped, dateadded, albumid)) # Update the checksum in emby table query = "UPDATE emby SET checksum = ? WHERE emby_id = ?" cursor.execute(query, (checksum, embyId)) ##### OR ADD THE ALBUM ##### else: self.logMsg("ADD album to Kodi library, Id: %s - Title: %s" % (embyId, name), 1) # Safety check: does the strMusicBrainzAlbumID already exist? cursor.execute("SELECT idAlbum FROM album WHERE strMusicBrainzAlbumID = ?", (musicBrainzId,)) try: albumid = cursor.fetchone()[0] except: # Create the album cursor.execute("select coalesce(max(idAlbum),0) as albumid from album") albumid = cursor.fetchone()[0] + 1 if kodiVersion == 15: # Kodi Isengard query = "INSERT INTO album(idAlbum, strAlbum, strMusicBrainzAlbumID, strArtists, iYear, strGenres, strReview, strImage, lastScraped, dateAdded, strReleaseType) values(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)" cursor.execute(query, (albumid, name, musicBrainzId, artists, year, genre, bio, thumb, lastScraped, dateadded, "album")) elif kodiVersion == 16: query = "INSERT INTO album(idAlbum, strAlbum, strMusicBrainzAlbumID, strArtists, iYear, strGenres, strReview, strImage, lastScraped, strReleaseType) values(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)" cursor.execute(query, (albumid, name, musicBrainzId, artists, year, genre, bio, thumb, lastScraped, "album")) else: # Kodi Gotham and Helix query = "INSERT INTO album(idAlbum, strAlbum, strMusicBrainzAlbumID, strArtists, iYear, strGenres, strReview, strImage, lastScraped, dateAdded) values(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)" cursor.execute(query, (albumid, name, musicBrainzId, artists, year, genre, bio, thumb, lastScraped, dateadded)) # Create the reference in emby table query = "INSERT INTO emby(emby_id, kodi_id, media_type, checksum) values(?, ?, ?, ?)" cursor.execute(query, (embyId, albumid, "album", checksum)) # Add genres self.AddGenresToMedia(albumid, genres, "album", cursor) # Update artwork self.textureCache.addArtwork(artworks, albumid, "album", cursor) # Link album to artists if MBartists: album_artists = MBitem['AlbumArtists'] else: album_artists = MBitem.get('ArtistItems', []) for artist in album_artists: cursor.execute("SELECT kodi_id FROM emby WHERE emby_id = ?", (artist['Id'],)) try: artistid = cursor.fetchone()[0] except: pass else: query = "INSERT OR REPLACE INTO album_artist(idArtist, idAlbum, strArtist) values(?, ?, ?)" cursor.execute(query, (artistid, albumid, artist['Name'])) # Update discography query = "INSERT OR REPLACE INTO discography(idArtist, strAlbum, strYear) values(?, ?, ?)" cursor.execute(query, (artistid, name, str(year))) def addOrUpdateSongToKodiLibrary(self, MBitem, connection, cursor): kodiVersion = self.kodiversion embyId = MBitem["Id"] # If the item already exist in the local Kodi DB we'll perform a full item update # If the item doesn't exist, we'll add it to the database cursor.execute("SELECT kodi_id FROM emby WHERE emby_id = ?", (embyId,)) try: songid = cursor.fetchone()[0] except: songid = None timeInfo = API().getTimeInfo(MBitem) userData = API().getUserData(MBitem) genres = MBitem.get('Genres') ##### The song details ##### playcount = userData.get('PlayCount') lastplayed = userData.get('LastPlayedDate') dateadded = API().getDateCreated(MBitem) checksum = API().getChecksum(MBitem) name = MBitem['Name'] musicBrainzId = API().getProvider(MBitem, "musicBrainzTrackId") genre = " / ".join(genres) artists = " / ".join(MBitem.get('Artists')) track = MBitem.get('IndexNumber') year = MBitem.get('ProductionYear') bio = API().getOverview(MBitem) duration = timeInfo.get('TotalTime') if utils.settings('directstreammusic') == "true": WINDOW = xbmcgui.Window(10000) username = WINDOW.getProperty('currUser') server = WINDOW.getProperty('server%s' % username) playurl = PlayUtils().directStream(MBitem, server, embyId, "Audio") filename = "stream.mp3" path = playurl.replace(filename, "") else: # Get the path and filename playurl = PlayUtils().directPlay(MBitem) path, filename = ntsplit(playurl) if "/" in playurl: path = "%s/" % path elif "\\" in playurl: path = "%s\\" % path # Validate the path in database cursor.execute("SELECT idPath as pathid FROM path WHERE strPath = ?", (path,)) try: pathid = cursor.fetchone()[0] except: cursor.execute("select coalesce(max(idPath),0) as pathid from path") pathid = cursor.fetchone()[0] + 1 query = "INSERT INTO path(idPath, strPath) values(?, ?)" cursor.execute(query, (pathid, path)) # Get the album cursor.execute("SELECT kodi_id FROM emby WHERE emby_id = ?", (MBitem.get("AlbumId"),)) try: albumid = cursor.fetchone()[0] except: # No album found, create a single's album cursor.execute("select coalesce(max(idAlbum),0) as albumid from album") albumid = cursor.fetchone()[0] + 1 if kodiVersion == 15: # Kodi Isengard query = "INSERT INTO album(idAlbum, strArtists, strGenres, iYear, dateAdded, strReleaseType) values(?, ?, ?, ?, ?, ?)" cursor.execute(query, (albumid, artists, genre, year, dateadded, "single")) elif kodiVersion == 16: query = "INSERT INTO album(idAlbum, strArtists, strGenres, iYear, strReleaseType) values(?, ?, ?, ?, ?)" cursor.execute(query, (albumid, artists, genre, year, "single")) else: # Kodi Gotham and Helix query = "INSERT INTO album(idAlbum, strArtists, strGenres, iYear, dateAdded) values(?, ?, ?, ?, ?)" cursor.execute(query, (albumid, artists, genre, year, dateadded)) # Link album to artists for artist in MBitem['ArtistItems']: cursor.execute("SELECT kodi_id FROM emby WHERE emby_id = ?", (artist['Id'],)) try: artistid = cursor.fetchone()[0] except: pass else: query = "INSERT OR REPLACE INTO album_artist(idArtist, idAlbum, strArtist) values(?, ?, ?)" cursor.execute(query, (artistid, albumid, artist['Name'])) ##### UPDATE THE SONG ##### if songid: self.logMsg("UPDATE song to Kodi library, Id: %s - Title: %s" % (embyId, name), 1) query = "UPDATE song SET idAlbum = ?, strArtists = ?, strGenres = ?, strTitle = ?, iTrack = ?, iDuration = ?, iYear = ?, strFilename = ?, strMusicBrainzTrackID = ?, iTimesPlayed = ?, lastplayed = ? WHERE idSong = ?" cursor.execute(query, (albumid, artists, genre, name, track, duration, year, filename, musicBrainzId, playcount, lastplayed, songid)) # Update the checksum in emby table query = "UPDATE emby SET checksum = ? WHERE emby_id = ?" cursor.execute(query, (checksum, embyId)) ##### OR ADD THE SONG ##### else: self.logMsg("ADD song to Kodi library, Id: %s - Title: %s" % (embyId, name), 1) # Create the song cursor.execute("select coalesce(max(idSong),0) as songid from song") songid = cursor.fetchone()[0] + 1 query = "INSERT INTO song(idSong, idAlbum, idPath, strArtists, strGenres, strTitle, iTrack, iDuration, iYear, strFileName, strMusicBrainzTrackID, iTimesPlayed, lastplayed) values(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)" cursor.execute(query, (songid, albumid, pathid, artists, genre, name, track, duration, year, filename, musicBrainzId, playcount, lastplayed)) # Create the reference in emby table query = "INSERT INTO emby(emby_id, kodi_id, media_type, checksum) values(?, ?, ?, ?)" cursor.execute(query, (embyId, songid, "song", checksum)) # Add genres self.AddGenresToMedia(songid, genres, "song", cursor) # Link song to album if albumid: query = "INSERT OR REPLACE INTO albuminfosong(idAlbumInfoSong, idAlbumInfo, iTrack, strTitle, iDuration) values(?, ?, ?, ?, ?)" cursor.execute(query, (songid, albumid, track, name, duration)) # Link song to artist for artist in MBitem.get('ArtistItems'): cursor.execute("SELECT kodi_id FROM emby WHERE emby_id = ?", (artist['Id'],)) try: artistid = cursor.fetchone()[0] except: pass else: query = "INSERT OR REPLACE INTO song_artist(idArtist, idSong, strArtist) values(?, ?, ?)" cursor.execute(query, (artistid, songid, artist['Name'])) # Update artwork self.textureCache.addArtwork(API().getAllArtwork(MBitem), songid, "song", cursor) def deleteItemFromKodiLibrary(self, id, connection, cursor): cursor.execute("SELECT kodi_id, media_type FROM emby WHERE emby_id=?", (id,)) try: result = cursor.fetchone() kodi_id = result[0] media_type = result[1] except: pass else: if "artist" in media_type: self.logMsg("Deleting artist from Kodi library, Id: %s" % id, 1) cursor.execute("DELETE FROM artist WHERE idArtist = ?", (kodi_id,)) elif "song" in media_type: self.logMsg("Deleting song from Kodi library, Id: %s" % id, 1) cursor.execute("DELETE FROM song WHERE idSong = ?", (kodi_id,)) elif "album" in media_type: self.logMsg("Deleting album from Kodi library, Id: %s" % id, 1) cursor.execute("DELETE FROM album WHERE idAlbum = ?", (kodi_id,)) # Delete the record in emby table cursor.execute("DELETE FROM emby WHERE emby_id = ?", (id,)) def addOrUpdateArt(self, imageUrl, kodiId, mediaType, imageType, cursor): if imageUrl: cacheimage = False cursor.execute("SELECT url FROM art WHERE media_id = ? AND media_type = ? AND type = ?", (kodiId, mediaType, imageType,)) try: url = cursor.fetchone()[0] except: # Image does not exists cacheimage = True self.logMsg("Adding Art Link for kodiId: %s (%s)" % (kodiId, imageUrl), 1) query = "INSERT INTO art(media_id, media_type, type, url) values(?, ?, ?, ?)" cursor.execute(query, (kodiId, mediaType, imageType, imageUrl)) else: if url != imageUrl: cacheimage = True self.logMsg("Updating Art Link for kodiId: %s (%s) -> (%s)" % (kodiId, url, imageUrl), 1) query = "UPDATE art SET url = ? WHERE media_id = ? and media_type = ? and type = ?" cursor.execute(query, (imageUrl, kodiId, mediaType, imageType)) # Cache fanart textures in Kodi texture cache if cacheimage and "fanart" in imageType: self.textureCache.CacheTexture(imageUrl) def AddGenresToMedia(self, id, genres, mediatype, cursor): if genres: for genre in genres: cursor.execute("SELECT idGenre as idGenre FROM genre WHERE strGenre = ? COLLATE NOCASE", (genre,)) try: idGenre = cursor.fetchone()[0] except: # Create the genre cursor.execute("select coalesce(max(idGenre),0) as idGenre from genre") idGenre = cursor.fetchone()[0] + 1 query = "INSERT INTO genre(idGenre, strGenre) values(?, ?)" cursor.execute(query, (idGenre, genre)) finally: # Assign the genre to item if "album" in mediatype: query = "INSERT OR REPLACE INTO album_genre(idGenre, idAlbum) values(?, ?)" cursor.execute(query, (idGenre, id)) elif "song" in mediatype: query = "INSERT OR REPLACE INTO song_genre(idGenre, idSong) values(?, ?)" cursor.execute(query, (idGenre, id)) def updateUserdata(self, userdata, connection, cursor): # This updates: LastPlayedDate, Playcount embyId = userdata['ItemId'] MBitem = ReadEmbyDB().getItem(embyId) if not MBitem: self.logMsg("UPDATE userdata to Kodi library FAILED, Item %s not found on server!" % embyId, 1) return # Get details checksum = API().getChecksum(MBitem) userdata = API().getUserData(MBitem) # Find the Kodi Id cursor.execute("SELECT kodi_id, media_type FROM emby WHERE emby_id = ?", (embyId,)) try: result = cursor.fetchone() kodiid = result[0] mediatype = result[1] self.logMsg("Found embyId: %s in database - kodiId: %s type: %s" % (embyId, kodiid, mediatype), 1) except: self.logMsg("Id: %s not found in the emby database table." % embyId, 1) else: if mediatype in ("song"): playcount = userdata['PlayCount'] dateplayed = userdata['LastPlayedDate'] query = "UPDATE song SET iTimesPlayed = ?, lastplayed = ? WHERE idSong = ?" cursor.execute(query, (playcount, dateplayed, kodiid)) #update the checksum in emby table query = "UPDATE emby SET checksum = ? WHERE emby_id = ?" cursor.execute(query, (checksum, embyId))
def getPlayUrl(self, server, id, result): addonSettings = xbmcaddon.Addon(id='plugin.video.xbmb3c') # if the path is local and depending on the video quality play we can direct play it do so- xbmc.log("XBMB3C getPlayUrl") if self.isDirectPlay(result) == True: xbmc.log("XBMB3C getPlayUrl -> Direct Play") playurl = result.get("Path") if playurl != None: #We have a path to play so play it USER_AGENT = 'QuickTime/7.7.4' # If the file it is not a media stub if (result.get("IsPlaceHolder") != True): if (result.get("VideoType") == "Dvd"): playurl = playurl + "/VIDEO_TS/VIDEO_TS.IFO" elif (result.get("VideoType") == "BluRay"): playurl = playurl + "/BDMV/index.bdmv" if addonSettings.getSetting('smbusername') == '': playurl = playurl.replace("\\\\", "smb://") else: playurl = playurl.replace( "\\\\", "smb://" + addonSettings.getSetting('smbusername') + ':' + addonSettings.getSetting('smbpassword') + '@') playurl = playurl.replace("\\", "/") if ("apple.com" in playurl): playurl += '?|User-Agent=%s' % USER_AGENT if addonSettings.getSetting('playFromStream') == "true": playurl = 'http://' + server + '/mediabrowser/Videos/' + id + '/stream?static=true' mediaSources = result.get("MediaSources") if (mediaSources != None): if mediaSources[0].get( 'DefaultAudioStreamIndex') != None: playurl = playurl + "&AudioStreamIndex=" + str( mediaSources[0].get('DefaultAudioStreamIndex')) if mediaSources[0].get( 'DefaultSubtitleStreamIndex') != None: playurl = playurl + "&SubtitleStreamIndex=" + str( mediaSources[0].get('DefaultAudioStreamIndex')) else: #No path or has a path but not sufficient network so transcode xbmc.log("XBMB3C getPlayUrl -> Transcode") if result.get("Type") == "Audio": playurl = 'http://' + server + '/mediabrowser/Audio/' + id + '/stream.mp3' else: clientInfo = ClientInformation() txt_mac = clientInfo.getMachineId() playurl = 'http://' + server + '/mediabrowser/Videos/' + id + '/master.m3u8?mediaSourceId=' + id playurl = playurl + '&videoCodec=h264' playurl = playurl + '&AudioCodec=aac,ac3' playurl = playurl + '&deviceId=' + txt_mac playurl = playurl + '&VideoBitrate=' + str( int(self.getVideoBitRate()) * 1000) mediaSources = result.get("MediaSources") if (mediaSources != None): if mediaSources[0].get('DefaultAudioStreamIndex') != None: playurl = playurl + "&AudioStreamIndex=" + str( mediaSources[0].get('DefaultAudioStreamIndex')) if mediaSources[0].get( 'DefaultSubtitleStreamIndex') != None: playurl = playurl + "&SubtitleStreamIndex=" + str( mediaSources[0].get('DefaultSubtitleStreamIndex')) return playurl.encode('utf-8')
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 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 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
def authenticate(self): WINDOW = xbmcgui.Window(10000) self.addonSettings = xbmcaddon.Addon(id='plugin.video.xbmb3c') token = WINDOW.getProperty("AccessToken" + self.addonSettings.getSetting('username')) if (token != None and token != ""): self.logMsg( "DownloadUtils -> Returning saved AccessToken for user : "******" token: " + token) return token port = self.addonSettings.getSetting("port") host = self.addonSettings.getSetting("ipaddress") if (host == None or host == "" or port == None or port == ""): return "" url = "http://" + self.addonSettings.getSetting( "ipaddress") + ":" + self.addonSettings.getSetting( "port") + "/mediabrowser/Users/AuthenticateByName?format=json" clientInfo = ClientInformation() txt_mac = clientInfo.getMachineId() version = clientInfo.getVersion() deviceName = self.addonSettings.getSetting('deviceName') deviceName = deviceName.replace("\"", "_") authString = "Mediabrowser Client=\"Kodi\",Device=\"" + deviceName + "\",DeviceId=\"" + txt_mac + "\",Version=\"" + version + "\"" headers = {'Accept-encoding': 'gzip', 'Authorization': authString} if self.addonSettings.getSetting( 'password') != None and self.addonSettings.getSetting( 'password') != '': sha1 = hashlib.sha1(self.addonSettings.getSetting('password')) sha1 = sha1.hexdigest() else: sha1 = 'da39a3ee5e6b4b0d3255bfef95601890afd80709' messageData = "username="******"&password="******"POST", authenticate=False, suppress=True) accessToken = None try: result = json.loads(resp) accessToken = result.get("AccessToken") except: pass if (accessToken != None): self.logMsg("User Authenticated : " + accessToken) WINDOW.setProperty( "AccessToken" + self.addonSettings.getSetting('username'), accessToken) WINDOW.setProperty( "userid" + self.addonSettings.getSetting('username'), result.get("User").get("Id")) return accessToken else: self.logMsg("User NOT Authenticated") WINDOW.setProperty( "AccessToken" + self.addonSettings.getSetting('username'), "") return ""
class ConnectionManager(): clientInfo = ClientInformation() user = UserClient() doUtils = DownloadUtils() addonName = clientInfo.getAddonName() addonId = clientInfo.getAddonId() addon = xbmcaddon.Addon() WINDOW = xbmcgui.Window(10000) def __init__(self): self.__language__ = self.addon.getLocalizedString def logMsg(self, msg, lvl=1): className = self.__class__.__name__ utils.logMsg("%s %s" % (self.addonName, className), msg, int(lvl)) def checkServer(self): self.WINDOW.setProperty("Server_Checked", "True") self.logMsg("Connection Manager Called", 2) server = self.user.getServer() if server != "": self.logMsg("Server already set", 2) return serverInfo = self.getServerDetails() try: prefix, ip, port = serverInfo.split(":") setServer = xbmcgui.Dialog().yesno( self.__language__(30167), "Proceed with the following server?", self.__language__(30169) + serverInfo) except: # serverInfo is None self.logMsg("getServerDetails failed", 1) xbmc.executebuiltin('Addon.OpenSettings(%s)' % self.addonId) return if setServer == 1: self.logMsg("Server selected. Saving information.", 1) utils.settings("ipaddress", ip.replace("/", "")) utils.settings("port", port) # If https, enable the setting if (prefix == 'https'): utils.settings('https', "true") else: self.logMsg("No server selected.", 1) xbmc.executebuiltin('Addon.OpenSettings(%s)' % self.addonId) return # Get List of public users self.logMsg("Getting user list", 1) server = "%s:%s" % (ip.replace("/", ""), port) url = "%s/mediabrowser/Users/Public?format=json" % serverInfo result = self.doUtils.downloadUrl(url, authenticate=False) if result == "": self.logMsg("Unable to connect to %s." % server, 1) return self.logMsg("Result: %s" % result, 2) # Process the list returned names = [] userList = [] for user in result: name = user['Name'] userList.append(name) if user['HasPassword']: name = "%s (Secure)" % name names.append(name) self.logMsg("User list: %s" % names, 1) resp = xbmcgui.Dialog().select(self.__language__(30200), names) if resp > -1: selected_user = userList[resp] self.logMsg("Selected User: %s" % selected_user, 1) utils.settings("username", selected_user) else: self.logMsg("No user selected.", 1) xbmc.executebuiltin('Addon.OpenSettings(%s)' % self.addonId) return musicDisabled = xbmcgui.Dialog().yesno("Music Setting", "Disable music library?") if musicDisabled: self.logMsg("User opted to disable music library.", 1) utils.settings('enableMusicSync', "false") else: # Music is enabled, prompt for direct stream musicPath = xbmcgui.Dialog().yesno( "Music Setting", "Direct stream the music library?", "Select this option only if you plan on listening to music outside your network." ) if musicPath: self.logMsg("User option to direct stream music library.", 1) utils.settings('directstreammusic', "true") return def getServerDetails(self): self.logMsg("Getting Server Details from Network") MULTI_GROUP = ("<broadcast>", 7359) MESSAGE = "who is EmbyServer?" sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.settimeout(6.0) sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 20) sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) sock.setsockopt(socket.SOL_IP, socket.IP_MULTICAST_LOOP, 1) sock.setsockopt(socket.IPPROTO_IP, socket.SO_REUSEADDR, 1) self.logMsg("MutliGroup : %s" % str(MULTI_GROUP), 2) self.logMsg("Sending UDP Data: %s" % MESSAGE, 2) sock.sendto(MESSAGE, MULTI_GROUP) try: data, addr = sock.recvfrom(1024) # buffer size is 1024 bytes self.logMsg("Received Response: %s" % data) # Get the address data = json.loads(data) return data['Address'] except: self.logMsg("No UDP Response") pass return None
class Service(): KodiMonitor = KodiMonitor.Kodi_Monitor() clientInfo = ClientInformation() addonName = clientInfo.getAddonName() logLevel = UserClient().getLogLevel() WINDOW = xbmcgui.Window(10000) newWebSocketThread = None newUserClient = None newLibraryThread = None warn_auth = True welcome_msg = True server_online = True def __init__(self, *args): addonName = self.addonName clientInfo = self.clientInfo logLevel = self.logLevel utils.window('getLogLevel', value=str(logLevel)) utils.window('kodiProfile_emby', value=xbmc.translatePath("special://profile")) # Initial logging self.logMsg("Starting Monitor", 0) self.logMsg("======== START %s ========" % addonName, 0) self.logMsg("Platform: %s" % (clientInfo.getPlatform()), 0) self.logMsg("KODI Version: %s" % xbmc.getInfoLabel('System.BuildVersion'), 0) self.logMsg("%s Version: %s" % (addonName, clientInfo.getVersion()), 0) self.logMsg("Using plugin paths: %s" % (utils.settings('useDirectPaths') != "true"), 0) self.logMsg("Log Level: %s" % logLevel, 0) # Reset window props for profile switch utils.window('Server_online', clear=True) utils.window('Server_status', clear=True) utils.window('startup', clear=True) utils.window('OnWakeSync', clear=True) utils.window('kodiScan', clear=True) utils.window('minDBVersionCheck', clear=True) # Set min DB version utils.window('minDBVersion', value="1.1.52") embyProperty = utils.window('Emby.nodes.total') propNames = [ "index","path","title","content", "inprogress.content","inprogress.title", "inprogress.content","inprogress.path", "nextepisodes.title","nextepisodes.content", "nextepisodes.path","unwatched.title", "unwatched.content","unwatched.path", "recent.title","recent.content","recent.path", "recentepisodes.title","recentepisodes.content", "recentepisodes.path","inprogressepisodes.title", "inprogressepisodes.content","inprogressepisodes.path" ] if embyProperty: totalNodes = int(embyProperty) for i in range(totalNodes): for prop in propNames: utils.window('Emby.nodes.%s.%s' % (str(i), prop), clear=True) def logMsg(self, msg, lvl=1): className = self.__class__.__name__ utils.logMsg("%s %s" % (self.addonName, className), msg, int(lvl)) def ServiceEntryPoint(self): kodiProfile = xbmc.translatePath("special://profile") # Server auto-detect ConnectionManager().checkServer() # Initialize important threads user = UserClient() player = Player() ws = WebSocketThread() library = LibrarySync() # Sync and progress report lastProgressUpdate = datetime.today() while not self.KodiMonitor.abortRequested(): # Before proceeding, need to make sure: # 1. Server is online # 2. User is set # 3. User has access to the server if utils.window("kodiProfile_emby") != kodiProfile: # Profile change happened, terminate this thread self.logMsg("Kodi profile was: %s and changed to: %s. Terminating old Emby thread." % (kodiProfile, utils.window("kodiProfile_emby")), 1) break if utils.window('Server_online') == "true": # Emby server is online # Verify if user is set and has access to the server if (user.currUser is not None) and user.HasAccess: # If an item is playing if xbmc.Player().isPlaying(): try: # Update and report progress playTime = xbmc.Player().getTime() totalTime = xbmc.Player().getTotalTime() currentFile = player.currentFile # Update positionticks if player.played_information.get(currentFile) is not None: player.played_information[currentFile]['currentPosition'] = playTime td = datetime.today() - lastProgressUpdate secDiff = td.seconds # Report progress to Emby server if (secDiff > 3): player.reportPlayback() lastProgressUpdate = datetime.today() elif utils.window('commandUpdate') == "true": # Received a remote control command that # requires updating immediately utils.window('commandUpdate', clear=True) player.reportPlayback() lastProgressUpdate = da4tetime.today() except Exception as e: self.logMsg("Exception in Playback Monitor Service: %s" % e, 1) pass else: # Start up events self.warn_auth = True if utils.settings('supressConnectMsg') == "false": if self.welcome_msg: # Reset authentication warnings self.welcome_msg = False # Get additional users additionalUsers = user.AdditionalUser if additionalUsers: add = ", %s" % ", ".join(additionalUsers) else: add = "" xbmcgui.Dialog().notification("Emby server", "Welcome %s%s!" % (user.currUser, add), icon="special://home/addons/plugin.video.emby/icon.png", time=2000, sound=False) # Start the Websocket Client if (self.newWebSocketThread is None): self.newWebSocketThread = "Started" ws.start() # Start the Library Sync Thread if (self.newLibraryThread is None): self.newLibraryThread = "Started" library.start() else: if (user.currUser is None) and self.warn_auth: # Alert user is not authenticated and suppress future warning self.warn_auth = False self.logMsg("Not authenticated yet.", 1) # User access is restricted. # Keep verifying until access is granted # unless server goes offline or Kodi is shut down. while user.HasAccess == False: # Verify access with an API call user.hasAccess() if utils.window('Server_online') != "true": # Server went offline break if self.KodiMonitor.waitForAbort(5): # Abort was requested while waiting. We should exit break else: # Wait until Emby server is online # or Kodi is shut down. while not self.KodiMonitor.abortRequested(): if user.getServer() == "": # No server info set in add-on settings pass elif user.getPublicUsers() == False: # Server is offline. # Alert the user and suppress future warning if self.server_online: self.logMsg("Server is offline.", 1) utils.window('Server_online', value="false") xbmcgui.Dialog().notification("Error connecting", "%s Server is unreachable." % self.addonName, icon="special://home/addons/plugin.video.emby/icon.png", sound=False) self.server_online = False else: # Server is online if not self.server_online: # Server was offline when Kodi started. # Wait for server to be fully established. if self.KodiMonitor.waitForAbort(5): # Abort was requested while waiting. break # Alert the user that server is online. xbmcgui.Dialog().notification("Emby server", "Welcome %s!" % user.currUser, icon="special://home/addons/plugin.video.emby/icon.png", time=2000, sound=False) self.server_online = True self.logMsg("Server is online and ready.", 1) utils.window('Server_online', value="true") # Start the User client if self.newUserClient is None: self.newUserClient = "Started" user.start() break if self.KodiMonitor.waitForAbort(1): # Abort was requested while waiting. break if self.KodiMonitor.waitForAbort(1): # Abort was requested while waiting. We should exit break ##### Emby thread is terminating. ##### # If music is enabled and direct stream for music is enabled # We use Kodi pathsubstitution to allow for music to play outside network # The setting needs to be set before Kodi starts. if utils.settings('enableMusicSync') == "true" and utils.settings('directstreammusic') == "true": # We need to keep track of the settings alternate = utils.settings('altip') == "true" pathsub = utils.settings('pathsub') == "true" if pathsub and not alternate: # Path sub in place, but primary address in use, remove it utils.pathsubstitution(False) elif not pathsub and alternate: # Path sub not in place, but secondary address in use, add it utils.pathsubstitution() if (self.newWebSocketThread is not None): ws.stopClient() if (self.newUserClient is not None): user.stopClient() self.logMsg("======== STOP %s ========" % self.addonName, 0)
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() # Set user preferences in settings self.setUserPref() self.currUser = username 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