diff --git a/resources/lib/DownloadUtils.py b/resources/lib/DownloadUtils.py
index 04d2da85..5c47144d 100644
--- a/resources/lib/DownloadUtils.py
+++ b/resources/lib/DownloadUtils.py
@@ -1,32 +1,38 @@
-import xbmc
-import xbmcgui
-import xbmcaddon
+# -*- coding: utf-8 -*-
+
+##################################################################################################
-import requests
import json
+import requests
import logging
-import Utils as utils
-from ClientInformation import ClientInformation
-from requests.packages.urllib3.exceptions import InsecureRequestWarning
+import xbmc
+import xbmcgui
+
+import utils
+import clientinfo
+
+##################################################################################################
# Disable requests logging
+from requests.packages.urllib3.exceptions import InsecureRequestWarning
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
-#logging.getLogger("requests").setLevel(logging.WARNING)
+#logging.getLogger('requests').setLevel(logging.WARNING)
+
+##################################################################################################
+
class DownloadUtils():
# Borg - multiple instances, shared state
_shared_state = {}
- clientInfo = ClientInformation()
-
+ clientInfo = clientinfo.ClientInfo()
addonName = clientInfo.getAddonName()
- addon = xbmcaddon.Addon()
- WINDOW = xbmcgui.Window(10000)
# Requests session
s = None
- timeout = 60
+ timeout = 30
+
def __init__(self):
@@ -34,41 +40,44 @@ class DownloadUtils():
def logMsg(self, msg, lvl=1):
- self.className = self.__class__.__name__
- utils.logMsg("%s %s" % (self.addonName, self.className), msg, int(lvl))
+ className = self.__class__.__name__
+ utils.logMsg("%s %s" % (self.addonName, className), msg, lvl)
+
def setUsername(self, username):
- # Reserved for UserClient only
+ # Reserved for userclient only
self.username = username
self.logMsg("Set username: %s" % username, 2)
def setUserId(self, userId):
- # Reserved for UserClient only
+ # Reserved for userclient only
self.userId = userId
self.logMsg("Set userId: %s" % userId, 2)
def setServer(self, server):
- # Reserved for UserClient only
+ # Reserved for userclient only
self.server = server
self.logMsg("Set server: %s" % server, 2)
def setToken(self, token):
- # Reserved for UserClient only
+ # Reserved for userclient only
self.token = token
self.logMsg("Set token: %s" % token, 2)
def setSSL(self, ssl, sslclient):
- # Reserved for UserClient only
+ # 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"
+ url = "{server}/emby/Sessions/Capabilities/Full?format=json"
data = {
+
'PlayableMediaTypes': "Audio,Video",
'SupportsMediaControl': True,
'SupportedCommands': (
@@ -86,49 +95,57 @@ class DownloadUtils():
}
self.logMsg("Capabilities URL: %s" % url, 2)
- self.logMsg("PostData: %s" % data, 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.")
+ self.downloadUrl(url, postBody=data, type="POST")
+ self.logMsg("Posted capabilities to %s" % self.server, 2)
# Attempt at getting sessionId
- url = "{server}/mediabrowser/Sessions?DeviceId=%s&format=json" % deviceId
-
+ url = "{server}/emby/Sessions?DeviceId=%s&format=json" % deviceId
+ result = self.downloadUrl(url)
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:
+ sessionId = result[0]['Id']
+
+ except (KeyError, TypeError):
self.logMsg("Failed to retrieve sessionId.", 1)
+
else:
+ self.logMsg("Session: %s" % result, 2)
+ self.logMsg("SessionId: %s" % sessionId, 1)
+ utils.window('emby_sessionId', value=sessionId)
+
# Post any permanent additional users
- additionalUsers = utils.settings('additionalUsers').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)
+ additionalUsers = utils.settings('additionalUsers')
+ if additionalUsers:
+
+ additionalUsers = additionalUsers.split(',')
+ self.logMsg(
+ "List of permanent users added to the session: %s"
+ % additionalUsers, 1)
+
+ # Get the user list from server to get the userId
+ url = "{server}/emby/Users?format=json"
+ result = self.downloadUrl(url)
+
+ for additional in additionalUsers:
+ addUser = additional.decode('utf-8').lower()
+
+ # Compare to server users to list of permanent additional users
+ for user in result:
+ username = user['Name'].lower()
- 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)
+ userId = user['Id']
+ url = (
+ "{server}/emby/Sessions/%s/Users/%s?format=json"
+ % (sessionId, userId)
+ )
+ self.downloadUrl(url, postBody={}, type="POST")
+
def startSession(self):
- self.deviceId = self.clientInfo.getMachineId()
+ self.deviceId = self.clientInfo.getDeviceId()
# User is identified from this point
# Attach authenticated header to the session
@@ -152,7 +169,7 @@ class DownloadUtils():
self.s.mount("http://", requests.adapters.HTTPAdapter(max_retries=1))
self.s.mount("https://", requests.adapters.HTTPAdapter(max_retries=1))
- self.logMsg("Requests session started on: %s" % self.server)
+ self.logMsg("Requests session started on: %s" % self.server, 1)
def stopSession(self):
try:
@@ -165,93 +182,116 @@ class DownloadUtils():
clientInfo = self.clientInfo
deviceName = clientInfo.getDeviceName()
- deviceId = clientInfo.getMachineId()
+ deviceId = clientInfo.getDeviceId()
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}
-
+ 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
+ auth = (
+ 'MediaBrowser UserId="%s", Client="Kodi", Device="%s", DeviceId="%s", Version="%s"'
+ % (userId, deviceName, deviceId, version))
+ header = {
- def downloadUrl(self, url, postBody=None, type="GET", authenticate=True):
+ '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", parameters=None, 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)
+ # Replace for the real values
+ url = url.replace("{server}", self.server)
+ url = url.replace("{UserId}", self.userId)
- self.logMsg("URL: %s" % url, 2)
# Prepare request
if type == "GET":
- r = s.get(url, json=postBody, timeout=timeout)
+ r = s.get(url, json=postBody, params=parameters, timeout=timeout)
elif type == "POST":
r = s.post(url, json=postBody, timeout=timeout)
elif type == "DELETE":
r = s.delete(url, json=postBody, timeout=timeout)
except AttributeError:
-
+ # request session does not exists
# 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)
+ self.userId = utils.window('emby_currUser')
+ self.server = utils.window('emby_server%s' % self.userId)
+ self.token = utils.window('emby_accessToken%s' % self.userId)
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
+ if utils.settings('sslverify') == "true":
+ verifyssl = True
+ if utils.settings('sslcert') != "None":
+ cert = utils.settings('sslcert')
- # Replace for the real values and append api_key
- url = url.replace("{server}", self.server, 1)
- url = url.replace("{UserId}", self.userId, 1)
+ # Replace for the real values
+ url = url.replace("{server}", self.server)
+ url = url.replace("{UserId}", self.userId)
- 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)
+ r = requests.get(url,
+ json=postBody,
+ params=parameters,
+ 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)
+ 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)
+ 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
@@ -263,41 +303,49 @@ class DownloadUtils():
# Prepare request
if type == "GET":
- r = requests.get(url, json=postBody, headers=header, timeout=timeout, verify=verifyssl)
+ r = requests.get(url,
+ json=postBody,
+ params=parameters,
+ headers=header,
+ timeout=timeout,
+ verify=verifyssl)
+
elif type == "POST":
- r = requests.post(url, json=postBody, headers=header, timeout=timeout, verify=verifyssl)
+ r = requests.post(url,
+ json=postBody,
+ headers=header,
+ timeout=timeout,
+ verify=verifyssl)
- # Process the response
+ ##### THE RESPONSE #####
+ self.logMsg(r.url, 2)
if r.status_code == 204:
# No body in the response
self.logMsg("====== 204 Success ======", 2)
- return default_link
elif r.status_code == requests.codes.ok:
+
try:
# UTF-8 - JSON object
r = r.json()
self.logMsg("====== 200 Success ======", 2)
self.logMsg("Response: %s" % r, 2)
return r
+
except:
- if r.headers.get('content-type') == "text/html":
- pass
- else:
+ if r.headers.get('content-type') != "text/html":
self.logMsg("Unable to convert the response for: %s" % url, 1)
else:
r.raise_for_status()
-
- return default_link
- # TO REVIEW EXCEPTIONS
+ ##### EXCEPTIONS #####
+
except requests.exceptions.ConnectionError as e:
# Make the addon aware of status
- if WINDOW.getProperty("Server_online") != "false":
+ if utils.window('emby_online') != "false":
self.logMsg("Server unreachable at: %s" % url, 0)
self.logMsg(e, 2)
- WINDOW.setProperty("Server_online", "false")
- pass
+ utils.window('emby_online', value="false")
except requests.exceptions.ConnectTimeout as e:
self.logMsg("Server timeout at: %s" % url, 0)
@@ -307,29 +355,35 @@ class DownloadUtils():
if r.status_code == 401:
# Unauthorized
- status = WINDOW.getProperty("Server_status")
+ status = utils.window('emby_serverStatus')
- if 'x-application-error-code' in r.headers:
+ if 'X-Application-Error-Code' in r.headers:
+ # Emby server errors
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)
+ utils.window('emby_serverStatus', value="restricted")
+ xbmcgui.Dialog().notification(
+ heading="Emby server",
+ message="Access restricted.",
+ icon=xbmcgui.NOTIFICATION_ERROR,
+ time=5000)
return False
+
elif r.headers['X-Application-Error-Code'] == "UnauthorizedAccessException":
- # User tried to do something his emby account doesn't allow - admin restricted in some way
+ # User tried to do something his emby account doesn't allow
pass
- elif (status == "401") or (status == "Auth"):
- pass
-
- else:
- # Tell UserClient token has been revoked.
- WINDOW.setProperty("Server_status", "401")
+ elif status not in ("401", "Auth"):
+ # Tell userclient token has been revoked.
+ utils.window('emby_serverStatus', value="401")
self.logMsg("HTTP Error: %s" % e, 0)
- xbmcgui.Dialog().notification("Error connecting", "Unauthorized.", xbmcgui.NOTIFICATION_ERROR)
+ xbmcgui.Dialog().notification(
+ heading="Error connecting",
+ message="Unauthorized.",
+ icon=xbmcgui.NOTIFICATION_ERROR)
return 401
- elif (r.status_code == 301) or (r.status_code == 302):
+ elif r.status_code in (301, 302):
# Redirects
pass
elif r.status_code == 400:
@@ -344,4 +398,4 @@ class DownloadUtils():
self.logMsg("Unknown error connecting to: %s" % url, 0)
self.logMsg(e, 1)
- return default_link
+ return default_link
\ No newline at end of file
diff --git a/resources/lib/Entrypoint.py b/resources/lib/Entrypoint.py
index 95a5a327..588b8d3c 100644
--- a/resources/lib/Entrypoint.py
+++ b/resources/lib/Entrypoint.py
@@ -1,100 +1,157 @@
-import xbmcaddon
-import xbmcplugin
+# -*- coding: utf-8 -*-
+
+#################################################################################################
+
+import json
+import os
+import sys
+import urlparse
+
import xbmc
+import xbmcaddon
import xbmcgui
import xbmcvfs
-import os, sys
-import threading
-import json
-import urllib
-import time
+import xbmcplugin
-WINDOW = xbmcgui.Window(10000)
+import artwork
+import utils
+import clientinfo
+import downloadutils
+import read_embyserver as embyserver
+import embydb_functions as embydb
+import playlist
+import playbackutils as pbutils
+import playutils
+import api
-import Utils as utils
-from ClientInformation import ClientInformation
-from PlaybackUtils import PlaybackUtils
-from PlayUtils import PlayUtils
-from DownloadUtils import DownloadUtils
-from ReadEmbyDB import ReadEmbyDB
-from API import API
-from UserPreferences import UserPreferences
+#################################################################################################
-##### Play items via plugin://plugin.video.emby/ #####
-def doPlayback(id):
- url = "{server}/mediabrowser/Users/{UserId}/Items/%s?format=json&ImageTypeLimit=1" % id
- result = DownloadUtils().downloadUrl(url)
- item = PlaybackUtils().PLAY(result, setup="default")
+def doPlayback(itemid, dbid):
-#### DO RESET AUTH #####
+ emby = embyserver.Read_EmbyServer()
+ item = emby.getItem(itemid)
+ pbutils.PlaybackUtils(item).play(itemid, dbid)
+
+##### DO RESET AUTH #####
def resetAuth():
# User tried login and failed too many times
- resp = xbmcgui.Dialog().yesno("Warning", "Emby might lock your account if you fail to log in too many times. Proceed anyway?")
+ resp = xbmcgui.Dialog().yesno(
+ heading="Warning",
+ line1=(
+ "Emby might lock your account if you fail to log in too many times. "
+ "Proceed anyway?"))
if resp == 1:
- xbmc.log("Reset login attempts.")
- WINDOW.setProperty("Server_status", "Auth")
+ utils.logMsg("EMBY", "Reset login attempts.", 1)
+ utils.window('emby_serverStatus', value="Auth")
else:
xbmc.executebuiltin('Addon.OpenSettings(plugin.video.emby)')
-### ADD ADDITIONAL USERS ###
+def addDirectoryItem(label, path, folder=True):
+ li = xbmcgui.ListItem(label, path=path)
+ li.setThumbnailImage("special://home/addons/plugin.video.emby/icon.png")
+ li.setArt({"fanart":"special://home/addons/plugin.video.emby/fanart.jpg"})
+ li.setArt({"landscape":"special://home/addons/plugin.video.emby/fanart.jpg"})
+ xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=path, listitem=li, isFolder=folder)
+
+def doMainListing():
+
+ xbmcplugin.setContent(int(sys.argv[1]), 'files')
+ # Get emby nodes from the window props
+ embyprops = utils.window('Emby.nodes.total')
+ if embyprops:
+ totalnodes = int(embyprops)
+ for i in range(totalnodes):
+ path = utils.window('Emby.nodes.%s.index' % i)
+ if not path:
+ path = utils.window('Emby.nodes.%s.content' % i)
+ label = utils.window('Emby.nodes.%s.title' % i)
+ if path:
+ addDirectoryItem(label, path)
+
+ # some extra entries for settings and stuff. TODO --> localize the labels
+ addDirectoryItem("Network credentials", "plugin://plugin.video.emby/?mode=passwords", False)
+ addDirectoryItem("Settings", "plugin://plugin.video.emby/?mode=settings", False)
+ addDirectoryItem("Add user to session", "plugin://plugin.video.emby/?mode=adduser", False)
+ #addDirectoryItem("Cache all images to Kodi texture cache (advanced)", "plugin://plugin.video.emby/?mode=texturecache")
+ addDirectoryItem("Perform manual sync", "plugin://plugin.video.emby/?mode=manualsync", False)
+ addDirectoryItem(
+ label="Repair local database (force update all content)",
+ path="plugin://plugin.video.emby/?mode=repair",
+ folder=False)
+ addDirectoryItem(
+ label="Perform local database reset (full resync)",
+ path="plugin://plugin.video.emby/?mode=reset",
+ folder=False)
+ addDirectoryItem(
+ label="Sync Emby Theme Media to Kodi",
+ path="plugin://plugin.video.emby/?mode=thememedia",
+ folder=False)
+
+ xbmcplugin.endOfDirectory(int(sys.argv[1]))
+
+##### ADD ADDITIONAL USERS #####
def addUser():
- doUtils = DownloadUtils()
- clientInfo = ClientInformation()
- currUser = WINDOW.getProperty("currUser")
- deviceId = clientInfo.getMachineId()
+ doUtils = downloadutils.DownloadUtils()
+ clientInfo = clientinfo.ClientInfo()
+ deviceId = clientInfo.getDeviceId()
deviceName = clientInfo.getDeviceName()
+ userid = utils.window('emby_currUser')
+ dialog = xbmcgui.Dialog()
# Get session
- url = "{server}/mediabrowser/Sessions?DeviceId=%s" % deviceId
+ url = "{server}/emby/Sessions?DeviceId=%s&format=json" % deviceId
result = doUtils.downloadUrl(url)
try:
- sessionId = result[0][u'Id']
- additionalUsers = result[0][u'AdditionalUsers']
+ sessionId = result[0]['Id']
+ additionalUsers = result[0]['AdditionalUsers']
# Add user to session
userlist = {}
users = []
- url = "{server}/mediabrowser/Users?IsDisabled=false&IsHidden=false"
+ url = "{server}/emby/Users?IsDisabled=false&IsHidden=false&format=json"
result = doUtils.downloadUrl(url)
# pull the list of users
for user in result:
- name = user[u'Name']
- userId = user[u'Id']
- if currUser not in name:
+ name = user['Name']
+ userId = user['Id']
+ if userid != userId:
userlist[name] = userId
users.append(name)
# Display dialog if there's additional users
if additionalUsers:
- option = xbmcgui.Dialog().select("Add/Remove user from the session", ["Add user", "Remove user"])
+ option = dialog.select("Add/Remove user from the session", ["Add user", "Remove user"])
# Users currently in the session
additionalUserlist = {}
additionalUsername = []
# Users currently in the session
for user in additionalUsers:
- name = user[u'UserName']
- userId = user[u'UserId']
+ name = user['UserName']
+ userId = user['UserId']
additionalUserlist[name] = userId
additionalUsername.append(name)
if option == 1:
# User selected Remove user
- resp = xbmcgui.Dialog().select("Remove user from the session", additionalUsername)
+ resp = 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)
+ url = "{server}/emby/Sessions/%s/Users/%s" % (sessionId, selected_userId)
+ doUtils.downloadUrl(url, postBody={}, type="DELETE")
+ dialog.notification(
+ heading="Success!",
+ message="%s removed from viewing session" % selected,
+ icon="special://home/addons/plugin.video.emby/icon.png",
+ time=1000)
# clear picture
- position = WINDOW.getProperty('EmbyAdditionalUserPosition.' + selected_userId)
- WINDOW.clearProperty('EmbyAdditionalUserImage.' + str(position))
+ position = utils.window('EmbyAdditionalUserPosition.%s' % selected_userId)
+ utils.window('EmbyAdditionalUserImage.%s' % position, clear=True)
return
else:
return
@@ -111,138 +168,143 @@ def addUser():
return
# Subtract any additional users
- xbmc.log("Displaying list of users: %s" % users)
- resp = xbmcgui.Dialog().select("Add user to the session", users)
+ utils.logMsg("EMBY", "Displaying list of users: %s" % users)
+ resp = dialog.select("Add user to the session", users)
# post additional user
if resp > -1:
selected = users[resp]
selected_userId = userlist[selected]
- url = "{server}/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)
+ url = "{server}/emby/Sessions/%s/Users/%s" % (sessionId, selected_userId)
+ doUtils.downloadUrl(url, postBody={}, type="POST")
+ dialog.notification(
+ heading="Success!",
+ message="%s added to viewing session" % selected,
+ icon="special://home/addons/plugin.video.emby/icon.png",
+ time=1000)
except:
- xbmc.log("Failed to add user to session.")
- xbmcgui.Dialog().notification("Error", "Unable to add/remove user from the session.", xbmcgui.NOTIFICATION_ERROR)
+ utils.logMsg("EMBY", "Failed to add user to session.")
+ dialog.notification(
+ heading="Error",
+ message="Unable to add/remove user from the session.",
+ icon=xbmcgui.NOTIFICATION_ERROR)
- 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))
+ # Add additional user images
+ # always clear the individual items first
+ totalNodes = 10
+ for i in range(totalNodes):
+ if not utils.window('EmbyAdditionalUserImage.%s' % i):
+ break
+ utils.window('EmbyAdditionalUserImage.%s' % i)
- url = "{server}/mediabrowser/Sessions?DeviceId=%s" % deviceId
+ url = "{server}/emby/Sessions?DeviceId=%s" % deviceId
+ result = doUtils.downloadUrl(url)
+ additionalUsers = result[0]['AdditionalUsers']
+ count = 0
+ for additionaluser in additionalUsers:
+ url = "{server}/emby/Users/%s?format=json" % additionaluser['UserId']
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
+ utils.window('EmbyAdditionalUserImage.%s' % count,
+ value=artwork.Artwork().getUserArtwork(result, 'Primary'))
+ utils.window('EmbyAdditionalUserPosition.%s' % additionaluser['UserId'], value=str(count))
+ count +=1
-# THEME MUSIC/VIDEOS
+##### THEME MUSIC/VIDEOS #####
def getThemeMedia():
- doUtils = DownloadUtils()
- playUtils = PlayUtils()
-
- currUser = WINDOW.getProperty('currUser')
- server = WINDOW.getProperty('server%s' % currUser)
+ doUtils = downloadutils.DownloadUtils()
+ dialog = xbmcgui.Dialog()
playback = None
- library = xbmc.translatePath("special://profile/addon_data/plugin.video.emby/library/").decode('utf-8')
-
# Choose playback method
- resp = xbmcgui.Dialog().select("Choose playback method for your themes", ["Direct Play", "Direct Stream"])
+ resp = dialog.select("Playback method for your themes", ["Direct Play", "Direct Stream"])
if resp == 0:
- # Direct Play
playback = "DirectPlay"
elif resp == 1:
- # Direct Stream
playback = "DirectStream"
- else:return
-
- # Set custom path for user
- tvtunes_path = xbmc.translatePath("special://profile/addon_data/script.tvtunes/").decode('utf-8')
- if xbmcvfs.exists(tvtunes_path):
- tvtunes = xbmcaddon.Addon(id="script.tvtunes")
- tvtunes.setSetting('custom_path_enable', "true")
- tvtunes.setSetting('custom_path', library)
- xbmc.log("TV Tunes custom path is enabled and set.")
else:
- # if it does not exist this will not work so warn user, often they need to edit the settings first for it to be created.
- dialog = xbmcgui.Dialog()
- dialog.ok('Warning', 'The settings file does not exist in tvtunes. Go to the tvtunes addon and change a setting, then come back and re-run')
return
-
+ library = xbmc.translatePath(
+ "special://profile/addon_data/plugin.video.emby/library/").decode('utf-8')
# Create library directory
if not xbmcvfs.exists(library):
xbmcvfs.mkdir(library)
+ # Set custom path for user
+ tvtunes_path = xbmc.translatePath(
+ "special://profile/addon_data/script.tvtunes/").decode('utf-8')
+ if xbmcvfs.exists(tvtunes_path):
+ tvtunes = xbmcaddon.Addon(id="script.tvtunes")
+ tvtunes.setSetting('custom_path_enable', "true")
+ tvtunes.setSetting('custom_path', library)
+ utils.logMsg("EMBY", "TV Tunes custom path is enabled and set.", 1)
+ else:
+ # if it does not exist this will not work so warn user
+ # often they need to edit the settings first for it to be created.
+ dialog.ok(
+ heading="Warning",
+ line1=(
+ "The settings file does not exist in tvtunes. ",
+ "Go to the tvtunes addon and change a setting, then come back and re-run."))
+ xbmc.executebuiltin('Addon.OpenSettings(script.tvtunes)')
+ return
+
# Get every user view Id
- userViews = []
- url = "{server}/mediabrowser/Users/{UserId}/Items?format=json"
- result = doUtils.downloadUrl(url)
-
- for view in result[u'Items']:
- userviewId = view[u'Id']
- userViews.append(userviewId)
-
+ embyconn = utils.kodiSQL('emby')
+ embycursor = embyconn.cursor()
+ emby_db = embydb.Embydb_Functions(embycursor)
+ viewids = emby_db.getViews()
+ embycursor.close()
# Get Ids with Theme Videos
itemIds = {}
- for view in userViews:
- url = "{server}/mediabrowser/Users/{UserId}/Items?HasThemeVideo=True&ParentId=%s&format=json" % view
+ for view in viewids:
+ url = "{server}/emby/Users/{UserId}/Items?HasThemeVideo=True&ParentId=%s&format=json" % view
result = doUtils.downloadUrl(url)
- if result[u'TotalRecordCount'] != 0:
- for item in result[u'Items']:
- itemId = item[u'Id']
- folderName = item[u'Name']
+ if result['TotalRecordCount'] != 0:
+ for item in result['Items']:
+ itemId = item['Id']
+ folderName = item['Name']
folderName = utils.normalize_string(folderName.encode('utf-8'))
itemIds[itemId] = folderName
# Get paths for theme videos
for itemId in itemIds:
- nfo_path = xbmc.translatePath("special://profile/addon_data/plugin.video.emby/library/%s/" % itemIds[itemId])
+ nfo_path = xbmc.translatePath(
+ "special://profile/addon_data/plugin.video.emby/library/%s/" % itemIds[itemId])
# Create folders for each content
if not xbmcvfs.exists(nfo_path):
xbmcvfs.mkdir(nfo_path)
# Where to put the nfos
nfo_path = "%s%s" % (nfo_path, "tvtunes.nfo")
- url = "{server}/mediabrowser/Items/%s/ThemeVideos?format=json" % itemId
+ url = "{server}/emby/Items/%s/ThemeVideos?format=json" % itemId
result = doUtils.downloadUrl(url)
# Create nfo and write themes to it
nfo_file = open(nfo_path, 'w')
pathstowrite = ""
# May be more than one theme
- for theme in result[u'Items']:
+ for theme in result['Items']:
+ putils = playutils.PlayUtils(theme)
if playback == "DirectPlay":
- playurl = playUtils.directPlay(theme)
+ playurl = putils.directPlay()
else:
- playurl = playUtils.directStream(result, server, theme[u'Id'], "ThemeVideo")
+ playurl = putils.directStream()
pathstowrite += ('%s' % playurl.encode('utf-8'))
# Check if the item has theme songs and add them
- url = "{server}/mediabrowser/Items/%s/ThemeSongs?format=json" % itemId
+ url = "{server}/emby/Items/%s/ThemeSongs?format=json" % itemId
result = doUtils.downloadUrl(url)
# May be more than one theme
- for theme in result[u'Items']:
+ for theme in result['Items']:
+ putils = playutils.PlayUtils(theme)
if playback == "DirectPlay":
- playurl = playUtils.directPlay(theme)
+ playurl = putils.directPlay()
else:
- playurl = playUtils.directStream(result, server, theme[u'Id'], "Audio")
+ playurl = putils.directStream()
pathstowrite += ('%s' % playurl.encode('utf-8'))
nfo_file.write(
@@ -253,13 +315,13 @@ def getThemeMedia():
# Get Ids with Theme songs
musicitemIds = {}
- for view in userViews:
- url = "{server}/mediabrowser/Users/{UserId}/Items?HasThemeSong=True&ParentId=%s&format=json" % view
+ for view in viewids:
+ url = "{server}/emby/Users/{UserId}/Items?HasThemeSong=True&ParentId=%s&format=json" % view
result = doUtils.downloadUrl(url)
- if result[u'TotalRecordCount'] != 0:
- for item in result[u'Items']:
- itemId = item[u'Id']
- folderName = item[u'Name']
+ if result['TotalRecordCount'] != 0:
+ for item in result['Items']:
+ itemId = item['Id']
+ folderName = item['Name']
folderName = utils.normalize_string(folderName.encode('utf-8'))
musicitemIds[itemId] = folderName
@@ -270,25 +332,27 @@ def getThemeMedia():
if itemId in itemIds:
continue
- nfo_path = xbmc.translatePath("special://profile/addon_data/plugin.video.emby/library/%s/" % musicitemIds[itemId])
+ nfo_path = xbmc.translatePath(
+ "special://profile/addon_data/plugin.video.emby/library/%s/" % musicitemIds[itemId])
# Create folders for each content
if not xbmcvfs.exists(nfo_path):
xbmcvfs.mkdir(nfo_path)
# Where to put the nfos
nfo_path = "%s%s" % (nfo_path, "tvtunes.nfo")
- url = "{server}/mediabrowser/Items/%s/ThemeSongs?format=json" % itemId
+ url = "{server}/emby/Items/%s/ThemeSongs?format=json" % itemId
result = doUtils.downloadUrl(url)
# Create nfo and write themes to it
nfo_file = open(nfo_path, 'w')
pathstowrite = ""
# May be more than one theme
- for theme in result[u'Items']:
+ for theme in result['Items']:
+ putils = playutils.PlayUtils(theme)
if playback == "DirectPlay":
- playurl = playUtils.directPlay(theme)
+ playurl = putils.directPlay()
else:
- playurl = playUtils.directStream(result, server, theme[u'Id'], "Audio")
+ playurl = putils.directStream()
pathstowrite += ('%s' % playurl.encode('utf-8'))
nfo_file.write(
@@ -297,398 +361,482 @@ def getThemeMedia():
# Close nfo file
nfo_file.close()
-def userPreferences():
- doUtils = DownloadUtils()
- addonSettings = xbmcaddon.Addon(id='plugin.video.emby')
- userPreferencesPage = UserPreferences("script-emby-kodi-UserPreferences.xml", addonSettings.getAddonInfo('path'), "default", "1080i")
- url = "{server}/mediabrowser/Users/{UserId}"
- result = doUtils.downloadUrl(url)
- configuration = result[u'Configuration']
- userPreferencesPage.setConfiguration(configuration)
- userPreferencesPage.setName(result[u'Name'])
- userPreferencesPage.setImage(API().getUserArtwork(result,"Primary"))
-
- userPreferencesPage.doModal()
- if userPreferencesPage.isSave():
- url = "{server}/mediabrowser/Users/{UserId}/Configuration"
- postdata = userPreferencesPage.getConfiguration()
- doUtils.downloadUrl(url, postBody=postdata, type="POST")
+ dialog.notification(
+ heading="Emby for Kodi",
+ message="Themes added!",
+ icon="special://home/addons/plugin.video.emby/icon.png",
+ time=1000,
+ sound=False)
##### BROWSE EMBY CHANNELS #####
-def BrowseChannels(id, folderid=None):
+def BrowseChannels(itemid, folderid=None):
_addon_id = int(sys.argv[1])
_addon_url = sys.argv[0]
-
+ doUtils = downloadutils.DownloadUtils()
+ art = artwork.Artwork()
+
xbmcplugin.setContent(int(sys.argv[1]), 'files')
if folderid:
- url = "{server}/mediabrowser/Channels/" + id + "/Items?userid={UserId}&folderid=" + folderid + "&format=json"
+ url = (
+ "{server}/emby/Channels/%s/Items?userid={UserId}&folderid=%s&format=json"
+ % (itemid, folderid))
+ elif itemid == "0":
+ # id 0 is the root channels folder
+ url = "{server}/emby/Channels?{UserId}&format=json"
else:
- if id == "0": # id 0 is the root channels folder
- url = "{server}/mediabrowser/Channels?{UserId}&format=json"
- else:
- url = "{server}/mediabrowser/Channels/" + id + "/Items?userid={UserId}&format=json"
+ url = "{server}/emby/Channels/%s/Items?UserId={UserId}&format=json" % itemid
- results = DownloadUtils().downloadUrl(url)
- if results:
- result = results.get("Items")
- if(result == None):
- result = []
+ result = doUtils.downloadUrl(url)
+ try:
+ channels = result['Items']
+ except TypeError:
+ pass
+ else:
+ for item in channels:
- item_count = len(result)
- current_item = 1;
-
- for item in result:
- id=str(item.get("Id")).encode('utf-8')
- type=item.get("Type").encode('utf-8')
-
-
- if(item.get("Name") != None):
- tempTitle = item.get("Name")
- tempTitle=tempTitle.encode('utf-8')
- else:
- tempTitle = "Missing Title"
-
- if type=="ChannelFolderItem":
+ API = api.API(item)
+ itemid = item['Id']
+ itemtype = item['Type']
+ title = item.get('Name', "Missing Title")
+ li = xbmcgui.ListItem(title)
+
+ if itemtype == "ChannelFolderItem":
isFolder = True
else:
isFolder = False
- item_type = str(type).encode('utf-8')
-
- if(item.get("ChannelId") != None):
- channelId = str(item.get("ChannelId")).encode('utf-8')
-
- channelName = ''
- if(item.get("ChannelName") != None):
- channelName = item.get("ChannelName").encode('utf-8')
-
- if(item.get("PremiereDate") != None):
- premieredatelist = (item.get("PremiereDate")).split("T")
- premieredate = premieredatelist[0]
- else:
- premieredate = ""
-
- #mediaStreams=API().getMediaStreams(item, True)
-
- #people = API().getPeople(item)
-
+
+ channelId = item.get('ChannelId', "")
+ channelName = item.get('ChannelName', "")
+
+ premieredate = API.getPremiereDate()
# Process Genres
- genre = API().getGenre(item)
-
+ genre = API.getGenres()
# Process UserData
- userData = item.get("UserData")
- PlaybackPositionTicks = '100'
- overlay = "0"
- favorite = "False"
- seekTime = 0
- if(userData != None):
- if userData.get("Played") != True:
- overlay = "7"
- watched = "true"
- else:
- overlay = "6"
- watched = "false"
- if userData.get("IsFavorite") == True:
- overlay = "5"
- favorite = "True"
- else:
- favorite = "False"
- if userData.get("PlaybackPositionTicks") != None:
- PlaybackPositionTicks = str(userData.get("PlaybackPositionTicks"))
- reasonableTicks = int(userData.get("PlaybackPositionTicks")) / 1000
- seekTime = reasonableTicks / 10000
+ overlay = 0
+
+ userdata = API.getUserData()
+ seektime = userdata['Resume']
+ played = userdata['Played']
+ if played:
+ overlay = 7
+ else:
+ overlay = 6
+
+ favorite = userdata['Favorite']
+ if favorite:
+ overlay = 5
- playCount = 0
- if(userData != None and userData.get("Played") == True):
- playCount = 1
+ playcount = userdata['PlayCount']
+ if playcount is None:
+ playcount = 0
+
# Populate the details list
- details={'title' : tempTitle,
- 'channelname' : channelName,
- 'plot' : item.get("Overview"),
- 'Overlay' : overlay,
- 'playcount' : str(playCount)}
-
- if item.get("Type") == "ChannelVideoItem":
+ details = {
+
+ 'title': title,
+ 'channelname': channelName,
+ 'plot': API.getOverview(),
+ 'Overlay': str(overlay),
+ 'playcount': str(playcount)
+ }
+
+ if itemtype == "ChannelVideoItem":
xbmcplugin.setContent(_addon_id, 'movies')
- elif item.get("Type") == "ChannelAudioItem":
+ elif itemtype == "ChannelAudioItem":
xbmcplugin.setContent(_addon_id, 'songs')
- # Populate the extraData list
- extraData={'thumb' : API().getArtwork(item, "Primary") ,
- 'fanart_image' : API().getArtwork(item, "Backdrop") ,
- 'poster' : API().getArtwork(item, "poster") ,
- 'tvshow.poster': API().getArtwork(item, "tvshow.poster") ,
- 'banner' : API().getArtwork(item, "Banner") ,
- 'clearlogo' : API().getArtwork(item, "Logo") ,
- 'discart' : API().getArtwork(item, "Disc") ,
- 'clearart' : API().getArtwork(item, "Art") ,
- 'landscape' : API().getArtwork(item, "Thumb") ,
- 'id' : id ,
- 'rating' : item.get("CommunityRating"),
- 'year' : item.get("ProductionYear"),
- 'premieredate' : premieredate,
- 'genre' : genre,
- 'playcount' : str(playCount),
- 'itemtype' : item_type}
-
- if extraData['thumb'] == '':
- extraData['thumb'] = extraData['fanart_image']
-
- liz = xbmcgui.ListItem(tempTitle)
+ # Populate the extradata list and artwork
+ pbutils.PlaybackUtils(item).setArtwork(li)
+ extradata = {
- artTypes=['poster', 'tvshow.poster', 'fanart_image', 'clearlogo', 'discart', 'banner', 'clearart', 'landscape', 'small_poster', 'tiny_poster', 'medium_poster','small_fanartimage', 'medium_fanartimage', 'medium_landscape', 'fanart_noindicators']
+ 'id': itemid,
+ 'rating': item.get('CommunityRating'),
+ 'year': item.get('ProductionYear'),
+ 'premieredate': premieredate,
+ 'genre': genre,
+ 'playcount': str(playcount),
+ 'itemtype': itemtype
+ }
+ li.setInfo('video', infoLabels=extradata)
+ li.setThumbnailImage(art.getAllArtwork(item)['Primary'])
+ li.setIconImage('DefaultTVShows.png')
+
+ if itemtype == "Channel":
+ path = "%s?id=%s&mode=channels" % (_addon_url, itemid)
+ xbmcplugin.addDirectoryItem(handle=_addon_id, url=path, listitem=li, isFolder=True)
- for artType in artTypes:
- imagePath=str(extraData.get(artType,''))
- liz=PlaybackUtils().setArt(liz,artType, imagePath)
-
- liz.setThumbnailImage(API().getArtwork(item, "Primary"))
- liz.setIconImage('DefaultTVShows.png')
- #liz.setInfo( type="Video", infoLabels={ "Rating": item.get("CommunityRating") })
- #liz.setInfo( type="Video", infoLabels={ "Plot": item.get("Overview") })
-
- if type=="Channel":
- file = _addon_url + "?id=%s&mode=channels"%id
- xbmcplugin.addDirectoryItem(handle=_addon_id, url=file, listitem=liz, isFolder=True)
-
- elif isFolder == True:
- file = _addon_url + "?id=%s&mode=channelsfolder&folderid=%s" %(channelId, id)
- xbmcplugin.addDirectoryItem(handle=_addon_id, url=file, listitem=liz, isFolder=True)
+ elif isFolder:
+ path = "%s?id=%s&mode=channelsfolder&folderid=%s" % (_addon_url, channelId, itemid)
+ xbmcplugin.addDirectoryItem(handle=_addon_id, url=path, listitem=li, isFolder=True)
else:
- file = _addon_url + "?id=%s&mode=play"%id
- liz.setProperty('IsPlayable', 'true')
- xbmcplugin.addDirectoryItem(handle=_addon_id, url=file, listitem=liz)
+ path = "%s?id=%s&mode=play" % (_addon_url, itemid)
+ li.setProperty('IsPlayable', 'true')
+ xbmcplugin.addDirectoryItem(handle=_addon_id, url=path, listitem=li)
xbmcplugin.endOfDirectory(handle=int(sys.argv[1]))
-##### GET NEXTUP EPISODES FOR TAGNAME #####
-def getNextUpEpisodes(tagname,limit):
- count=0
-
- #if the addon is called with nextup parameter, we return the nextepisodes list of the given tagname
- xbmcplugin.setContent(int(sys.argv[1]), 'episodes')
- # First we get a list of all the in-progress TV shows - filtered by tag
- json_query_string = xbmc.executeJSONRPC('{"jsonrpc": "2.0", "method": "VideoLibrary.GetTVShows", "params": { "sort": { "order": "descending", "method": "lastplayed" }, "filter": {"and": [{"operator":"true", "field":"inprogress", "value":""}, {"operator": "is", "field": "tag", "value": "%s"}]}, "properties": [ "title", "studio", "mpaa", "file", "art" ] }, "id": "libTvShows"}' %tagname)
-
- json_result = json.loads(json_query_string)
- # If we found any, find the oldest unwatched show for each one.
- if json_result.has_key('result') and json_result['result'].has_key('tvshows'):
- for item in json_result['result']['tvshows']:
-
- # If Ignore Specials is true only choose episodes from seasons greater than 0.
- if utils.settings("ignoreSpecialsNextEpisodes")=="true":
- json_query2 = xbmc.executeJSONRPC('{"jsonrpc": "2.0", "method": "VideoLibrary.GetEpisodes", "params": { "tvshowid": %d, "sort": {"method":"episode"}, "filter": {"and": [ {"field": "playcount", "operator": "lessthan", "value":"1"}, {"field": "season", "operator": "greaterthan", "value": "0"} ]}, "properties": [ "title", "playcount", "season", "episode", "showtitle", "plot", "file", "rating", "resume", "tvshowid", "art", "streamdetails", "firstaired", "runtime", "writer", "dateadded", "lastplayed" ], "limits":{"end":1}}, "id": "1"}' %item['tvshowid'])
- else:
- json_query2 = xbmc.executeJSONRPC('{"jsonrpc": "2.0", "method": "VideoLibrary.GetEpisodes", "params": { "tvshowid": %d, "sort": {"method":"episode"}, "filter": {"field": "playcount", "operator": "lessthan", "value":"1"}, "properties": [ "title", "playcount", "season", "episode", "showtitle", "plot", "file", "rating", "resume", "tvshowid", "art", "streamdetails", "firstaired", "runtime", "writer", "dateadded", "lastplayed" ], "limits":{"end":1}}, "id": "1"}' %item['tvshowid'])
-
- if json_query2:
- json_query2 = json.loads(json_query2)
- if json_query2.has_key('result') and json_query2['result'].has_key('episodes'):
- for item in json_query2['result']['episodes']:
- liz = createListItem(item)
- xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=item['file'], listitem=liz)
- count +=1
- if count == limit:
- break
- xbmcplugin.endOfDirectory(handle=int(sys.argv[1]))
-
-def getInProgressEpisodes(tagname,limit):
- count = 0
- #if the addon is called with inprogressepisodes parameter, we return the inprogressepisodes list of the given tagname
- xbmcplugin.setContent(int(sys.argv[1]), 'episodes')
- # First we get a list of all the in-progress TV shows - filtered by tag
- json_query_string = xbmc.executeJSONRPC('{"jsonrpc": "2.0", "method": "VideoLibrary.GetTVShows", "params": { "sort": { "order": "descending", "method": "lastplayed" }, "filter": {"and": [{"operator":"true", "field":"inprogress", "value":""}, {"operator": "contains", "field": "tag", "value": "%s"}]}, "properties": [ "title", "studio", "mpaa", "file", "art" ] }, "id": "libTvShows"}' %tagname)
- json_result = json.loads(json_query_string)
- # If we found any, find all in progress episodes for each one.
- if json_result.has_key('result') and json_result['result'].has_key('tvshows'):
- for item in json_result['result']['tvshows']:
- json_query2 = xbmc.executeJSONRPC('{"jsonrpc": "2.0", "method": "VideoLibrary.GetEpisodes", "params": { "tvshowid": %d, "sort": {"method":"episode"}, "filter": {"field": "inprogress", "operator": "true", "value":""}, "properties": [ "title", "playcount", "season", "episode", "showtitle", "plot", "file", "rating", "resume", "tvshowid", "art", "cast", "streamdetails", "firstaired", "runtime", "writer", "dateadded", "lastplayed" ]}, "id": "1"}' %item['tvshowid'])
-
- if json_query2:
- json_query2 = json.loads(json_query2)
- if json_query2.has_key('result') and json_query2['result'].has_key('episodes'):
- for item in json_query2['result']['episodes']:
- liz = createListItem(item)
- xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=item['file'], listitem=liz)
- count +=1
- if count == limit:
- break
- xbmcplugin.endOfDirectory(handle=int(sys.argv[1]))
-
-def getRecentEpisodes(tagname,limit):
- #if the addon is called with recentepisodes parameter, we return the recentepisodes list of the given tagname
- xbmcplugin.setContent(int(sys.argv[1]), 'episodes')
- # First we get a list of all the TV shows - filtered by tag
- json_query_string = xbmc.executeJSONRPC('{"jsonrpc": "2.0", "method": "VideoLibrary.GetTVShows", "params": { "sort": { "order": "descending", "method": "dateadded" }, "properties": [ "title","sorttitle" ], "filter": {"operator": "contains", "field": "tag", "value": "%s"} }, "id": "libTvShows"}' %tagname)
- json_result = json.loads(json_query_string)
-
- # If we found any, put all tv show id's in a list
- if json_result.has_key('result') and json_result['result'].has_key('tvshows'):
- alltvshowIds = list()
- for tvshow in json_result['result']['tvshows']:
- alltvshowIds.append(tvshow["tvshowid"])
- alltvshowIds = set(alltvshowIds)
-
- #get all recently added episodes
- json_query2 = xbmc.executeJSONRPC('{"jsonrpc": "2.0", "method": "VideoLibrary.GetEpisodes", "params": { "sort": {"order": "descending", "method": "dateadded"}, "filter": {"field": "playcount", "operator": "lessthan", "value":"1"}, "properties": [ "title", "playcount", "season", "episode", "showtitle", "plot", "file", "rating", "resume", "tvshowid", "art", "streamdetails", "firstaired", "runtime", "cast", "writer", "dateadded", "lastplayed" ]}, "limits":{"end":%d}, "id": "1"}' %limit)
- count = 0
- if json_query2:
- json_query2 = json.loads(json_query2)
- if json_query2.has_key('result') and json_query2['result'].has_key('episodes'):
- for item in json_query2['result']['episodes']:
- if item["tvshowid"] in alltvshowIds:
- liz = createListItem(item)
- xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=item['file'], listitem=liz)
- count += 1
- if count == limit:
- break
- xbmcplugin.endOfDirectory(handle=int(sys.argv[1]))
-
+##### LISTITEM SETUP FOR VIDEONODES #####
def createListItem(item):
-
- liz = xbmcgui.ListItem(item['title'])
- liz.setInfo( type="Video", infoLabels={ "Title": item['title'] })
- liz.setProperty('IsPlayable', 'true')
- liz.setInfo( type="Video", infoLabels={ "duration": str(item['runtime']/60) })
+
+ title = item['title']
+ li = xbmcgui.ListItem(title)
+ li.setProperty('IsPlayable', "true")
+ metadata = {
+
+ 'Title': title,
+ 'duration': str(item['runtime']/60),
+ 'Plot': item['plot'],
+ 'Playcount': item['playcount']
+ }
+
if "episode" in item:
- episode = "%.2d" % float(item['episode'])
- liz.setInfo( type="Video", infoLabels={ "Episode": item['episode'] })
-
+ episode = item['episode']
+ metadata['Episode'] = episode
+
if "season" in item:
- season = "%.2d" % float(item['season'])
- liz.setInfo( type="Video", infoLabels={ "Season": item['season'] })
-
+ season = item['season']
+ metadata['Season'] = season
+
if season and episode:
- episodeno = "s%se%s" %(season,episode)
- liz.setProperty("episodeno", episodeno)
-
+ li.setProperty('episodeno', "s%.2de%.2d" % (season, episode))
+
if "firstaired" in item:
- liz.setInfo( type="Video", infoLabels={ "Premiered": item['firstaired'] })
-
- plot = item['plot']
- liz.setInfo( type="Video", infoLabels={ "Plot": plot })
-
+ metadata['Premiered'] = item['firstaired']
+
if "showtitle" in item:
- liz.setInfo( type="Video", infoLabels={ "TVshowTitle": item['showtitle'] })
-
+ metadata['TVshowTitle'] = item['showtitle']
+
if "rating" in item:
- liz.setInfo( type="Video", infoLabels={ "Rating": str(round(float(item['rating']),1)) })
- liz.setInfo( type="Video", infoLabels={ "Playcount": item['playcount'] })
+ metadata['Rating'] = str(round(float(item['rating']),1))
+
if "director" in item:
- liz.setInfo( type="Video", infoLabels={ "Director": " / ".join(item['director']) })
+ metadata['Director'] = " / ".join(item['director'])
+
if "writer" in item:
- liz.setInfo( type="Video", infoLabels={ "Writer": " / ".join(item['writer']) })
-
+ metadata['Writer'] = " / ".join(item['writer'])
+
if "cast" in item:
- listCast = []
- listCastAndRole = []
- for castmember in item["cast"]:
- listCast.append( castmember["name"] )
- listCastAndRole.append( (castmember["name"], castmember["role"]) )
- cast = [listCast, listCastAndRole]
- liz.setInfo( type="Video", infoLabels={ "Cast": cast[0] })
- liz.setInfo( type="Video", infoLabels={ "CastAndRole": cast[1] })
-
- liz.setProperty("resumetime", str(item['resume']['position']))
- liz.setProperty("totaltime", str(item['resume']['total']))
- liz.setArt(item['art'])
- liz.setThumbnailImage(item['art'].get('thumb',''))
- liz.setIconImage('DefaultTVShows.png')
- liz.setProperty("dbid", str(item['episodeid']))
- liz.setProperty("fanart_image", item['art'].get('tvshow.fanart',''))
+ cast = []
+ castandrole = []
+ for person in item['cast']:
+ name = person['name']
+ cast.append(name)
+ castandrole.append((name, person['role']))
+ metadata['Cast'] = cast
+ metadata['CastAndRole'] = castandrole
+
+ li.setInfo(type="Video", infoLabels=metadata)
+ li.setProperty('resumetime', str(item['resume']['position']))
+ li.setProperty('totaltime', str(item['resume']['total']))
+ li.setArt(item['art'])
+ li.setThumbnailImage(item['art'].get('thumb',''))
+ li.setIconImage('DefaultTVShows.png')
+ li.setProperty('dbid', str(item['episodeid']))
+ li.setProperty('fanart_image', item['art'].get('tvshow.fanart',''))
for key, value in item['streamdetails'].iteritems():
for stream in value:
- liz.addStreamInfo( key, stream )
+ li.addStreamInfo(key, stream)
- return liz
+ return li
+
+##### GET NEXTUP EPISODES FOR TAGNAME #####
+def getNextUpEpisodes(tagname, limit):
+ count = 0
+ # if the addon is called with nextup parameter,
+ # we return the nextepisodes list of the given tagname
+ xbmcplugin.setContent(int(sys.argv[1]), 'episodes')
+ # First we get a list of all the TV shows - filtered by tag
+ query = {
+
+ 'jsonrpc': "2.0",
+ 'id': "libTvShows",
+ 'method': "VideoLibrary.GetTVShows",
+ 'params': {
+
+ 'sort': {'order': "descending", 'method': "lastplayed"},
+ 'filter': {
+ 'and': [
+ {'operator': "true", 'field': "inprogress", 'value': ""},
+ {'operator': "contains", 'field': "tag", 'value': "%s" % tagname}
+ ]},
+ 'properties': ['title', 'studio', 'mpaa', 'file', 'art']
+ }
+ }
+ result = xbmc.executeJSONRPC(json.dumps(query))
+ result = json.loads(result)
+ # If we found any, find the oldest unwatched show for each one.
+ try:
+ items = result['result']['tvshows']
+ except (KeyError, TypeError):
+ pass
+ else:
+ for item in items:
+ if utils.settings('ignoreSpecialsNextEpisodes') == "true":
+ query = {
+
+ 'jsonrpc': "2.0",
+ 'id': 1,
+ 'method': "VideoLibrary.GetEpisodes",
+ 'params': {
+
+ 'tvshowid': item['tvshowid'],
+ 'sort': {'method': "episode"},
+ 'filter': {
+ 'and': [
+ {'operator': "lessthan", 'field': "playcount", 'value': "1"},
+ {'operator': "greaterthan", 'field': "season", 'value': "0"}
+ ]},
+ 'properties': [
+ "title", "playcount", "season", "episode", "showtitle",
+ "plot", "file", "rating", "resume", "tvshowid", "art",
+ "streamdetails", "firstaired", "runtime", "writer",
+ "dateadded", "lastplayed"
+ ],
+ 'limits': {"end": 1}
+ }
+ }
+ else:
+ query = {
+
+ 'jsonrpc': "2.0",
+ 'id': 1,
+ 'method': "VideoLibrary.GetEpisodes",
+ 'params': {
+
+ 'tvshowid': item['tvshowid'],
+ 'sort': {'method': "episode"},
+ 'filter': {'operator': "lessthan", 'field': "playcount", 'value': "1"},
+ 'properties': [
+ "title", "playcount", "season", "episode", "showtitle",
+ "plot", "file", "rating", "resume", "tvshowid", "art",
+ "streamdetails", "firstaired", "runtime", "writer",
+ "dateadded", "lastplayed"
+ ],
+ 'limits': {"end": 1}
+ }
+ }
+
+ result = xbmc.executeJSONRPC(json.dumps(query))
+ result = json.loads(result)
+ try:
+ episodes = result['result']['episodes']
+ except (KeyError, TypeError):
+ pass
+ else:
+ for episode in episodes:
+ li = createListItem(episode)
+ xbmcplugin.addDirectoryItem(
+ handle=int(sys.argv[1]),
+ url=item['file'],
+ listitem=li)
+ count += 1
+
+ if count == limit:
+ break
+
+ xbmcplugin.endOfDirectory(handle=int(sys.argv[1]))
+
+##### GET INPROGRESS EPISODES FOR TAGNAME #####
+def getInProgressEpisodes(tagname, limit):
+
+ count = 0
+ # if the addon is called with inprogressepisodes parameter,
+ # we return the inprogressepisodes list of the given tagname
+ xbmcplugin.setContent(int(sys.argv[1]), 'episodes')
+ # First we get a list of all the in-progress TV shows - filtered by tag
+ query = {
+
+ 'jsonrpc': "2.0",
+ 'id': "libTvShows",
+ 'method': "VideoLibrary.GetTVShows",
+ 'params': {
+
+ 'sort': {'order': "descending", 'method': "lastplayed"},
+ 'filter': {
+ 'and': [
+ {'operator': "true", 'field': "inprogress", 'value': ""},
+ {'operator': "contains", 'field': "tag", 'value': "%s" % tagname}
+ ]},
+ 'properties': ['title', 'studio', 'mpaa', 'file', 'art']
+ }
+ }
+ result = xbmc.executeJSONRPC(json.dumps(query))
+ result = json.loads(result)
+ # If we found any, find the oldest unwatched show for each one.
+ try:
+ items = result['result']['tvshows']
+ except (KeyError, TypeError):
+ pass
+ else:
+ for item in items:
+ query = {
+
+ 'jsonrpc': "2.0",
+ 'id': 1,
+ 'method': "VideoLibrary.GetEpisodes",
+ 'params': {
+
+ 'tvshowid': item['tvshowid'],
+ 'sort': {'method': "episode"},
+ 'filter': {'operator': "true", 'field': "inprogress", 'value': ""},
+ 'properties': [
+ "title", "playcount", "season", "episode", "showtitle", "plot",
+ "file", "rating", "resume", "tvshowid", "art", "cast",
+ "streamdetails", "firstaired", "runtime", "writer",
+ "dateadded", "lastplayed"
+ ]
+ }
+ }
+ result = xbmc.executeJSONRPC(json.dumps(query))
+ result = json.loads(result)
+ try:
+ episodes = result['result']['episodes']
+ except (KeyError, TypeError):
+ pass
+ else:
+ for episode in episodes:
+ li = createListItem(episode)
+ xbmcplugin.addDirectoryItem(
+ handle=int(sys.argv[1]),
+ url=item['file'],
+ listitem=li)
+ count += 1
+
+ if count == limit:
+ break
+
+ xbmcplugin.endOfDirectory(handle=int(sys.argv[1]))
+
+##### GET RECENT EPISODES FOR TAGNAME #####
+def getRecentEpisodes(tagname, limit):
+
+ count = 0
+ # if the addon is called with recentepisodes parameter,
+ # we return the recentepisodes list of the given tagname
+ xbmcplugin.setContent(int(sys.argv[1]), 'episodes')
+ # First we get a list of all the TV shows - filtered by tag
+ query = {
+
+ 'jsonrpc': "2.0",
+ 'id': "libTvShows",
+ 'method': "VideoLibrary.GetTVShows",
+ 'params': {
+
+ 'sort': {'order': "descending", 'method': "dateadded"},
+ 'filter': {'operator': "contains", 'field': "tag", 'value': "%s" % tagname},
+ 'properties': ["title","sorttitle"]
+ }
+ }
+ result = xbmc.executeJSONRPC(json.dumps(query))
+ result = json.loads(result)
+ # If we found any, find the oldest unwatched show for each one.
+ try:
+ items = result['result']['tvshows']
+ except (KeyError, TypeError):
+ pass
+ else:
+ allshowsIds = set()
+ for item in items:
+ allshowsIds.add(item['tvshowid'])
+
+ query = {
+
+ 'jsonrpc': "2.0",
+ 'id': 1,
+ 'method': "VideoLibrary.GetEpisodes",
+ 'params': {
+
+ 'sort': {'order': "descending", 'method': "dateadded"},
+ 'filter': {'operator': "lessthan", 'field': "playcount", 'value': "1"},
+ 'properties': [
+ "title", "playcount", "season", "episode", "showtitle", "plot",
+ "file", "rating", "resume", "tvshowid", "art", "streamdetails",
+ "firstaired", "runtime", "cast", "writer", "dateadded", "lastplayed"
+ ],
+ "limits": {"end": limit}
+ }
+ }
+ result = xbmc.executeJSONRPC(json.dumps(query))
+ result = json.loads(result)
+ try:
+ episodes = result['result']['episodes']
+ except (KeyError, TypeError):
+ pass
+ else:
+ for episode in episodes:
+ if episode['tvshowid'] in allshowsIds:
+ li = createListItem(episode)
+ xbmcplugin.addDirectoryItem(
+ handle=int(sys.argv[1]),
+ url=item['file'],
+ listitem=li)
+ count += 1
+
+ if count == limit:
+ break
+
+ xbmcplugin.endOfDirectory(handle=int(sys.argv[1]))
+
##### GET EXTRAFANART FOR LISTITEM #####
def getExtraFanArt():
- itemPath = ""
- embyId = ""
- #get extrafanart for listitem - this will only be used for skins that actually call the listitem's path + fanart dir...
+ emby = embyserver.Read_EmbyServer()
+ art = artwork.Artwork()
+
+ # Get extrafanart for listitem
+ # this will only be used for skins that actually call the listitem's path + fanart dir...
try:
- #only do this if the listitem has actually changed
+ # Only do this if the listitem has actually changed
itemPath = xbmc.getInfoLabel("ListItem.FileNameAndPath")
if not itemPath:
itemPath = xbmc.getInfoLabel("ListItem.Path")
- if ("/tvshows/" in itemPath or "/musicvideos/" in itemPath or "/movies/" in itemPath):
- embyId = itemPath.split("/")[-2]
+ if any([x in itemPath for x in ['tvshows', 'musicvideos', 'movies']]):
+ params = urlparse.parse_qs(itemPath)
+ embyId = params['id'][0]
- utils.logMsg("%s %s" % ("Emby addon", "getExtraFanArt"), "requesting extraFanArt for Id: " + embyId, 1)
+ utils.logMsg("EMBY", "Requesting extrafanart for Id: %s" % embyId, 1)
- #we need to store the images locally for this to work because of the caching system in xbmc
- fanartDir = xbmc.translatePath("special://thumbnails/emby/" + embyId + "/")
+ # We need to store the images locally for this to work
+ # because of the caching system in xbmc
+ fanartDir = xbmc.translatePath("special://thumbnails/emby/%s/" % embyId).decode('utf-8')
if not xbmcvfs.exists(fanartDir):
- #download the images to the cache directory
- xbmcvfs.mkdir(fanartDir)
- item = ReadEmbyDB().getFullItem(embyId)
- if item != None:
- if item.has_key("BackdropImageTags"):
- if(len(item["BackdropImageTags"]) > 0):
- WINDOW = xbmcgui.Window(10000)
- username = WINDOW.getProperty('currUser')
- server = WINDOW.getProperty('server%s' % username)
- totalbackdrops = len(item["BackdropImageTags"])
- count = 0
- for backdrop in item["BackdropImageTags"]:
- backgroundUrl = "%s/mediabrowser/Items/%s/Images/Backdrop/%s/?MaxWidth=10000&MaxHeight=10000&Format=original&Tag=%s&EnableImageEnhancers=false" % (server, embyId, str(count), backdrop)
- count += 1
- fanartFile = os.path.join(fanartDir,"fanart" + backdrop + ".jpg")
- li = xbmcgui.ListItem(backdrop, path=fanartFile)
- xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=fanartFile, listitem=li)
- xbmcvfs.copy(backgroundUrl,fanartFile)
-
+ # Download the images to the cache directory
+ xbmcvfs.mkdirs(fanartDir)
+ item = emby.getItem(embyId)
+ if item:
+ backdrops = art.getAllArtwork(item)['Backdrop']
+ tags = item['BackdropImageTags']
+ count = 0
+ for backdrop in backdrops:
+ # Same ordering as in artwork
+ tag = tags[count]
+ fanartFile = os.path.join(fanartDir, "fanart%s.jpg" % tag)
+ li = xbmcgui.ListItem(tag, path=fanartFile)
+ xbmcplugin.addDirectoryItem(
+ handle=int(sys.argv[1]),
+ url=fanartFile,
+ listitem=li)
+ xbmcvfs.copy(backdrop, fanartFile)
+ count += 1
else:
- #use existing cached images
+ utils.logMsg("EMBY", "Found cached backdrop.", 2)
+ # Use existing cached images
dirs, files = xbmcvfs.listdir(fanartDir)
- count = 1
for file in files:
- count +=1
- li = xbmcgui.ListItem(file, path=os.path.join(fanartDir,file))
- xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=os.path.join(fanartDir,file), listitem=li)
+ fanartFile = os.path.join(fanartDir, file)
+ li = xbmcgui.ListItem(file, path=fanartFile)
+ xbmcplugin.addDirectoryItem(
+ handle=int(sys.argv[1]),
+ url=fanartFile,
+ listitem=li)
except Exception as e:
- utils.logMsg("%s %s" % ("Emby addon", "Error in getExtraFanArt"), str(e), 1)
- pass
-
- #always do endofdirectory to prevent errors in the logs
- xbmcplugin.endOfDirectory(int(sys.argv[1]))
-
-def addDirectoryItem(label, path, folder=True):
- li = xbmcgui.ListItem(label, path=path)
- li.setThumbnailImage("special://home/addons/plugin.video.emby/icon.png")
- li.setArt({"fanart":"special://home/addons/plugin.video.emby/fanart.jpg"})
- li.setArt({"landscape":"special://home/addons/plugin.video.emby/fanart.jpg"})
- xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=path, listitem=li, isFolder=folder)
-
-# if the addon is called without parameters we show the listing...
-def doMainListing():
-
- xbmcplugin.setContent(int(sys.argv[1]), 'files')
- #get emby nodes from the window props
- embyProperty = WINDOW.getProperty("Emby.nodes.total")
- if embyProperty:
- totalNodes = int(embyProperty)
- for i in range(totalNodes):
- path = WINDOW.getProperty("Emby.nodes.%s.index" %str(i))
- if not path:
- path = WINDOW.getProperty("Emby.nodes.%s.content" %str(i))
- label = WINDOW.getProperty("Emby.nodes.%s.title" %str(i))
- if path:
- addDirectoryItem(label, path)
-
- # some extra entries for settings and stuff. TODO --> localize the labels
- addDirectoryItem("Settings", "plugin://plugin.video.emby/?mode=settings")
- addDirectoryItem("Perform manual sync", "plugin://plugin.video.emby/?mode=manualsync")
- addDirectoryItem("Add user to session", "plugin://plugin.video.emby/?mode=adduser")
- addDirectoryItem("Configure user preferences", "plugin://plugin.video.emby/?mode=userprefs")
- addDirectoryItem("Perform local database reset (full resync)", "plugin://plugin.video.emby/?mode=reset")
- addDirectoryItem("Cache all images to Kodi texture cache (advanced)", "plugin://plugin.video.emby/?mode=texturecache")
- addDirectoryItem("Sync Emby Theme Media to Kodi", "plugin://plugin.video.emby/?mode=thememedia")
+ utils.logMsg("EMBY", "Error getting extrafanart: %s" % e, 1)
+ # Always do endofdirectory to prevent errors in the logs
xbmcplugin.endOfDirectory(int(sys.argv[1]))
\ No newline at end of file
diff --git a/resources/lib/KodiMonitor.py b/resources/lib/KodiMonitor.py
index 2d4dc943..1b3f7862 100644
--- a/resources/lib/KodiMonitor.py
+++ b/resources/lib/KodiMonitor.py
@@ -1,150 +1,195 @@
+# -*- coding: utf-8 -*-
+
#################################################################################################
-# Kodi Monitor
-# Watched events that occur in Kodi, like setting media watched
-#################################################################################################
+
+import json
import xbmc
import xbmcgui
-import xbmcaddon
-import json
-import Utils as utils
-from WriteKodiVideoDB import WriteKodiVideoDB
-from ReadKodiDB import ReadKodiDB
-from PlayUtils import PlayUtils
-from DownloadUtils import DownloadUtils
-from PlaybackUtils import PlaybackUtils
+import clientinfo
+import downloadutils
+import embydb_functions as embydb
+import playbackutils as pbutils
+import utils
+
+#################################################################################################
-class Kodi_Monitor( xbmc.Monitor ):
-
- WINDOW = xbmcgui.Window(10000)
+class KodiMonitor(xbmc.Monitor):
- def __init__(self, *args, **kwargs):
- xbmc.Monitor.__init__(self)
- def logMsg(self, msg, lvl = 1):
+ def __init__(self):
+
+ self.clientInfo = clientinfo.ClientInfo()
+ self.addonName = self.clientInfo.getAddonName()
+ self.doUtils = downloadutils.DownloadUtils()
+
+ self.logMsg("Kodi monitor started.", 1)
+
+ def logMsg(self, msg, lvl=1):
+
+ self.className = self.__class__.__name__
+ utils.logMsg("%s %s" % (self.addonName, self.className), msg, lvl)
- className = self.__class__.__name__
- utils.logMsg("%s %s" % ("EMBY", className), msg, int(lvl))
def onScanStarted(self, library):
- utils.window('kodiScan', value="true")
- self.logMsg("Kodi library scan running.", 2)
-
+ self.logMsg("Kodi library scan %s running." % library, 2)
+ if library == "video":
+ utils.window('emby_kodiScan', value="true")
+
def onScanFinished(self, library):
- utils.window('kodiScan', clear=True)
- self.logMsg("Kodi library scan finished.", 2)
-
- #this library monitor is used to detect a watchedstate change by the user through the library
- #as well as detect when a library item has been deleted to pass the delete to the Emby server
- def onNotification (self, sender, method, data):
+ self.logMsg("Kodi library scan %s finished." % library, 2)
+ if library == "video":
+ utils.window('emby_kodiScan', clear=True)
- WINDOW = self.WINDOW
- downloadUtils = DownloadUtils()
- #player started playing an item -
- if ("Playlist.OnAdd" in method or "Player.OnPlay" in method):
+ def onNotification(self, sender, method, data):
- jsondata = json.loads(data)
- if jsondata:
- if jsondata.has_key("item"):
- if jsondata.get("item").has_key("id") and jsondata.get("item").has_key("type"):
- id = jsondata.get("item").get("id")
- type = jsondata.get("item").get("type")
-
- if (utils.settings('useDirectPaths')=='true' and not type == "song") or (type == "song" and utils.settings('enableMusicSync') == "true"):
-
- if type == "song":
- connection = utils.KodiSQL('music')
- cursor = connection.cursor()
- embyid = ReadKodiDB().getEmbyIdByKodiId(id, type, connection, cursor)
- cursor.close()
- else:
- embyid = ReadKodiDB().getEmbyIdByKodiId(id,type)
-
- if embyid:
-
- url = "{server}/mediabrowser/Users/{UserId}/Items/%s?format=json" % embyid
- result = downloadUtils.downloadUrl(url)
- self.logMsg("Result: %s" % result, 2)
-
- playurl = None
- count = 0
- while not playurl and count < 2:
- try:
- playurl = xbmc.Player().getPlayingFile()
- except RuntimeError:
- xbmc.sleep(200)
- else:
- listItem = xbmcgui.ListItem()
- PlaybackUtils().setProperties(playurl, result, listItem)
-
- if type == "song" and utils.settings('directstreammusic') == "true":
- utils.window('%splaymethod' % playurl, value="DirectStream")
- else:
- utils.window('%splaymethod' % playurl, value="DirectPlay")
-
- count += 1
-
- if method == "VideoLibrary.OnUpdate":
- # Triggers 4 times, the following is only for manually marking as watched/unwatched
- jsondata = json.loads(data)
+ doUtils = self.doUtils
+ if method not in ("Playlist.OnAdd"):
+ self.logMsg("Method: %s Data: %s" % (method, data), 1)
+ if data:
+ data = json.loads(data)
+
+
+ if method == "Player.OnPlay":
+ # Set up report progress for emby playback
+ item = data.get('item')
try:
- playcount = jsondata.get('playcount')
- item = jsondata['item']['id']
- type = jsondata['item']['type']
- prop = utils.window('Played%s%s' % (type, item))
- except:
- self.logMsg("Could not process VideoLibrary.OnUpdate data.", 1)
+ kodiid = item['id']
+ type = item['type']
+ except (KeyError, TypeError):
+ self.logMsg("Properties already set for item.", 1)
else:
- self.logMsg("VideoLibrary.OnUpdate: %s" % data, 2)
- if prop != "true":
- # Set property to prevent the multi triggering
- utils.window('Played%s%s' % (type, item), "true")
- WriteKodiVideoDB().updatePlayCountFromKodi(item, type, playcount)
+ if ((utils.settings('useDirectPaths') == "1" and not type == "song") or
+ (type == "song" and utils.settings('disableMusic') == "false")):
+ # Set up properties for player
+ embyconn = utils.kodiSQL('emby')
+ embycursor = embyconn.cursor()
+ emby_db = embydb.Embydb_Functions(embycursor)
+ emby_dbitem = emby_db.getItem_byKodiId(kodiid, type)
+ try:
+ itemid = emby_dbitem[0]
+ except TypeError:
+ self.logMsg("No kodiid returned.", 1)
+ else:
+ url = "{server}/emby/Users/{UserId}/Items/%s?format=json" % itemid
+ result = doUtils.downloadUrl(url)
+ self.logMsg("Item: %s" % result, 2)
- self.clearProperty(type, item)
-
- if method == "System.OnWake":
- xbmc.sleep(10000) #Allow network to wake up
- WINDOW.setProperty("OnWakeSync", "true")
+ playurl = None
+ count = 0
+ while not playurl and count < 2:
+ try:
+ playurl = xbmc.Player().getPlayingFile()
+ except RuntimeError:
+ count += 1
+ xbmc.sleep(200)
+ else:
+ listItem = xbmcgui.ListItem()
+ playback = pbutils.PlaybackUtils(result)
- if method == "VideoLibrary.OnRemove":
- xbmc.log('Intercepted remove from sender: ' + sender + ' method: ' + method + ' data: ' + data)
- jsondata = json.loads(data)
- id = ReadKodiDB().getEmbyIdByKodiId(jsondata.get("id"), jsondata.get("type"))
- if id == None:
- return
- xbmc.log("Deleting Emby ID: " + id + " from database")
- connection = utils.KodiSQL()
- cursor = connection.cursor()
- cursor.execute("DELETE FROM emby WHERE emby_id = ?", (id,))
- connection.commit()
- cursor.close
+ if type == "song" and utils.settings('streamMusic') == "true":
+ utils.window('emby_%s.playmethod' % playurl,
+ value="DirectStream")
+ else:
+ utils.window('emby_%s.playmethod' % playurl,
+ value="DirectPlay")
+ # Set properties for player.py
+ playback.setProperties(playurl, listItem)
+ finally:
+ embycursor.close()
- if jsondata:
- if jsondata.get("type") == "episode" or "movie":
- url='{server}/mediabrowser/Items?Ids=' + id + '&format=json'
- #This is a check to see if the item exists on the server, if it doesn't it may have already been deleted by another client
- result = DownloadUtils().downloadUrl(url)
- item = result.get("Items")[0]
- if data:
- return_value = xbmcgui.Dialog().yesno("Confirm Delete", "Delete file on Emby Server?")
- if return_value:
- url='{server}/mediabrowser/Items/' + id
- xbmc.log('Deleting via URL: ' + url)
- DownloadUtils().downloadUrl(url, type="DELETE")
+
+ elif method == "VideoLibrary.OnUpdate":
+ # Manually marking as watched/unwatched
+ playcount = data.get('playcount')
+ item = data.get('item')
+ try:
+ kodiid = item['id']
+ type = item['type']
+ except (KeyError, TypeError):
+ self.logMsg("Item is invalid for playstate update.", 1)
+ else:
+ # Send notification to the server.
+ embyconn = utils.kodiSQL('emby')
+ embycursor = embyconn.cursor()
+ emby_db = embydb.Embydb_Functions(embycursor)
+ emby_dbitem = emby_db.getItem_byKodiId(kodiid, type)
+ try:
+ itemid = emby_dbitem[0]
+ except TypeError:
+ self.logMsg("Could not find itemid in emby database.", 1)
+ else:
+ # Stop from manually marking as watched unwatched, with actual playback.
+ if utils.window('emby_skipWatched%s' % itemid) == "true":
+ # property is set in player.py
+ utils.window('emby_skipWatched%s' % itemid, clear=True)
+ else:
+ # notify the server
+ url = "{server}/emby/Users/{UserId}/PlayedItems/%s?format=json" % itemid
+ if playcount != 0:
+ doUtils.downloadUrl(url, type="POST")
+ self.logMsg("Mark as watched for itemid: %s" % itemid, 1)
+ else:
+ doUtils.downloadUrl(url, type="DELETE")
+ self.logMsg("Mark as unwatched for itemid: %s" % itemid, 1)
+ finally:
+ embycursor.close()
+
+
+ elif method == "VideoLibrary.OnRemove":
+
+ try:
+ kodiid = data['id']
+ type = data['type']
+ except (KeyError, TypeError):
+ self.logMsg("Item is invalid for emby deletion.", 1)
+ else:
+ # Send the delete action to the server.
+ offerDelete = False
+
+ if type == "episode" and utils.settings('deleteTV') == "true":
+ offerDelete = True
+ elif type == "movie" and utils.settings('deleteMovies') == "true":
+ offerDelete = True
+
+ if utils.settings('offerDelete') != "true":
+ # Delete could be disabled, even if the subsetting is enabled.
+ offerDelete = False
+
+ if offerDelete:
+ embyconn = utils.kodiSQL('emby')
+ embycursor = embyconn.cursor()
+ emby_db = embydb.Embydb_Functions(embycursor)
+ emby_dbitem = emby_db.getItem_byKodiId(kodiid, type)
+ try:
+ itemid = emby_dbitem[0]
+ except TypeError:
+ self.logMsg("Could not find itemid in emby database.", 1)
+ else:
+ if utils.settings('skipConfirmDelete') != "true":
+ resp = xbmcgui.Dialog().yesno(
+ heading="Confirm delete",
+ line1="Delete file on Emby Server?")
+ if not resp:
+ self.logMsg("User skipped deletion.", 1)
+ embycursor.close()
+ return
+ url = "{server}/emby/Items/%s?format=json" % itemid
+ self.logMsg("Deleting request: %s" % itemid)
+ doUtils.downloadUrl(url, type="DELETE")
+ finally:
+ embycursor.close()
+
+
+ elif method == "System.OnWake":
+ # Allow network to wake up
+ xbmc.sleep(10000)
+ utils.window('emby_onWake', value="true")
elif method == "Playlist.OnClear":
- self.logMsg("Clear playback properties.", 2)
- utils.window('propertiesPlayback', clear=True)
-
- def clearProperty(self, type, id):
- # The sleep is necessary since VideoLibrary.OnUpdate
- # triggers 4 times in a row.
- xbmc.sleep(100)
- utils.window('Played%s%s' % (type,id), clear=True)
-
- # Clear the widget cache
- utils.window('clearwidgetcache', value="clear")
\ No newline at end of file
+ utils.window('emby_customPlaylist', clear=True, windowid=10101)
+ #xbmcgui.Window(10101).clearProperties()
+ self.logMsg("Clear playlist properties.")
\ No newline at end of file
diff --git a/resources/lib/LibrarySync.py b/resources/lib/LibrarySync.py
index f50c88e2..d6d15d8e 100644
--- a/resources/lib/LibrarySync.py
+++ b/resources/lib/LibrarySync.py
@@ -1,1023 +1,181 @@
-#################################################################################################
-# LibrarySync
-#################################################################################################
+# -*- coding: utf-8 -*-
+
+##################################################################################################
+
+import sqlite3
+import threading
+from datetime import datetime, timedelta, time
import xbmc
import xbmcgui
-import xbmcaddon
import xbmcvfs
-import json
-import sqlite3
-import inspect
-import threading
-import urllib
-from datetime import datetime, timedelta, time
-from itertools import chain
-import urllib2
-import os
-import KodiMonitor
-from API import API
-import Utils as utils
-from ClientInformation import ClientInformation
-from DownloadUtils import DownloadUtils
-from ReadEmbyDB import ReadEmbyDB
-from ReadKodiDB import ReadKodiDB
-from WriteKodiVideoDB import WriteKodiVideoDB
-from WriteKodiMusicDB import WriteKodiMusicDB
-from VideoNodes import VideoNodes
+import api
+import utils
+import clientinfo
+import downloadutils
+import itemtypes
+import embydb_functions as embydb
+import kodidb_functions as kodidb
+import read_embyserver as embyserver
+import userclient
+import videonodes
-addondir = xbmc.translatePath(xbmcaddon.Addon(id='plugin.video.emby').getAddonInfo('profile'))
-dataPath = os.path.join(addondir,"library")
-movieLibrary = os.path.join(dataPath,'movies')
-tvLibrary = os.path.join(dataPath,'tvshows')
+##################################################################################################
-WINDOW = xbmcgui.Window( 10000 )
class LibrarySync(threading.Thread):
_shared_state = {}
- KodiMonitor = KodiMonitor.Kodi_Monitor()
- clientInfo = ClientInformation()
-
- addonName = clientInfo.getAddonName()
+ stop_thread = False
+ suspend_thread = False
+ # Track websocketclient updates
+ addedItems = []
updateItems = []
userdataItems = []
removeItems = []
- forceUpdate = False
+ forceLibraryUpdate = False
+ refresh_views = False
- def __init__(self, *args):
+
+ def __init__(self):
self.__dict__ = self._shared_state
- threading.Thread.__init__(self, *args)
+ self.monitor = xbmc.Monitor()
+
+ self.clientInfo = clientinfo.ClientInfo()
+ self.addonName = self.clientInfo.getAddonName()
+ self.doUtils = downloadutils.DownloadUtils()
+ self.user = userclient.UserClient()
+ self.emby = embyserver.Read_EmbyServer()
+ self.vnodes = videonodes.VideoNodes()
+
+ threading.Thread.__init__(self)
def logMsg(self, msg, lvl=1):
className = self.__class__.__name__
- utils.logMsg("%s %s" % (self.addonName, className), msg, int(lvl))
-
- def FullLibrarySync(self,manualRun=False):
-
- startupDone = WINDOW.getProperty("startup") == "done"
- syncInstallRunDone = utils.settings("SyncInstallRunDone") == "true"
- performMusicSync = utils.settings("enableMusicSync") == "true"
- dbSyncIndication = utils.settings("dbSyncIndication") == "true"
+ utils.logMsg("%s %s" % (self.addonName, className), msg, lvl)
- ### BUILD VIDEO NODES LISTING ###
- VideoNodes().buildVideoNodesListing()
- ### CREATE SOURCES ###
- if utils.settings("Sources") != "true":
- # Only create sources once
- self.logMsg("Sources.xml created.", 0)
- utils.createSources()
- utils.settings("Sources", "true")
-
- # just do a incremental sync if that is what is required
- if(utils.settings("useIncSync") == "true" and utils.settings("SyncInstallRunDone") == "true") and manualRun == False:
- utils.logMsg("Sync Database", "Using incremental sync instead of full sync useIncSync=True)", 0)
-
- du = DownloadUtils()
-
- lastSync = utils.settings("LastIncrenetalSync")
- if(lastSync == None or len(lastSync) == 0):
- lastSync = "2010-01-01T00:00:00Z"
- utils.logMsg("Sync Database", "Incremental Sync Setting Last Run Time Loaded : " + lastSync, 0)
- lastSync = urllib2.quote(lastSync)
+ def progressDialog(self, title, forced=False):
+
+ dialog = None
+
+ if utils.settings('dbSyncIndicator') == "true" or forced:
+ dialog = xbmcgui.DialogProgressBG()
+ dialog.create("Emby for Kodi", title)
+ self.logMsg("Show progress dialog: %s" % title, 2)
+
+ return dialog
+
+ def startSync(self):
+ # Run at start up - optional to use the server plugin
+ if utils.settings('SyncInstallRunDone') == "true":
- url = "{server}/Emby.Kodi.SyncQueue/{UserId}/GetItems?LastUpdateDT=" + lastSync + "&format=json"
- utils.logMsg("Sync Database", "Incremental Sync Get Items URL : " + url, 0)
+ # Validate views
+ self.refreshViews()
+ completed = False
+ # Verify if server plugin is installed.
+ if utils.settings('serverSync') == "true":
+ # Try to use fast start up
+ url = "{server}/emby/Plugins?format=json"
+ result = self.doUtils.downloadUrl(url)
+
+ for plugin in result:
+ if plugin['Name'] == "Emby.Kodi Sync Queue":
+ self.logMsg("Found server plugin.", 2)
+ completed = self.fastSync()
- try:
- results = du.downloadUrl(url)
- changedItems = results["ItemsUpdated"] + results["ItemsAdded"]
- removedItems = results["ItemsRemoved"]
- userChanges = results["UserDataChanged"]
- except:
- utils.logMsg("Sync Database", "Incremental Sync Get Changes Failed", 0)
- pass
- else:
- maxItems = int(utils.settings("incSyncMaxItems"))
- utils.logMsg("Sync Database", "Incremental Sync Changes : " + str(results), 0)
- if(len(changedItems) < maxItems and len(removedItems) < maxItems and len(userChanges) < maxItems):
+ if not completed:
+ # Fast sync failed or server plugin is not found
+ completed = self.fullSync(manualrun=True)
+ else:
+ # Install sync is not completed
+ completed = self.fullSync()
+
+ return completed
+
+ def fastSync(self):
+
+ lastSync = utils.settings('LastIncrementalSync')
+ if not lastSync:
+ lastSync = "2010-01-01T00:00:00Z"
+ self.logMsg("Last sync run: %s" % lastSync, 1)
+
+ url = "{server}/emby/Emby.Kodi.SyncQueue/{UserId}/GetItems?format=json"
+ params = {'LastUpdateDT': lastSync}
+ result = self.doUtils.downloadUrl(url, parameters=params)
+
+ try:
+ processlist = {
- WINDOW.setProperty("startup", "done")
-
- LibrarySync().remove_items(removedItems)
- LibrarySync().update_items(changedItems)
- LibrarySync().user_data_update(userChanges)
-
- return True
- else:
- utils.logMsg("Sync Database", "Too Many For Incremental Sync (" + str(maxItems) + "), changedItems" + str(len(changedItems)) + " removedItems:" + str(len(removedItems)) + " userChanges:" + str(len(userChanges)), 0)
+ 'added': result['ItemsAdded'],
+ 'update': result['ItemsUpdated'],
+ 'userdata': result['UserDataChanged'],
+ 'remove': result['ItemsRemoved']
+ }
+
+ except (KeyError, TypeError):
+ self.logMsg("Failed to retrieve latest updates using fast sync.", 1)
+ return False
- #set some variable to check if this is the first run
- WINDOW.setProperty("SyncDatabaseRunning", "true")
-
- #show the progress dialog
- pDialog = None
- if (syncInstallRunDone == False or dbSyncIndication or manualRun):
- pDialog = xbmcgui.DialogProgressBG()
- pDialog.create('Emby for Kodi', 'Performing full sync')
-
- if(WINDOW.getProperty("SyncDatabaseShouldStop") == "true"):
- utils.logMsg("Sync Database", "Can not start SyncDatabaseShouldStop=True", 0)
+ else:
+ self.logMsg("Fast sync changes: %s" % result, 1)
+ for action in processlist:
+ self.triage_items(action, processlist[action])
+
return True
- try:
- completed = True
-
- ### PROCESS VIDEO LIBRARY ###
-
- #create the sql connection to video db
- connection = utils.KodiSQL("video")
- cursor = connection.cursor()
-
- #Add the special emby table
- cursor.execute("CREATE TABLE IF NOT EXISTS emby(emby_id TEXT, kodi_id INTEGER, media_type TEXT, checksum TEXT, parent_id INTEGER, kodi_file_id INTEGER)")
- try:
- cursor.execute("ALTER TABLE emby ADD COLUMN kodi_file_id INTEGER")
- except: pass
- self.dbCommit(connection)
-
- # sync movies
- self.MoviesFullSync(connection,cursor,pDialog)
-
- if (self.ShouldStop()):
- return False
-
- #sync Tvshows and episodes
- self.TvShowsFullSync(connection,cursor,pDialog)
-
- if (self.ShouldStop()):
- return False
-
- # sync musicvideos
- self.MusicVideosFullSync(connection,cursor,pDialog)
-
- #close sql connection
- cursor.close()
-
- ### PROCESS MUSIC LIBRARY ###
- if performMusicSync:
- #create the sql connection to music db
- connection = utils.KodiSQL("music")
- cursor = connection.cursor()
-
- #Add the special emby table
- cursor.execute("CREATE TABLE IF NOT EXISTS emby(emby_id TEXT, kodi_id INTEGER, media_type TEXT, checksum TEXT, parent_id INTEGER, kodi_file_id INTEGER)")
- try:
- cursor.execute("ALTER TABLE emby ADD COLUMN kodi_file_id INTEGER")
- except: pass
- self.dbCommit(connection)
-
- self.MusicFullSync(connection,cursor,pDialog)
- cursor.close()
-
- # set the install done setting
- if(syncInstallRunDone == False and completed):
- utils.settings("SyncInstallRunDone", "true")
- utils.settings("dbCreatedWithVersion", self.clientInfo.getVersion())
-
- # Commit all DB changes at once and Force refresh the library
- #xbmc.executebuiltin("UpdateLibrary(video)")
- #self.updateLibrary("video")
- #xbmc.executebuiltin("UpdateLibrary(music)")
-
- # set prop to show we have run for the first time
- WINDOW.setProperty("startup", "done")
-
- # tell any widgets to refresh because the content has changed
- WINDOW.setProperty("widgetreload", datetime.now().strftime('%Y-%m-%d %H:%M:%S'))
-
- self.SaveLastSync()
-
- finally:
- WINDOW.setProperty("SyncDatabaseRunning", "false")
- utils.logMsg("Sync DB", "syncDatabase Exiting", 0)
+ def saveLastSync(self):
+ # Save last sync time
+ overlap = 2
- if(pDialog != None):
- pDialog.close()
-
- return True
-
- def SaveLastSync(self):
- # save last sync time
-
- du = DownloadUtils()
url = "{server}/Emby.Kodi.SyncQueue/GetServerDateTime?format=json"
-
- try:
- results = du.downloadUrl(url)
- lastSync = results["ServerDateTime"]
- self.logMsg("Sync Database, Incremental Sync Using Server Time: %s" % lastSync, 0)
- lastSync = datetime.strptime(lastSync, "%Y-%m-%dT%H:%M:%SZ")
- lastSync = (lastSync - timedelta(minutes=5)).strftime('%Y-%m-%dT%H:%M:%SZ')
- self.logMsg("Sync Database, Incremental Sync Using Server Time -5 min: %s" % lastSync, 0)
- except:
- lastSync = (datetime.utcnow() - timedelta(minutes=5)).strftime('%Y-%m-%dT%H:%M:%SZ')
- self.logMsg("Sync Database, Incremental Sync Using Client Time -5 min: %s" % lastSync, 0)
-
- self.logMsg("Sync Database, Incremental Sync Setting Last Run Time Saved: %s" % lastSync, 0)
- utils.settings("LastIncrenetalSync", lastSync)
-
- def MoviesFullSync(self,connection, cursor, pDialog):
-
- views = ReadEmbyDB().getCollections("movies")
+ result = self.doUtils.downloadUrl(url)
+ try: # datetime fails when used more than once, TypeError
+ server_time = result['ServerDateTime']
+ server_time = datetime.strptime(server_time, "%Y-%m-%dT%H:%M:%SZ")
- allKodiMovieIds = list()
- allEmbyMovieIds = list()
-
- for view in views:
-
- allEmbyMovies = ReadEmbyDB().getMovies(view.get('id'))
- allKodiMovies = ReadKodiDB().getKodiMovies(connection, cursor)
-
- for kodimovie in allKodiMovies:
- allKodiMovieIds.append(kodimovie[1])
-
- title = view.get('title')
- content = view.get('content')
+ except Exception as e:
+ # If the server plugin is not installed or an error happened.
+ self.logMsg("An exception occurred: %s" % e, 1)
+ time_now = datetime.utcnow()-timedelta(minutes=overlap)
+ lastSync = time_now.strftime('%Y-%m-%dT%H:%M:%SZ')
+ self.logMsg("New sync time: client time -%s min: %s" % (overlap, lastSync), 1)
- if content == "mixed":
- title = "%s - Movies" % title
-
- for kodimovie in allKodiMovies:
- allKodiMovieIds.append(kodimovie[1])
-
- total = len(allEmbyMovies) + 1
- count = 1
-
- #### PROCESS ADDS AND UPDATES ###
- for item in allEmbyMovies:
-
- if (self.ShouldStop()):
- return False
-
- if not item.get('IsFolder'):
- allEmbyMovieIds.append(item["Id"])
-
- if(pDialog != None):
- progressTitle = "Processing " + view.get('title') + " (" + str(count) + " of " + str(total) + ")"
- percentage = int(((float(count) / float(total)) * 100))
- pDialog.update(percentage, "Emby for Kodi - Running Sync", progressTitle)
- count += 1
-
- kodiMovie = None
- for kodimovie in allKodiMovies:
- if kodimovie[1] == item["Id"]:
- kodiMovie = kodimovie
-
- if kodiMovie == None:
- WriteKodiVideoDB().addOrUpdateMovieToKodiLibrary(item["Id"],connection, cursor, title)
- else:
- if kodiMovie[2] != API().getChecksum(item):
- WriteKodiVideoDB().addOrUpdateMovieToKodiLibrary(item["Id"],connection, cursor, title)
-
-
-
- #### PROCESS BOX SETS #####
- utils.logMsg("Sync Movies", "BoxSet Sync Started", 1)
- boxsets = ReadEmbyDB().getBoxSets()
-
- total = len(boxsets) + 1
- count = 1
- for boxset in boxsets:
- if(pDialog != None):
- progressTitle = "Processing BoxSets" + " (" + str(count) + " of " + str(total-1) + ")"
- percentage = int(((float(count) / float(total)) * 100))
- pDialog.update(percentage, "Emby for Kodi - Running Sync", progressTitle)
- count += 1
- if(self.ShouldStop()):
- return False
- boxsetMovies = ReadEmbyDB().getMoviesInBoxSet(boxset["Id"])
- WriteKodiVideoDB().addBoxsetToKodiLibrary(boxset, connection, cursor)
-
- WriteKodiVideoDB().removeMoviesFromBoxset(boxset, connection, cursor)
- for boxsetMovie in boxsetMovies:
- if(self.ShouldStop()):
- return False
- WriteKodiVideoDB().updateBoxsetToKodiLibrary(boxsetMovie,boxset, connection, cursor)
-
- utils.logMsg("Sync Movies", "BoxSet Sync Finished", 1)
-
- #### PROCESS DELETES #####
- allEmbyMovieIds = set(allEmbyMovieIds)
- for kodiId in allKodiMovieIds:
- if not kodiId in allEmbyMovieIds:
- WINDOW.setProperty(kodiId,"deleted")
- WriteKodiVideoDB().deleteItemFromKodiLibrary(kodiId, connection, cursor)
-
- ### commit all changes to database ###
- self.dbCommit(connection)
+ else:
+ lastSync = (server_time - timedelta(minutes=overlap)).strftime('%Y-%m-%dT%H:%M:%SZ')
+ self.logMsg("New sync time: server time -%s min: %s" % (overlap, lastSync), 1)
- def MusicVideosFullSync(self,connection,cursor, pDialog):
-
- allKodiMusicvideoIds = list()
- allEmbyMusicvideoIds = list()
-
- allEmbyMusicvideos = ReadEmbyDB().getMusicVideos()
- allKodiMusicvideos = ReadKodiDB().getKodiMusicVideos(connection, cursor)
-
- for kodivideo in allKodiMusicvideos:
- allKodiMusicvideoIds.append(kodivideo[1])
-
- total = len(allEmbyMusicvideos) + 1
- count = 1
-
- #### PROCESS ADDS AND UPDATES ###
- for item in allEmbyMusicvideos:
-
- if (self.ShouldStop()):
- return False
-
- if not item.get('IsFolder'):
- allEmbyMusicvideoIds.append(item["Id"])
-
- if(pDialog != None):
- progressTitle = "Processing MusicVideos (" + str(count) + " of " + str(total) + ")"
- percentage = int(((float(count) / float(total)) * 100))
- pDialog.update(percentage, "Emby for Kodi - Running Sync", progressTitle)
- count += 1
-
- kodiVideo = None
- for kodivideo in allKodiMusicvideos:
- if kodivideo[1] == item["Id"]:
- kodiVideo = kodivideo
-
- if kodiVideo == None:
- WriteKodiVideoDB().addOrUpdateMusicVideoToKodiLibrary(item["Id"],connection, cursor)
- else:
- if kodiVideo[2] != API().getChecksum(item):
- WriteKodiVideoDB().addOrUpdateMusicVideoToKodiLibrary(item["Id"],connection, cursor)
-
- #### PROCESS DELETES #####
- allEmbyMusicvideoIds = set(allEmbyMusicvideoIds)
- for kodiId in allKodiMusicvideoIds:
- if not kodiId in allEmbyMusicvideoIds:
- WINDOW.setProperty(kodiId,"deleted")
- WriteKodiVideoDB().deleteItemFromKodiLibrary(kodiId, connection, cursor)
-
- ### commit all changes to database ###
- self.dbCommit(connection)
-
- def TvShowsFullSync(self,connection,cursor,pDialog):
-
- views = ReadEmbyDB().getCollections("tvshows")
-
- allKodiTvShowIds = list()
- allEmbyTvShowIds = list()
-
- for view in views:
-
- allEmbyTvShows = ReadEmbyDB().getTvShows(view.get('id'))
- allKodiTvShows = ReadKodiDB().getKodiTvShows(connection, cursor)
-
- title = view.get('title')
- content = view.get('content')
+ finally:
+ utils.settings('LastIncrementalSync', value=lastSync)
- if content == "mixed":
- title = "%s - TV Shows" % title
-
- total = len(allEmbyTvShows) + 1
- count = 1
-
- for kodishow in allKodiTvShows:
- allKodiTvShowIds.append(kodishow[1])
-
- #### TVSHOW: PROCESS ADDS AND UPDATES ###
- for item in allEmbyTvShows:
-
- if (self.ShouldStop()):
- return False
-
- if(pDialog != None):
- progressTitle = "Processing " + view.get('title') + " (" + str(count) + " of " + str(total) + ")"
- percentage = int(((float(count) / float(total)) * 100))
- pDialog.update(percentage, "Emby for Kodi - Running Sync", progressTitle)
- count += 1
-
- if utils.settings('syncEmptyShows') == "true" or (item.get('IsFolder') and item.get('RecursiveItemCount') != 0):
- allEmbyTvShowIds.append(item["Id"])
-
- #build a list with all Id's and get the existing entry (if exists) in Kodi DB
- kodiShow = None
- for kodishow in allKodiTvShows:
- if kodishow[1] == item["Id"]:
- kodiShow = kodishow
-
- if kodiShow == None:
- # Tv show doesn't exist in Kodi yet so proceed and add it
- WriteKodiVideoDB().addOrUpdateTvShowToKodiLibrary(item["Id"],connection, cursor, title)
- else:
- # If there are changes to the item, perform a full sync of the item
- if kodiShow[2] != API().getChecksum(item):
- WriteKodiVideoDB().addOrUpdateTvShowToKodiLibrary(item["Id"],connection, cursor, title)
-
- #### PROCESS EPISODES ######
- self.EpisodesFullSync(connection,cursor,item["Id"])
-
- #### TVSHOW: PROCESS DELETES #####
- allEmbyTvShowIds = set(allEmbyTvShowIds)
- for kodiId in allKodiTvShowIds:
- if not kodiId in allEmbyTvShowIds:
- WINDOW.setProperty(kodiId,"deleted")
- WriteKodiVideoDB().deleteItemFromKodiLibrary(kodiId, connection, cursor)
-
- ### commit all changes to database ###
- self.dbCommit(connection)
-
- def EpisodesFullSync(self,connection,cursor,showId):
-
- WINDOW = xbmcgui.Window( 10000 )
-
- allKodiEpisodeIds = list()
- allEmbyEpisodeIds = list()
-
- # Get the kodi parent id
- cursor.execute("SELECT kodi_id FROM emby WHERE emby_id=?",(showId,))
- try:
- kodiShowId = cursor.fetchone()[0]
- except:
- self.logMsg("Unable to find show itemId:%s" % showId, 1)
- return
-
- allEmbyEpisodes = ReadEmbyDB().getEpisodes(showId)
- allKodiEpisodes = ReadKodiDB().getKodiEpisodes(connection, cursor, kodiShowId)
-
- for kodiepisode in allKodiEpisodes:
- allKodiEpisodeIds.append(kodiepisode[1])
-
- #### EPISODES: PROCESS ADDS AND UPDATES ###
- for item in allEmbyEpisodes:
-
- if (self.ShouldStop()):
- return False
-
- allEmbyEpisodeIds.append(item["Id"])
-
- #get the existing entry (if exists) in Kodi DB
- kodiEpisode = None
- for kodiepisode in allKodiEpisodes:
- if kodiepisode[1] == item["Id"]:
- kodiEpisode = kodiepisode
-
- if kodiEpisode == None:
- # Episode doesn't exist in Kodi yet so proceed and add it
- WriteKodiVideoDB().addOrUpdateEpisodeToKodiLibrary(item["Id"], kodiShowId, connection, cursor)
- else:
- # If there are changes to the item, perform a full sync of the item
- if kodiEpisode[2] != API().getChecksum(item):
- WriteKodiVideoDB().addOrUpdateEpisodeToKodiLibrary(item["Id"], kodiShowId, connection, cursor)
-
- #### EPISODES: PROCESS DELETES #####
- allEmbyEpisodeIds = set(allEmbyEpisodeIds)
- for kodiId in allKodiEpisodeIds:
- if (not kodiId in allEmbyEpisodeIds):
- WINDOW.setProperty(kodiId,"deleted")
- WriteKodiVideoDB().deleteItemFromKodiLibrary(kodiId, connection, cursor)
-
- def MusicFullSync(self, connection,cursor, pDialog):
-
- self.ProcessMusicArtists(connection,cursor,pDialog)
- self.dbCommit(connection)
- self.ProcessMusicAlbums(connection,cursor,pDialog)
- self.dbCommit(connection)
- self.ProcessMusicSongs(connection,cursor,pDialog)
-
- ### commit all changes to database ###
- self.dbCommit(connection)
-
- def ProcessMusicSongs(self,connection,cursor,pDialog):
-
- allKodiSongIds = list()
- allEmbySongIds = list()
-
- allEmbySongs = ReadEmbyDB().getMusicSongsTotal()
- allKodiSongs = ReadKodiDB().getKodiMusicSongs(connection, cursor)
-
- for kodisong in allKodiSongs:
- allKodiSongIds.append(kodisong[1])
-
- total = len(allEmbySongs) + 1
- count = 1
-
- #### PROCESS SONGS ADDS AND UPDATES ###
- for item in allEmbySongs:
-
- if (self.ShouldStop()):
- return False
-
- allEmbySongIds.append(item["Id"])
-
- if(pDialog != None):
- progressTitle = "Processing Music Songs (" + str(count) + " of " + str(total) + ")"
- percentage = int(((float(count) / float(total)) * 100))
- pDialog.update(percentage, "Emby for Kodi - Running Sync", progressTitle)
- count += 1
-
- kodiSong = None
- for kodisong in allKodiSongs:
- if kodisong[1] == item["Id"]:
- kodiSong = kodisong
-
- if kodiSong == None:
- WriteKodiMusicDB().addOrUpdateSongToKodiLibrary(item,connection, cursor)
- else:
- if kodiSong[2] != API().getChecksum(item):
- WriteKodiMusicDB().addOrUpdateSongToKodiLibrary(item,connection, cursor)
-
- #### PROCESS DELETES #####
- allEmbySongIds = set(allEmbySongIds)
- for kodiId in allKodiSongIds:
- if not kodiId in allEmbySongIds:
- WINDOW.setProperty(kodiId,"deleted")
- WriteKodiMusicDB().deleteItemFromKodiLibrary(kodiId, connection, cursor)
-
- def ProcessMusicArtists(self,connection,cursor,pDialog):
-
- allKodiArtistIds = list()
- allEmbyArtistIds = list()
-
- allEmbyArtists = ReadEmbyDB().getMusicArtistsTotal()
- allKodiArtists = ReadKodiDB().getKodiMusicArtists(connection, cursor)
-
- for kodiartist in allKodiArtists:
- allKodiArtistIds.append(kodiartist[1])
-
- total = len(allEmbyArtists) + 1
- count = 1
-
- #### PROCESS ARTIST ADDS AND UPDATES ###
- for item in allEmbyArtists:
-
- if (self.ShouldStop()):
- return False
-
- allEmbyArtistIds.append(item["Id"])
-
- if(pDialog != None):
- progressTitle = "Processing Music Artists (" + str(count) + " of " + str(total) + ")"
- percentage = int(((float(count) / float(total)) * 100))
- pDialog.update(percentage, "Emby for Kodi - Running Sync", progressTitle)
- count += 1
-
- kodiArtist = None
- for kodiartist in allKodiArtists:
- if kodiartist[1] == item["Id"]:
- kodiArtist = kodiartist
-
- if kodiArtist == None:
- WriteKodiMusicDB().addOrUpdateArtistToKodiLibrary(item,connection, cursor)
- else:
- if kodiArtist[2] != API().getChecksum(item):
- WriteKodiMusicDB().addOrUpdateArtistToKodiLibrary(item,connection, cursor)
-
- #### PROCESS DELETES #####
- allEmbyArtistIds = set(allEmbyArtistIds)
- for kodiId in allKodiArtistIds:
- if not kodiId in allEmbyArtistIds:
- WINDOW.setProperty(kodiId,"deleted")
- WriteKodiMusicDB().deleteItemFromKodiLibrary(kodiId, connection, cursor)
-
- def ProcessMusicAlbums(self,connection,cursor,pDialog):
-
- allKodiAlbumIds = list()
- allEmbyAlbumIds = list()
-
- allEmbyAlbums = ReadEmbyDB().getMusicAlbumsTotal()
- allKodiAlbums = ReadKodiDB().getKodiMusicAlbums(connection, cursor)
-
- for kodialbum in allKodiAlbums:
- allKodiAlbumIds.append(kodialbum[1])
-
- total = len(allEmbyAlbums) + 1
- count = 1
-
- #### PROCESS SONGS ADDS AND UPDATES ###
- for item in allEmbyAlbums:
-
- if (self.ShouldStop()):
- return False
-
- allEmbyAlbumIds.append(item["Id"])
-
- if(pDialog != None):
- progressTitle = "Processing Music Albums (" + str(count) + " of " + str(total) + ")"
- percentage = int(((float(count) / float(total)) * 100))
- pDialog.update(percentage, "Emby for Kodi - Running Sync", progressTitle)
- count += 1
-
- kodiAlbum = None
- for kodialbum in allKodiAlbums:
- if kodialbum[1] == item["Id"]:
- kodiAlbum = kodialbum
-
- if kodiAlbum == None:
- WriteKodiMusicDB().addOrUpdateAlbumToKodiLibrary(item,connection, cursor)
- else:
- if kodiAlbum[2] != API().getChecksum(item):
- WriteKodiMusicDB().addOrUpdateAlbumToKodiLibrary(item,connection, cursor)
-
- #### PROCESS DELETES #####
- allEmbyAlbumIds = set(allEmbyAlbumIds)
- for kodiId in allKodiAlbumIds:
- if not kodiId in allEmbyAlbumIds:
- WINDOW.setProperty(kodiId,"deleted")
- WriteKodiMusicDB().deleteItemFromKodiLibrary(kodiId, connection, cursor)
-
- def IncrementalSync(self, itemList):
-
- startupDone = WINDOW.getProperty("startup") == "done"
-
- #only perform incremental scan when full scan is completed
- if startupDone:
-
- #this will only perform sync for items received by the websocket
- dbSyncIndication = utils.settings("dbSyncIndication") == "true"
- performMusicSync = utils.settings("enableMusicSync") == "true"
- WINDOW.setProperty("SyncDatabaseRunning", "true")
-
- #show the progress dialog
- pDialog = None
- if (dbSyncIndication and xbmc.Player().isPlaying() == False):
- pDialog = xbmcgui.DialogProgressBG()
- pDialog.create('Emby for Kodi', 'Incremental Sync')
- self.logMsg("Doing LibraryChanged : Show Progress IncrementalSync()", 0);
-
- connection = utils.KodiSQL("video")
- cursor = connection.cursor()
-
- try:
- #### PROCESS MOVIES ####
- views = ReadEmbyDB().getCollections("movies")
- for view in views:
- allEmbyMovies = ReadEmbyDB().getMovies(view.get('id'), itemList)
- count = 1
- total = len(allEmbyMovies) + 1
- for item in allEmbyMovies:
- if(pDialog != None):
- progressTitle = "Incremental Sync "+ " (" + str(count) + " of " + str(total) + ")"
- percentage = int(((float(count) / float(total)) * 100))
- pDialog.update(percentage, "Emby for Kodi - Incremental Sync Movies", progressTitle)
- count = count + 1
- if not item.get('IsFolder'):
- WriteKodiVideoDB().addOrUpdateMovieToKodiLibrary(item["Id"],connection, cursor, view.get('title'))
-
- #### PROCESS BOX SETS #####
- boxsets = ReadEmbyDB().getBoxSets()
- count = 1
- total = len(boxsets) + 1
- for boxset in boxsets:
- if(boxset["Id"] in itemList):
- utils.logMsg("IncrementalSync", "Updating box Set : " + str(boxset["Name"]), 1)
- boxsetMovies = ReadEmbyDB().getMoviesInBoxSet(boxset["Id"])
- WriteKodiVideoDB().addBoxsetToKodiLibrary(boxset, connection, cursor)
- if(pDialog != None):
- progressTitle = "Incremental Sync "+ " (" + str(count) + " of " + str(total) + ")"
- percentage = int(((float(count) / float(total)) * 100))
- pDialog.update(percentage, "Emby for Kodi - Incremental Sync BoxSet", progressTitle)
- count = count + 1
- WriteKodiVideoDB().removeMoviesFromBoxset(boxset, connection, cursor)
- for boxsetMovie in boxsetMovies:
- WriteKodiVideoDB().updateBoxsetToKodiLibrary(boxsetMovie, boxset, connection, cursor)
- else:
- utils.logMsg("IncrementalSync", "Skipping Box Set : " + boxset["Name"], 1)
-
- #### PROCESS TV SHOWS ####
- views = ReadEmbyDB().getCollections("tvshows")
- for view in views:
- allEmbyTvShows = ReadEmbyDB().getTvShows(view.get('id'),itemList)
- count = 1
- total = len(allEmbyTvShows) + 1
- for item in allEmbyTvShows:
- if(pDialog != None):
- progressTitle = "Incremental Sync "+ " (" + str(count) + " of " + str(total) + ")"
- percentage = int(((float(count) / float(total)) * 100))
- pDialog.update(percentage, "Emby for Kodi - Incremental Sync Tv", progressTitle)
- count = count + 1
- if utils.settings('syncEmptyShows') == "true" or (item.get('IsFolder') and item.get('RecursiveItemCount') != 0):
- kodiId = WriteKodiVideoDB().addOrUpdateTvShowToKodiLibrary(item["Id"],connection, cursor, view.get('title'))
-
-
- #### PROCESS OTHERS BY THE ITEMLIST ######
- count = 1
- total = len(itemList) + 1
- for item in itemList:
-
- if(pDialog != None):
- progressTitle = "Incremental Sync "+ " (" + str(count) + " of " + str(total) + ")"
- percentage = int(((float(count) / float(total)) * 100))
- pDialog.update(percentage, "Emby for Kodi - Incremental Sync Items", progressTitle)
- count = count + 1
-
- MBitem = ReadEmbyDB().getItem(item)
- itemType = MBitem.get('Type', "")
-
- #### PROCESS EPISODES ######
- if "Episode" in itemType:
-
- #get the tv show
- cursor.execute("SELECT kodi_id FROM emby WHERE media_type='tvshow' AND emby_id=?", (MBitem.get("SeriesId"),))
- result = cursor.fetchone()
- if result:
- kodi_show_id = result[0]
- else:
- kodi_show_id = None
-
- if kodi_show_id:
- WriteKodiVideoDB().addOrUpdateEpisodeToKodiLibrary(MBitem["Id"], kodi_show_id, connection, cursor)
- else:
- #tv show doesn't exist
- #perform full tvshow sync instead so both the show and episodes get added
- self.TvShowsFullSync(connection,cursor,None)
-
- elif "Season" in itemType:
-
- #get the tv show
- cursor.execute("SELECT kodi_id FROM emby WHERE media_type='tvshow' AND emby_id=?", (MBitem.get("SeriesId"),))
- result = cursor.fetchone()
- if result:
- kodi_show_id = result[0]
- # update season
- WriteKodiVideoDB().updateSeasons(MBitem["SeriesId"], kodi_show_id, connection, cursor)
-
- #### PROCESS BOXSETS ######
- elif "BoxSet" in itemType:
- boxsetMovies = ReadEmbyDB().getMoviesInBoxSet(boxset["Id"])
- WriteKodiVideoDB().addBoxsetToKodiLibrary(boxset,connection, cursor)
-
- for boxsetMovie in boxsetMovies:
- WriteKodiVideoDB().updateBoxsetToKodiLibrary(boxsetMovie,boxset, connection, cursor)
-
- #### PROCESS MUSICVIDEOS ####
- elif "MusicVideo" in itemType:
- if not MBitem.get('IsFolder'):
- WriteKodiVideoDB().addOrUpdateMusicVideoToKodiLibrary(MBitem["Id"],connection, cursor)
-
- ### commit all changes to database ###
- self.dbCommit(connection)
- cursor.close()
-
- ### PROCESS MUSIC LIBRARY ###
- if performMusicSync:
- connection = utils.KodiSQL("music")
- cursor = connection.cursor()
- for item in itemList:
- MBitem = ReadEmbyDB().getItem(item)
- itemType = MBitem.get('Type', "")
-
- if "MusicArtist" in itemType:
- WriteKodiMusicDB().addOrUpdateArtistToKodiLibrary(MBitem, connection, cursor)
- if "MusicAlbum" in itemType:
- WriteKodiMusicDB().addOrUpdateAlbumToKodiLibrary(MBitem, connection, cursor)
- if "Audio" in itemType:
- WriteKodiMusicDB().addOrUpdateSongToKodiLibrary(MBitem, connection, cursor)
- self.dbCommit(connection)
- cursor.close()
-
- finally:
- if(pDialog != None):
- pDialog.close()
-
- #self.updateLibrary("video")
- WINDOW.setProperty("SyncDatabaseRunning", "false")
- # tell any widgets to refresh because the content has changed
- WINDOW.setProperty("widgetreload", datetime.now().strftime('%Y-%m-%d %H:%M:%S'))
-
- def removefromDB(self, itemList, deleteEmbyItem = False):
-
- dbSyncIndication = utils.settings("dbSyncIndication") == "true"
-
- #show the progress dialog
- pDialog = None
- if (dbSyncIndication and xbmc.Player().isPlaying() == False):
- pDialog = xbmcgui.DialogProgressBG()
- pDialog.create('Emby for Kodi', 'Incremental Sync')
- self.logMsg("Doing LibraryChanged : Show Progress removefromDB()", 0);
-
- # Delete from Kodi before Emby
- # To be able to get mediaType
- doUtils = DownloadUtils()
- video = {}
- music = []
-
- # Database connection to myVideosXX.db
- connectionvideo = utils.KodiSQL()
- cursorvideo = connectionvideo.cursor()
- # Database connection to myMusicXX.db
- connectionmusic = utils.KodiSQL("music")
- cursormusic = connectionmusic.cursor()
-
- count = 1
- total = len(itemList) + 1
- for item in itemList:
-
- if(pDialog != None):
- progressTitle = "Incremental Sync "+ " (" + str(count) + " of " + str(total) + ")"
- percentage = int(((float(count) / float(total)) * 100))
- pDialog.update(percentage, "Emby for Kodi - Incremental Sync Delete ", progressTitle)
- count = count + 1
-
- # Sort by type for database deletion
- try: # Search video database
- self.logMsg("Check video database.", 1)
- cursorvideo.execute("SELECT media_type FROM emby WHERE emby_id = ?", (item,))
- mediatype = cursorvideo.fetchone()[0]
- video[item] = mediatype
- #video.append(itemtype)
- except:
- self.logMsg("Check music database.", 1)
- try: # Search music database
- cursormusic.execute("SELECT media_type FROM emby WHERE emby_id = ?", (item,))
- cursormusic.fetchone()[0]
- music.append(item)
- except: self.logMsg("Item %s is not found in Kodi database." % item, 1)
-
- if len(video) > 0:
- connection = connectionvideo
- cursor = cursorvideo
- # Process video library
- count = 1
- total = len(video) + 1
- for item in video:
-
- if(pDialog != None):
- progressTitle = "Incremental Sync "+ " (" + str(count) + " of " + str(total) + ")"
- percentage = int(((float(count) / float(total)) * 100))
- pDialog.update(percentage, "Emby for Kodi - Incremental Sync Delete ", progressTitle)
- count = count + 1
-
- type = video[item]
- self.logMsg("Doing LibraryChanged: Items Removed: Calling deleteItemFromKodiLibrary: %s" % item, 1)
-
- if "episode" in type:
- # Get the TV Show Id for reference later
- showId = ReadKodiDB().getShowIdByEmbyId(item, connection, cursor)
- self.logMsg("ShowId: %s" % showId, 1)
- WriteKodiVideoDB().deleteItemFromKodiLibrary(item, connection, cursor)
- # Verification
- if "episode" in type:
- showTotalCount = ReadKodiDB().getShowTotalCount(showId, connection, cursor)
- self.logMsg("ShowTotalCount: %s" % showTotalCount, 1)
- # If there are no episodes left
- if showTotalCount == 0 or showTotalCount == None:
- # Delete show
- embyId = ReadKodiDB().getEmbyIdByKodiId(showId, "tvshow", connection, cursor)
- self.logMsg("Message: Doing LibraryChanged: Deleting show: %s" % embyId, 1)
- WriteKodiVideoDB().deleteItemFromKodiLibrary(embyId, connection, cursor)
-
- self.dbCommit(connection)
- # Close connection
- cursorvideo.close()
-
- if len(music) > 0:
- connection = connectionmusic
- cursor = cursormusic
- #Process music library
- if utils.settings('enableMusicSync') == "true":
-
- for item in music:
- self.logMsg("Message : Doing LibraryChanged : Items Removed : Calling deleteItemFromKodiLibrary (musiclibrary): " + item, 0)
- WriteKodiMusicDB().deleteItemFromKodiLibrary(item, connection, cursor)
-
- self.dbCommit(connection)
- # Close connection
- cursormusic.close()
-
- if deleteEmbyItem:
- for item in itemList:
- url = "{server}/mediabrowser/Items/%s" % item
- self.logMsg('Deleting via URL: %s' % url)
- doUtils.downloadUrl(url, type = "DELETE")
- xbmc.executebuiltin("Container.Refresh")
-
- if(pDialog != None):
- pDialog.close()
-
- def setUserdata(self, listItems):
-
- dbSyncIndication = utils.settings("dbSyncIndication") == "true"
- musicenabled = utils.settings('enableMusicSync') == "true"
-
- #show the progress dialog
- pDialog = None
- if (dbSyncIndication and xbmc.Player().isPlaying() == False):
- pDialog = xbmcgui.DialogProgressBG()
- pDialog.create('Emby for Kodi', 'Incremental Sync')
- self.logMsg("Doing LibraryChanged : Show Progress setUserdata()", 0);
-
- # We need to sort between video and music database
- video = []
- music = []
- # Database connection to myVideosXX.db
- connectionvideo = utils.KodiSQL()
- cursorvideo = connectionvideo.cursor()
- # Database connection to myMusicXX.db
- connectionmusic = utils.KodiSQL('music')
- cursormusic = connectionmusic.cursor()
-
- count = 1
- total = len(listItems) + 1
- for userdata in listItems:
- # Sort between video and music
- itemId = userdata['ItemId']
-
- if(pDialog != None):
- progressTitle = "Incremental Sync "+ " (" + str(count) + " of " + str(total) + ")"
- percentage = int(((float(count) / float(total)) * 100))
- pDialog.update(percentage, "Emby for Kodi - Incremental Sync User Data ", progressTitle)
- count = count + 1
-
- cursorvideo.execute("SELECT media_type FROM emby WHERE emby_id = ?", (itemId,))
- try: # Search video database
- self.logMsg("Check video database.", 2)
- mediatype = cursorvideo.fetchone()[0]
- video.append(userdata)
- except:
- if musicenabled:
- cursormusic.execute("SELECT media_type FROM emby WHERE emby_id = ?", (itemId,))
- try: # Search music database
- self.logMsg("Check the music database.", 2)
- mediatype = cursormusic.fetchone()[0]
- music.append(userdata)
- except: self.logMsg("Item %s is not found in Kodi database." % itemId, 1)
- else:
- self.logMsg("Item %s is not found in Kodi database." % itemId, 1)
-
- if len(video) > 0:
- connection = connectionvideo
- cursor = cursorvideo
- # Process the userdata update for video library
- count = 1
- total = len(video) + 1
- for userdata in video:
- if(pDialog != None):
- progressTitle = "Incremental Sync "+ " (" + str(count) + " of " + str(total) + ")"
- percentage = int(((float(count) / float(total)) * 100))
- pDialog.update(percentage, "Emby for Kodi - Incremental Sync User Data ", progressTitle)
- count = count + 1
- WriteKodiVideoDB().updateUserdata(userdata, connection, cursor)
-
- self.dbCommit(connection)
- #self.updateLibrary("video")
- # Close connection
- cursorvideo.close()
-
- if len(music) > 0:
- connection = connectionmusic
- cursor = cursormusic
- #Process music library
- count = 1
- total = len(video) + 1
- # Process the userdata update for music library
- if musicenabled:
- for userdata in music:
- if(pDialog != None):
- progressTitle = "Incremental Sync "+ " (" + str(count) + " of " + str(total) + ")"
- percentage = int(((float(count) / float(total)) * 100))
- pDialog.update(percentage, "Emby for Kodi - Incremental Sync User Data ", progressTitle)
- count = count + 1
- WriteKodiMusicDB().updateUserdata(userdata, connection, cursor)
-
- self.dbCommit(connection)
- #xbmc.executebuiltin("UpdateLibrary(music)")
- # Close connection
- cursormusic.close()
-
- if(pDialog != None):
- pDialog.close()
-
- def remove_items(self, itemsRemoved):
- # websocket client
- if(len(itemsRemoved) > 0):
- self.logMsg("Doing LibraryChanged : Processing Deleted : " + str(itemsRemoved), 0)
- self.removeItems.extend(itemsRemoved)
-
- def update_items(self, itemsToUpdate):
- # websocket client
- if(len(itemsToUpdate) > 0):
- self.logMsg("Doing LibraryChanged : Processing Added and Updated : " + str(itemsToUpdate), 0)
- self.updateItems.extend(itemsToUpdate)
-
- def user_data_update(self, userDataList):
- # websocket client
- if(len(userDataList) > 0):
- self.logMsg("Doing LibraryChanged : Processing User Data Changed : " + str(userDataList), 0)
- self.userdataItems.extend(userDataList)
+ def shouldStop(self):
+ # Checkpoint during the syncing process
+ if self.monitor.abortRequested():
+ return True
+ elif utils.window('emby_shouldStop') == "true":
+ return True
+ else: # Keep going
+ return False
def dbCommit(self, connection):
- # Central commit, will verify if Kodi database
- kodidb_scan = utils.window('kodiScan') == "true"
+ # Central commit, verifies if Kodi database update is running
+ kodidb_scan = utils.window('emby_kodiScan') == "true"
while kodidb_scan:
-
- self.logMsg("Kodi scan running. Waiting...", 1)
- kodidb_scan = utils.window('kodiScan') == "true"
- if self.KodiMonitor.waitForAbort(1):
+ self.logMsg("Kodi scan is running. Waiting...", 1)
+ kodidb_scan = utils.window('emby_kodiScan') == "true"
+
+ if self.shouldStop():
+ self.logMsg("Commit unsuccessful. Sync terminated.", 1)
+ break
+
+ if self.monitor.waitForAbort(1):
# Abort was requested while waiting. We should exit
self.logMsg("Commit unsuccessful.", 1)
break
@@ -1025,32 +183,1027 @@ class LibrarySync(threading.Thread):
connection.commit()
self.logMsg("Commit successful.", 1)
- def updateLibrary(self, type):
+ def fullSync(self, manualrun=False, repair=False):
+ # Only run once when first setting up. Can be run manually.
+ emby = self.emby
+ music_enabled = utils.settings('enableMusic') == "true"
- self.logMsg("Updating %s library." % type, 1)
- utils.window('kodiScan', value="true")
- xbmc.executebuiltin('UpdateLibrary(%s)' % type)
+ utils.window('emby_dbScan', value="true")
+ # Add sources
+ utils.sourcesXML()
- def ShouldStop(self):
-
- if(xbmc.abortRequested):
- return True
-
- if(WINDOW.getProperty("SyncDatabaseShouldStop") == "true"):
- return True
-
- return False
+ embyconn = utils.kodiSQL('emby')
+ embycursor = embyconn.cursor()
+ # Create the tables for the emby database
+ # emby, view, version
+ embycursor.execute(
+ """CREATE TABLE IF NOT EXISTS emby(
+ emby_id TEXT UNIQUE, media_folder TEXT, emby_type TEXT, media_type TEXT, kodi_id INTEGER,
+ kodi_fileid INTEGER, kodi_pathid INTEGER, parent_id INTEGER, checksum INTEGER)""")
+ embycursor.execute(
+ """CREATE TABLE IF NOT EXISTS view(
+ view_id TEXT UNIQUE, view_name TEXT, media_type TEXT, kodi_tagid INTEGER)""")
+ embycursor.execute("CREATE TABLE IF NOT EXISTS version(idVersion TEXT)")
+ embyconn.commit()
- def checkDBVersion(self, currVersion, minVersion):
- currMajor, currMinor, currPatch = currVersion.split(".")
- minMajor, minMinor, minPatch = minVersion.split(".")
+ # content sync: movies, tvshows, musicvideos, music
+ kodiconn = utils.kodiSQL('video')
+ kodicursor = kodiconn.cursor()
+
+ if manualrun:
+ message = "Manual sync"
+ elif repair:
+ message = "Repair sync"
+ else:
+ message = "Initial sync"
+
+ pDialog = self.progressDialog("%s" % message, forced=True)
+ starttotal = datetime.now()
+
+ # Set views
+ self.maintainViews(embycursor, kodicursor)
+ embyconn.commit()
+
+ # Sync video library
+ process = {
+
+ 'movies': self.movies,
+ 'musicvideos': self.musicvideos,
+ 'tvshows': self.tvshows,
+ 'homevideos': self.homevideos
+ }
+ for itemtype in process:
+ startTime = datetime.now()
+ completed = process[itemtype](embycursor, kodicursor, pDialog, compare=manualrun)
+ if not completed:
+
+ utils.window('emby_dbScan', clear=True)
+ if pDialog:
+ pDialog.close()
+
+ embycursor.close()
+ kodicursor.close()
+ return False
+ else:
+ self.dbCommit(kodiconn)
+ embyconn.commit()
+ elapsedTime = datetime.now() - startTime
+ self.logMsg(
+ "SyncDatabase (finished %s in: %s)"
+ % (itemtype, str(elapsedTime).split('.')[0]), 1)
+
+ # sync music
+ if music_enabled:
+
+ musicconn = utils.kodiSQL('music')
+ musiccursor = musicconn.cursor()
+
+ startTime = datetime.now()
+ completed = self.music(embycursor, musiccursor, pDialog, compare=manualrun)
+ if not completed:
+
+ utils.window('emby_dbScan', clear=True)
+ if pDialog:
+ pDialog.close()
+
+ embycursor.close()
+ musiccursor.close()
+ return False
+ else:
+ musicconn.commit()
+ embyconn.commit()
+ elapsedTime = datetime.now() - startTime
+ self.logMsg(
+ "SyncDatabase (finished music in: %s)"
+ % (str(elapsedTime).split('.')[0]), 1)
+ musiccursor.close()
+
+ if pDialog:
+ pDialog.close()
+
+ embycursor.close()
+ kodicursor.close()
+
+ utils.settings('SyncInstallRunDone', value="true")
+ utils.settings("dbCreatedWithVersion", self.clientInfo.getVersion())
+ self.saveLastSync()
+ # tell any widgets to refresh because the content has changed
+ utils.window('widgetreload', value=datetime.now().strftime('%Y-%m-%d %H:%M:%S'))
+ xbmc.executebuiltin('UpdateLibrary(video)')
+ elapsedtotal = datetime.now() - starttotal
+
+ utils.window('emby_dbScan', clear=True)
+ xbmcgui.Dialog().notification(
+ heading="Emby for Kodi",
+ message="%s completed in: %s!" %
+ (message, str(elapsedtotal).split('.')[0]),
+ icon="special://home/addons/plugin.video.emby/icon.png",
+ sound=False)
+ return True
+
+
+ def refreshViews(self):
+
+ embyconn = utils.kodiSQL('emby')
+ embycursor = embyconn.cursor()
+ kodiconn = utils.kodiSQL('video')
+ kodicursor = kodiconn.cursor()
+
+ # Compare views, assign correct tags to items
+ self.maintainViews(embycursor, kodicursor)
+
+ self.dbCommit(kodiconn)
+ kodicursor.close()
+
+ embyconn.commit()
+ embycursor.close()
+
+ def maintainViews(self, embycursor, kodicursor):
+ # Compare the views to emby
+ emby_db = embydb.Embydb_Functions(embycursor)
+ kodi_db = kodidb.Kodidb_Functions(kodicursor)
+ doUtils = self.doUtils
+ vnodes = self.vnodes
+
+ # Get views
+ url = "{server}/emby/Users/{UserId}/Views?format=json"
+ result = doUtils.downloadUrl(url)
+ grouped_views = result['Items']
+
+ try:
+ groupedFolders = self.user.userSettings['Configuration']['GroupedFolders']
+ except TypeError:
+ url = "{server}/emby/Users/{UserId}?format=json"
+ result = doUtils.downloadUrl(url)
+ groupedFolders = result['Configuration']['GroupedFolders']
+
+ # total nodes for window properties
+ vnodes.clearProperties()
+ totalnodes = 0
+
+ # Set views for supported media type
+ mediatypes = ['movies', 'tvshows', 'musicvideos', 'homevideos', 'music']
+ for mediatype in mediatypes:
+
+ # Get media folders from server
+ folders = self.emby.getViews(mediatype, root=True)
+ for folder in folders:
+
+ folderid = folder['id']
+ foldername = folder['name']
+ viewtype = folder['type']
+
+ if folderid in groupedFolders:
+ # Media folders are grouped into userview
+ for grouped_view in grouped_views:
+ if (grouped_view['Type'] == "UserView" and
+ grouped_view['CollectionType'] == mediatype):
+ # Take the name of the userview
+ foldername = grouped_view['Name']
+ break
+
+ # Get current media folders from emby database
+ view = emby_db.getView_byId(folderid)
+ try:
+ current_viewname = view[0]
+ current_viewtype = view[1]
+ current_tagid = view[2]
+
+ except TypeError:
+ self.logMsg("Creating viewid: %s in Emby database." % folderid, 1)
+ tagid = kodi_db.createTag(foldername)
+ # Create playlist for the video library
+ if mediatype != "music":
+ utils.playlistXSP(mediatype, foldername, viewtype)
+ # Create the video node
+ if mediatype != "musicvideos":
+ vnodes.viewNode(totalnodes, foldername, mediatype, viewtype)
+ totalnodes += 1
+ # Add view to emby database
+ emby_db.addView(folderid, foldername, viewtype, tagid)
+
+ else:
+ self.logMsg(' '.join((
+
+ "Found viewid: %s" % folderid,
+ "viewname: %s" % current_viewname,
+ "viewtype: %s" % current_viewtype,
+ "tagid: %s" % current_tagid)), 2)
+
+ # View was modified, update with latest info
+ if current_viewname != foldername:
+ self.logMsg("viewid: %s new viewname: %s" % (folderid, foldername), 1)
+ tagid = kodi_db.createTag(foldername)
+
+ # Update view with new info
+ emby_db.updateView(foldername, tagid, folderid)
+
+ if mediatype != "music":
+ if emby_db.getView_byName(current_viewname) is None:
+ # The tag could be a combined view. Ensure there's no other tags
+ # with the same name before deleting playlist.
+ utils.playlistXSP(
+ mediatype, current_viewname, current_viewtype, True)
+ # Delete video node
+ if mediatype != "musicvideos":
+ vnodes.viewNode(
+ indexnumber=totalnodes,
+ tagname=current_viewname,
+ mediatype=mediatype,
+ viewtype=current_viewtype,
+ delete=True)
+ # Added new playlist
+ utils.playlistXSP(mediatype, foldername, viewtype)
+ # Add new video node
+ if mediatype != "musicvideos":
+ vnodes.viewNode(totalnodes, foldername, mediatype, viewtype)
+ totalnodes += 1
+
+ # Update items with new tag
+ items = emby_db.getItem_byView(folderid)
+ for item in items:
+ # Remove the "s" from viewtype for tags
+ kodi_db.updateTag(
+ current_tagid, tagid, item[0], current_viewtype[:-1])
+ else:
+ if mediatype != "music":
+ # Validate the playlist exists or recreate it
+ utils.playlistXSP(mediatype, foldername, viewtype)
+ # Create the video node if not already exists
+ if mediatype != "musicvideos":
+ vnodes.viewNode(totalnodes, foldername, mediatype, viewtype)
+ totalnodes += 1
+ else:
+ # Add video nodes listings
+ vnodes.singleNode(totalnodes, "Favorite movies", "movies", "favourites")
+ totalnodes += 1
+ vnodes.singleNode(totalnodes, "Favorite tvshows", "tvshows", "favourites")
+ totalnodes += 1
+ vnodes.singleNode(totalnodes, "channels", "movies", "channels")
+ totalnodes += 1
+ # Save total
+ utils.window('Emby.nodes.total', str(totalnodes))
+
+
+ def movies(self, embycursor, kodicursor, pdialog, compare=False):
+ # Get movies from emby
+ emby = self.emby
+ emby_db = embydb.Embydb_Functions(embycursor)
+ movies = itemtypes.Movies(embycursor, kodicursor)
+
+ views = emby_db.getView_byType('movies')
+ views += emby_db.getView_byType('mixed')
+ self.logMsg("Media folders: %s" % views, 1)
+
+ if compare:
+ # Pull the list of movies and boxsets in Kodi
+ try:
+ all_kodimovies = dict(emby_db.getChecksum('Movie'))
+ except ValueError:
+ all_kodimovies = {}
+
+ try:
+ all_kodisets = dict(emby_db.getChecksum('BoxSet'))
+ except ValueError:
+ all_kodisets = {}
+
+ all_embymoviesIds = set()
+ all_embyboxsetsIds = set()
+ updatelist = []
+
+ ##### PROCESS MOVIES #####
+ for view in views:
+
+ if self.shouldStop():
+ return False
+
+ # Get items per view
+ viewId = view['id']
+ viewName = view['name']
+
+ if pdialog:
+ pdialog.update(
+ heading="Emby for Kodi",
+ message="Gathering movies from view: %s..." % viewName)
+
+ if compare:
+ # Manual sync
+ if pdialog:
+ pdialog.update(
+ heading="Emby for Kodi",
+ message="Comparing movies from view: %s..." % viewName)
+
+ all_embymovies = emby.getMovies(viewId, basic=True)
+ for embymovie in all_embymovies['Items']:
+
+ if self.shouldStop():
+ return False
+
+ API = api.API(embymovie)
+ itemid = embymovie['Id']
+ all_embymoviesIds.add(itemid)
+
+
+ if all_kodimovies.get(itemid) != API.getChecksum():
+ # Only update if movie is not in Kodi or checksum is different
+ updatelist.append(itemid)
+
+ self.logMsg("Movies to update for %s: %s" % (viewName, updatelist), 1)
+ embymovies = emby.getFullItems(updatelist)
+ total = len(updatelist)
+ del updatelist[:]
+ else:
+ # Initial or repair sync
+ all_embymovies = emby.getMovies(viewId)
+ total = all_embymovies['TotalRecordCount']
+ embymovies = all_embymovies['Items']
+
+
+ if pdialog:
+ pdialog.update(heading="Processing %s / %s items" % (viewName, total))
+
+ count = 0
+ for embymovie in embymovies:
+ # Process individual movies
+ if self.shouldStop():
+ return False
+
+ title = embymovie['Name']
+ if pdialog:
+ percentage = int((float(count) / float(total))*100)
+ pdialog.update(percentage, message=title)
+ count += 1
+ movies.add_update(embymovie, viewName, viewId)
+ else:
+ self.logMsg("Movies finished.", 2)
+
+
+ ##### PROCESS BOXSETS #####
+ if pdialog:
+ pdialog.update(heading="Emby for Kodi", message="Gathering boxsets from server...")
+
+ boxsets = emby.getBoxset()
+
+ if compare:
+ # Manual sync
+ embyboxsets = []
+
+ if pdialog:
+ pdialog.update(
+ heading="Emby for Kodi",
+ message="Comparing boxsets...")
+
+ for boxset in boxsets['Items']:
+
+ if self.shouldStop():
+ return False
+
+ # Boxset has no real userdata, so using etag to compare
+ checksum = boxset['Etag']
+ itemid = boxset['Id']
+ all_embyboxsetsIds.add(itemid)
+
+ if all_kodisets.get(itemid) != checksum:
+ # Only update if boxset is not in Kodi or checksum is different
+ updatelist.append(itemid)
+ embyboxsets.append(boxset)
+
+ self.logMsg("Boxsets to update: %s" % updatelist, 1)
+ total = len(updatelist)
+ else:
+ total = boxsets['TotalRecordCount']
+ embyboxsets = boxsets['Items']
+
+
+ if pdialog:
+ pdialog.update(heading="Processing Boxsets / %s items" % total)
+
+ count = 0
+ for boxset in embyboxsets:
+ # Process individual boxset
+ if self.shouldStop():
+ return False
+
+ title = boxset['Name']
+ if pdialog:
+ percentage = int((float(count) / float(total))*100)
+ pdialog.update(percentage, message=title)
+ count += 1
+ movies.add_updateBoxset(boxset)
+ else:
+ self.logMsg("Boxsets finished.", 2)
+
+
+ ##### PROCESS DELETES #####
+ if compare:
+ # Manual sync, process deletes
+ for kodimovie in all_kodimovies:
+ if kodimovie not in all_embymoviesIds:
+ movies.remove(kodimovie)
+ else:
+ self.logMsg("Movies compare finished.", 1)
+
+ for boxset in all_kodisets:
+ if boxset not in all_embyboxsetsIds:
+ movies.remove(boxset)
+ else:
+ self.logMsg("Boxsets compare finished.", 1)
+
+ return True
+
+ def musicvideos(self, embycursor, kodicursor, pdialog, compare=False):
+ # Get musicvideos from emby
+ emby = self.emby
+ emby_db = embydb.Embydb_Functions(embycursor)
+ mvideos = itemtypes.MusicVideos(embycursor, kodicursor)
+
+ views = emby_db.getView_byType('musicvideos')
+ self.logMsg("Media folders: %s" % views, 1)
+
+ if compare:
+ # Pull the list of musicvideos in Kodi
+ try:
+ all_kodimvideos = dict(emby_db.getChecksum('MusicVideo'))
+ except ValueError:
+ all_kodimvideos = {}
+
+ all_embymvideosIds = set()
+ updatelist = []
+
+ for view in views:
+
+ if self.shouldStop():
+ return False
+
+ # Get items per view
+ viewId = view['id']
+ viewName = view['name']
+
+ if pdialog:
+ pdialog.update(
+ heading="Emby for Kodi",
+ message="Gathering musicvideos from view: %s..." % viewName)
+
+ if compare:
+ # Manual sync
+ if pdialog:
+ pdialog.update(
+ heading="Emby for Kodi",
+ message="Comparing musicvideos from view: %s..." % viewName)
+
+ all_embymvideos = emby.getMusicVideos(viewId, basic=True)
+ for embymvideo in all_embymvideos['Items']:
+
+ if self.shouldStop():
+ return False
+
+ API = api.API(embymvideo)
+ itemid = embymvideo['Id']
+ all_embymvideosIds.add(itemid)
+
+
+ if all_kodimvideos.get(itemid) != API.getChecksum():
+ # Only update if musicvideo is not in Kodi or checksum is different
+ updatelist.append(itemid)
+
+ self.logMsg("MusicVideos to update for %s: %s" % (viewName, updatelist), 1)
+ embymvideos = emby.getFullItems(updatelist)
+ total = len(updatelist)
+ del updatelist[:]
+ else:
+ # Initial or repair sync
+ all_embymvideos = emby.getMusicVideos(viewId)
+ total = all_embymvideos['TotalRecordCount']
+ embymvideos = all_embymvideos['Items']
+
+
+ if pdialog:
+ pdialog.update(heading="Processing %s / %s items" % (viewName, total))
+
+ count = 0
+ for embymvideo in embymvideos:
+ # Process individual musicvideo
+ if self.shouldStop():
+ return False
+
+ title = embymvideo['Name']
+ if pdialog:
+ percentage = int((float(count) / float(total))*100)
+ pdialog.update(percentage, message=title)
+ count += 1
+ mvideos.add_update(embymvideo, viewName, viewId)
+ else:
+ self.logMsg("MusicVideos finished.", 2)
+
+ ##### PROCESS DELETES #####
+ if compare:
+ # Manual sync, process deletes
+ for kodimvideo in all_kodimvideos:
+ if kodimvideo not in all_embymvideosIds:
+ mvideos.remove(kodimvideo)
+ else:
+ self.logMsg("MusicVideos compare finished.", 1)
+
+ return True
+
+ def homevideos(self, embycursor, kodicursor, pdialog, compare=False):
+ # Get homevideos from emby
+ emby = self.emby
+ emby_db = embydb.Embydb_Functions(embycursor)
+ hvideos = itemtypes.HomeVideos(embycursor, kodicursor)
+
+ views = emby_db.getView_byType('homevideos')
+ self.logMsg("Media folders: %s" % views, 1)
+
+ if compare:
+ # Pull the list of homevideos in Kodi
+ try:
+ all_kodihvideos = dict(emby_db.getChecksum('Video'))
+ except ValueError:
+ all_kodihvideos = {}
+
+ all_embyhvideosIds = set()
+ updatelist = []
+
+ for view in views:
+
+ if self.shouldStop():
+ return False
+
+ # Get items per view
+ viewId = view['id']
+ viewName = view['name']
+
+ if pdialog:
+ pdialog.update(
+ heading="Emby for Kodi",
+ message="Gathering homevideos from view: %s..." % viewName)
+
+ all_embyhvideos = emby.getHomeVideos(viewId)
+
+ if compare:
+ # Manual sync
+ if pdialog:
+ pdialog.update(
+ heading="Emby for Kodi",
+ message="Comparing homevideos from view: %s..." % viewName)
+
+ for embyhvideo in all_embyhvideos['Items']:
+
+ if self.shouldStop():
+ return False
+
+ API = api.API(embyhvideo)
+ itemid = embyhvideo['Id']
+ all_embyhvideosIds.add(itemid)
+
+
+ if all_kodihvideos.get(itemid) != API.getChecksum():
+ # Only update if homemovie is not in Kodi or checksum is different
+ updatelist.append(itemid)
+
+ self.logMsg("HomeVideos to update for %s: %s" % (viewName, updatelist), 1)
+ embyhvideos = emby.getFullItems(updatelist)
+ total = len(updatelist)
+ del updatelist[:]
+ else:
+ total = all_embyhvideos['TotalRecordCount']
+ embyhvideos = all_embyhvideos['Items']
+
+ if pdialog:
+ pdialog.update(heading="Processing %s / %s items" % (viewName, total))
+
+ count = 0
+ for embyhvideo in embyhvideos:
+ # Process individual homemovies
+ if self.shouldStop():
+ return False
+
+ title = embyhvideo['Name']
+ if pdialog:
+ percentage = int((float(count) / float(total))*100)
+ pdialog.update(percentage, message=title)
+ count += 1
+ hvideos.add_update(embyhvideo, viewName, viewId)
+ else:
+ self.logMsg("HomeVideos finished.", 2)
+
+ ##### PROCESS DELETES #####
+ if compare:
+ # Manual sync, process deletes
+ for kodihvideo in all_kodihvideos:
+ if kodihvideo not in all_embyhvideosIds:
+ hvideos.remove(kodihvideo)
+ else:
+ self.logMsg("HomeVideos compare finished.", 1)
+
+ return True
+
+ def tvshows(self, embycursor, kodicursor, pdialog, compare=False):
+ # Get shows from emby
+ emby = self.emby
+ emby_db = embydb.Embydb_Functions(embycursor)
+ tvshows = itemtypes.TVShows(embycursor, kodicursor)
+
+ views = emby_db.getView_byType('tvshows')
+ views += emby_db.getView_byType('mixed')
+ self.logMsg("Media folders: %s" % views, 1)
+
+ if compare:
+ # Pull the list of movies and boxsets in Kodi
+ try:
+ all_koditvshows = dict(emby_db.getChecksum('Series'))
+ except ValueError:
+ all_koditvshows = {}
+
+ try:
+ all_kodiepisodes = dict(emby_db.getChecksum('Episode'))
+ except ValueError:
+ all_kodiepisodes = {}
+
+ all_embytvshowsIds = set()
+ all_embyepisodesIds = set()
+ updatelist = []
+
+
+ for view in views:
+
+ if self.shouldStop():
+ return False
+
+ # Get items per view
+ viewId = view['id']
+ viewName = view['name']
+
+ if pdialog:
+ pdialog.update(
+ heading="Emby for Kodi",
+ message="Gathering tvshows from view: %s..." % viewName)
+
+ if compare:
+ # Manual sync
+ if pdialog:
+ pdialog.update(
+ heading="Emby for Kodi",
+ message="Comparing tvshows from view: %s..." % viewName)
+
+ all_embytvshows = emby.getShows(viewId, basic=True)
+ for embytvshow in all_embytvshows['Items']:
+
+ if self.shouldStop():
+ return False
+
+ API = api.API(embytvshow)
+ itemid = embytvshow['Id']
+ all_embytvshowsIds.add(itemid)
+
+
+ if all_koditvshows.get(itemid) != API.getChecksum():
+ # Only update if movie is not in Kodi or checksum is different
+ updatelist.append(itemid)
+
+ self.logMsg("TVShows to update for %s: %s" % (viewName, updatelist), 1)
+ embytvshows = emby.getFullItems(updatelist)
+ total = len(updatelist)
+ del updatelist[:]
+ else:
+ all_embytvshows = emby.getShows(viewId)
+ total = all_embytvshows['TotalRecordCount']
+ embytvshows = all_embytvshows['Items']
+
+
+ if pdialog:
+ pdialog.update(heading="Processing %s / %s items" % (viewName, total))
+
+ count = 0
+ for embytvshow in embytvshows:
+ # Process individual show
+ if self.shouldStop():
+ return False
+
+ itemid = embytvshow['Id']
+ title = embytvshow['Name']
+ if pdialog:
+ percentage = int((float(count) / float(total))*100)
+ pdialog.update(percentage, message=title)
+ count += 1
+ tvshows.add_update(embytvshow, viewName, viewId)
+
+ if not compare:
+ # Process episodes
+ all_episodes = emby.getEpisodesbyShow(itemid)
+ for episode in all_episodes['Items']:
+
+ # Process individual show
+ if self.shouldStop():
+ return False
+
+ episodetitle = episode['Name']
+ if pdialog:
+ pdialog.update(percentage, message="%s - %s" % (title, episodetitle))
+ tvshows.add_updateEpisode(episode)
+ else:
+ if compare:
+ # Get all episodes in view
+ if pdialog:
+ pdialog.update(
+ heading="Emby for Kodi",
+ message="Comparing episodes from view: %s..." % viewName)
+
+ all_embyepisodes = emby.getEpisodes(viewId, basic=True)
+ for embyepisode in all_embyepisodes['Items']:
+
+ if self.shouldStop():
+ return False
+
+ API = api.API(embyepisode)
+ itemid = embyepisode['Id']
+ all_embyepisodesIds.add(itemid)
+
+ if all_kodiepisodes.get(itemid) != API.getChecksum():
+ # Only update if movie is not in Kodi or checksum is different
+ updatelist.append(itemid)
+
+ self.logMsg("Episodes to update for %s: %s" % (viewName, updatelist), 1)
+ embyepisodes = emby.getFullItems(updatelist)
+ total = len(updatelist)
+ del updatelist[:]
+
+ for episode in embyepisodes:
+
+ # Process individual episode
+ if self.shouldStop():
+ return False
+
+ title = episode['SeriesName']
+ episodetitle = episode['Name']
+ if pdialog:
+ pdialog.update(percentage, message="%s - %s" % (title, episodetitle))
+ tvshows.add_updateEpisode(episode)
+ else:
+ self.logMsg("TVShows finished.", 2)
+
+ ##### PROCESS DELETES #####
+ if compare:
+ # Manual sync, process deletes
+ for koditvshow in all_koditvshows:
+ if koditvshow not in all_embytvshowsIds:
+ tvshows.remove(koditvshow)
+ else:
+ self.logMsg("TVShows compare finished.", 1)
+
+ for kodiepisode in all_kodiepisodes:
+ if kodiepisode not in all_embyepisodesIds:
+ tvshows.remove(kodiepisode)
+ else:
+ self.logMsg("Episodes compare finished.", 1)
+
+ return True
+
+ def music(self, embycursor, kodicursor, pdialog, compare=False):
+ # Get music from emby
+ emby = self.emby
+ emby_db = embydb.Embydb_Functions(embycursor)
+ music = itemtypes.Music(embycursor, kodicursor)
+
+ if compare:
+ # Pull the list of movies and boxsets in Kodi
+ try:
+ all_kodiartists = dict(emby_db.getChecksum('MusicArtist'))
+ except ValueError:
+ all_kodiartists = {}
+
+ try:
+ all_kodialbums = dict(emby_db.getChecksum('MusicAlbum'))
+ except ValueError:
+ all_kodialbums = {}
+
+ try:
+ all_kodisongs = dict(emby_db.getChecksum('Audio'))
+ except ValueError:
+ all_kodisongs = {}
+
+ all_embyartistsIds = set()
+ all_embyalbumsIds = set()
+ all_embysongsIds = set()
+ updatelist = []
+
+ process = {
+
+ 'artists': [emby.getArtists, music.add_updateArtist],
+ 'albums': [emby.getAlbums, music.add_updateAlbum],
+ 'songs': [emby.getSongs, music.add_updateSong]
+ }
+ types = ['artists', 'albums', 'songs']
+ for type in types:
+
+ if pdialog:
+ pdialog.update(
+ heading="Emby for Kodi",
+ message="Gathering %s..." % type)
+
+ if compare:
+ # Manual Sync
+ if pdialog:
+ pdialog.update(
+ heading="Emby for Kodi",
+ message="Comparing %s..." % type)
+
+ if type != "artists":
+ all_embyitems = process[type][0](basic=True)
+ else:
+ all_embyitems = process[type][0]()
+ for embyitem in all_embyitems['Items']:
+
+ if self.shouldStop():
+ return False
+
+ API = api.API(embyitem)
+ itemid = embyitem['Id']
+ if type == "artists":
+ all_embyartistsIds.add(itemid)
+ if all_kodiartists.get(itemid) != API.getChecksum():
+ # Only update if artist is not in Kodi or checksum is different
+ updatelist.append(itemid)
+ elif type == "albums":
+ all_embyalbumsIds.add(itemid)
+ if all_kodialbums.get(itemid) != API.getChecksum():
+ # Only update if album is not in Kodi or checksum is different
+ updatelist.append(itemid)
+ else:
+ all_embysongsIds.add(itemid)
+ if all_kodisongs.get(itemid) != API.getChecksum():
+ # Only update if songs is not in Kodi or checksum is different
+ updatelist.append(itemid)
+
+ self.logMsg("%s to update: %s" % (type, updatelist), 1)
+ embyitems = emby.getFullItems(updatelist)
+ total = len(updatelist)
+ del updatelist[:]
+ else:
+ all_embyitems = process[type][0]()
+ total = all_embyitems['TotalRecordCount']
+ embyitems = all_embyitems['Items']
+
+ if pdialog:
+ pdialog.update(heading="Processing %s / %s items" % (type, total))
+
+ count = 0
+ for embyitem in embyitems:
+ # Process individual item
+ if self.shouldStop():
+ return False
+
+ title = embyitem['Name']
+ if pdialog:
+ percentage = int((float(count) / float(total))*100)
+ pdialog.update(percentage, message=title)
+ count += 1
+
+ process[type][1](embyitem)
+ else:
+ self.logMsg("%s finished." % type, 2)
+
+ ##### PROCESS DELETES #####
+ if compare:
+ # Manual sync, process deletes
+ for kodiartist in all_kodiartists:
+ if kodiartist not in all_embyartistsIds and all_kodiartists[kodiartist] is not None:
+ music.remove(kodiartist)
+ else:
+ self.logMsg("Artist compare finished.", 1)
+
+ for kodialbum in all_kodialbums:
+ if kodialbum not in all_embyalbumsIds:
+ music.remove(kodialbum)
+ else:
+ self.logMsg("Albums compare finished.", 1)
+
+ for kodisong in all_kodisongs:
+ if kodisong not in all_embysongsIds:
+ music.remove(kodisong)
+ else:
+ self.logMsg("Songs compare finished.", 1)
+
+ return True
+
+ # Reserved for websocket_client.py and fast start
+ def triage_items(self, process, items):
+
+ processlist = {
+
+ 'added': self.addedItems,
+ 'update': self.updateItems,
+ 'userdata': self.userdataItems,
+ 'remove': self.removeItems
+ }
+ if items:
+ if process == "userdata":
+ itemids = []
+ for item in items:
+ itemids.append(item['ItemId'])
+ items = itemids
+
+ self.logMsg("Queue %s: %s" % (process, items), 1)
+ processlist[process].extend(items)
+
+ def incrementalSync(self):
+
+ embyconn = utils.kodiSQL('emby')
+ embycursor = embyconn.cursor()
+ kodiconn = utils.kodiSQL('video')
+ kodicursor = kodiconn.cursor()
+ emby = self.emby
+ emby_db = embydb.Embydb_Functions(embycursor)
+ pDialog = None
+
+ if self.refresh_views:
+ # Received userconfig update
+ self.refresh_views = False
+ self.maintainViews(embycursor, kodicursor)
+ self.forceLibraryUpdate = True
+
+ if self.addedItems or self.updateItems or self.userdataItems or self.removeItems:
+ # Only present dialog if we are going to process items
+ pDialog = self.progressDialog('Incremental sync')
+
+
+ process = {
+
+ 'added': self.addedItems,
+ 'update': self.updateItems,
+ 'userdata': self.userdataItems,
+ 'remove': self.removeItems
+ }
+ types = ['added', 'update', 'userdata', 'remove']
+ for type in types:
+
+ if process[type] and utils.window('emby_kodiScan') != "true":
+
+ listItems = list(process[type])
+ del process[type][:] # Reset class list
+
+ items_process = itemtypes.Items(embycursor, kodicursor)
+ update = False
+
+ # Prepare items according to process type
+ if type == "added":
+ items = emby.sortby_mediatype(listItems)
+
+ elif type in ("userdata", "remove"):
+ items = emby_db.sortby_mediaType(listItems, unsorted=False)
+
+ else:
+ items = emby_db.sortby_mediaType(listItems)
+ if items.get('Unsorted'):
+ sorted_items = emby.sortby_mediatype(items['Unsorted'])
+ doupdate = items_process.itemsbyId(sorted_items, "added", pDialog)
+ if doupdate:
+ update = True
+ del items['Unsorted']
+
+ doupdate = items_process.itemsbyId(items, type, pDialog)
+ if doupdate:
+ update = True
+
+ if update:
+ self.forceLibraryUpdate = True
+
+
+ if self.forceLibraryUpdate:
+ # Force update the Kodi library
+ self.forceLibraryUpdate = False
+ self.dbCommit(kodiconn)
+ embyconn.commit()
+ self.saveLastSync()
+
+ # tell any widgets to refresh because the content has changed
+ utils.window('widgetreload', value=datetime.now().strftime('%Y-%m-%d %H:%M:%S'))
+
+ self.logMsg("Updating video library.", 1)
+ utils.window('emby_kodiScan', value="true")
+ xbmc.executebuiltin('UpdateLibrary(video)')
+
+ if pDialog:
+ pDialog.close()
+
+ kodicursor.close()
+ embycursor.close()
+
+
+ def compareDBVersion(self, current, minimum):
+ # It returns True is database is up to date. False otherwise.
+ self.logMsg("current: %s minimum: %s" % (current, minimum), 1)
+ currMajor, currMinor, currPatch = current.split(".")
+ minMajor, minMinor, minPatch = minimum.split(".")
+
if currMajor > minMajor:
return True
- elif currMajor == minMajor and currMinor > minMinor:
- return True
- elif currMajor == minMajor and currMinor == minMinor and currPatch >= minPatch:
+ elif currMajor == minMajor and (currMinor > minMinor or
+ (currMinor == minMinor and currPatch >= minPatch)):
return True
else:
+ # Database out of date.
return False
def run(self):
@@ -1058,134 +1211,128 @@ class LibrarySync(threading.Thread):
try:
self.run_internal()
except Exception as e:
- xbmcgui.Dialog().ok("Emby for Kodi", "Library sync thread has crashed!", "You will need to restart Kodi.", "Please report this on the forum, we will need your log.")
+ xbmcgui.Dialog().ok(
+ heading="Emby for Kodi",
+ line1=(
+ "Library sync thread has exited! "
+ "You should restart Kodi now. "
+ "Please report this on the forum."))
raise
def run_internal(self):
startupComplete = False
- kodiProfile = xbmc.translatePath("special://profile")
+ monitor = self.monitor
- self.logMsg("--- Starting Library Sync Thread ---", 0)
+ self.logMsg("---===### Starting LibrarySync ###===---", 0)
- while not self.KodiMonitor.abortRequested():
+ while not monitor.abortRequested():
- # In the event the server goes offline after
- # the thread has already been started.
- while self.suspendClient == True:
- # The service.py will change self.suspendClient to False
- if self.KodiMonitor.waitForAbort(5):
+ # In the event the server goes offline
+ while self.suspend_thread:
+ # Set in service.py
+ if monitor.waitForAbort(5):
# Abort was requested while waiting. We should exit
break
- # Check if the version of Emby for Kodi the DB was created with is recent enough - controled by Window property set at top of service _INIT_
-
- # START TEMPORARY CODE
- # Only get in here for a while, can be removed later
- if utils.settings("dbCreatedWithVersion")=="" and utils.settings("SyncInstallRunDone") == "true":
- self.logMsg("Unknown DB version", 0)
- return_value = xbmcgui.Dialog().yesno("DB Version", "Can't detect version of Emby for Kodi the DB was created with.\nWas it at least version " + utils.window('minDBVersion') + "?")
- if return_value == 0:
- utils.settings("dbCreatedWithVersion","0.0.0")
- self.logMsg("DB version out of date according to user", 0)
- else:
- utils.settings("dbCreatedWithVersion", utils.window('minDBVersion'))
- self.logMsg("DB version okay according to user", 0)
- # END TEMPORARY CODE
-
- if (utils.settings("SyncInstallRunDone") == "true" and self.checkDBVersion(utils.settings("dbCreatedWithVersion"), utils.window('minDBVersion'))==False and utils.window('minDBVersionCheck') != "true"):
- self.logMsg("DB version out of date according to check", 0)
- return_value = xbmcgui.Dialog().yesno("DB Version", "Detected the DB needs to be recreated for\nthis version of Emby for Kodi.\nProceed?")
- if return_value == 0:
- self.logMsg("DB version out of date !!! USER IGNORED !!!", 0)
- xbmcgui.Dialog().ok("Emby for Kodi","Emby for Kodi may not work\ncorrectly until the database is reset.\n")
- utils.window('minDBVersionCheck', value="true")
- else:
- utils.reset()
-
- # Library sync
- if not startupComplete:
+ if (utils.window('emby_dbCheck') != "true" and
+ utils.settings('SyncInstallRunDone') == "true"):
- # Verify the database for videos
- videodb = utils.getKodiVideoDBPath()
- if not xbmcvfs.exists(videodb):
- # Database does not exists.
- self.logMsg("The current Kodi version is incompatible with the Emby for Kodi add-on. Please visit here, to see currently supported Kodi versions: https://github.com/MediaBrowser/Emby.Kodi/wiki", 0)
- xbmcgui.Dialog().ok("Emby Warning", "Cancelling the database syncing process. Current Kodi version: %s is unsupported. Please verify your logs for more info." % xbmc.getInfoLabel('System.BuildVersion'))
+ # Verify the validity of the database
+ currentVersion = utils.settings('dbCreatedWithVersion')
+ minVersion = utils.window('emby_minDBVersion')
+ uptoDate = self.compareDBVersion(currentVersion, minVersion)
+
+ if not uptoDate:
+ self.logMsg(
+ "Db version out of date: %s minimum version required: %s"
+ % (currentVersion, minVersion), 0)
+
+ resp = xbmcgui.Dialog().yesno(
+ heading="Db Version",
+ line1=(
+ "Detected the database needs to be "
+ "recreated for this version of Emby for Kodi. "
+ "Proceed?"))
+ if not resp:
+ self.logMsg("Db version out of date! USER IGNORED!", 0)
+ xbmcgui.Dialog().ok(
+ heading="Emby for Kodi",
+ line1=(
+ "Emby for Kodi may not work correctly "
+ "until the database is reset."))
+ else:
+ utils.reset()
+
+ utils.window('emby_dbCheck', value="true")
+
+
+ if not startupComplete:
+ # Verify the video database can be found
+ videoDb = utils.getKodiVideoDBPath()
+ if not xbmcvfs.exists(videoDb):
+ # Database does not exists
+ self.logMsg(
+ "The current Kodi version is incompatible "
+ "with the Emby for Kodi add-on. Please visit "
+ "https://github.com/MediaBrowser/Emby.Kodi/wiki "
+ "to know which Kodi versions are supported.", 0)
+
+ xbmcgui.Dialog().ok(
+ heading="Emby Warning",
+ line1=(
+ "Cancelling the database syncing process. "
+ "Current Kodi versoin: %s is unsupported. "
+ "Please verify your logs for more info."
+ % xbmc.getInfoLabel('System.BuildVersion')))
break
- # Run full sync
- self.logMsg("DB Version: " + utils.settings("dbCreatedWithVersion"), 0)
- self.logMsg("Doing_Db_Sync: syncDatabase (Started)", 1)
+ # Run start up sync
+ self.logMsg("Db version: %s" % utils.settings('dbCreatedWithVersion'), 0)
+ self.logMsg("SyncDatabase (started)", 1)
startTime = datetime.now()
- libSync = self.FullLibrarySync()
+ librarySync = self.startSync()
elapsedTime = datetime.now() - startTime
- self.logMsg("Doing_Db_Sync: syncDatabase (Finished in: %s) %s" % (str(elapsedTime).split('.')[0], libSync), 1)
+ self.logMsg(
+ "SyncDatabase (finished in: %s) %s"
+ % (str(elapsedTime).split('.')[0], librarySync), 1)
+ # Only try the initial sync once per kodi session regardless
+ # This will prevent an infinite loop in case something goes wrong.
+ startupComplete = True
- if libSync:
- startupComplete = True
+ # Process updates
+ if utils.window('emby_dbScan') != "true":
+ self.incrementalSync()
- # Set via Kodi Monitor event
- if utils.window('OnWakeSync') == "true" and utils.window('Server_online') == "true":
- utils.window("OnWakeSync", clear=True)
- if utils.window("SyncDatabaseRunning") != "true":
- self.logMsg("Doing_Db_Sync Post Resume: syncDatabase (Started)", 0)
- libSync = self.FullLibrarySync()
- self.logMsg("Doing_Db_Sync Post Resume: syncDatabase (Finished) " + str(libSync), 0)
+ if (utils.window('emby_onWake') == "true" and
+ utils.window('emby_online') == "true"):
+ # Kodi is waking up
+ # Set in kodimonitor.py
+ utils.window('emby_onWake', clear=True)
+ if utils.window('emby_syncRunning') != "true":
+ self.logMsg("SyncDatabase onWake (started)", 0)
+ librarySync = self.startSync()
+ self.logMsg("SyncDatabase onWake (finished) %s", librarySync, 0)
-
- doSaveLastSync = False
-
- if len(self.updateItems) > 0 and utils.window('kodiScan') != "true":
- # Add or update items
- self.logMsg("Processing items: %s" % (str(self.updateItems)), 1)
- listItems = self.updateItems
- self.updateItems = []
- self.IncrementalSync(listItems)
- self.forceUpdate = True
- doSaveLastSync = True
-
- if len(self.userdataItems) > 0 and utils.window('kodiScan') != "true":
- # Process userdata changes only
- self.logMsg("Processing items: %s" % (str(self.userdataItems)), 1)
- listItems = self.userdataItems
- self.userdataItems = []
- self.setUserdata(listItems)
- self.forceUpdate = True
- doSaveLastSync = True
-
- if len(self.removeItems) > 0 and utils.window('kodiScan') != "true":
- # Remove item from Kodi library
- self.logMsg("Removing items: %s" % self.removeItems, 1)
- listItems = self.removeItems
- self.removeItems = []
- self.removefromDB(listItems)
- self.forceUpdate = True
- doSaveLastSync = True
-
- if doSaveLastSync == True:
- self.SaveLastSync()
-
- if self.forceUpdate and not self.updateItems and not self.userdataItems and not self.removeItems:
- # Force update Kodi library
- self.forceUpdate = False
- self.updateLibrary("video")
-
- if utils.window("kodiProfile_emby") != kodiProfile:
- # Profile change happened, terminate this thread
- self.logMsg("Kodi profile was: %s and changed to: %s. Terminating Library thread." % (kodiProfile, utils.window("kodiProfile_emby")), 1)
+ if self.stop_thread:
+ # Set in service.py
+ self.logMsg("Service terminated thread.", 2)
break
- if self.KodiMonitor.waitForAbort(1):
+ if monitor.waitForAbort(1):
# Abort was requested while waiting. We should exit
break
- self.logMsg("--- Library Sync Thread stopped ---", 0)
+ self.logMsg("###===--- LibrarySync Stopped ---===###", 0)
- def suspendClient(self):
- self.suspendClient = True
- self.logMsg("--- Library Sync Thread paused ---", 0)
+ def stopThread(self):
+ self.stop_thread = True
+ self.logMsg("Ending thread...", 2)
- def resumeClient(self):
- self.suspendClient = False
- self.logMsg("--- Library Sync Thread resumed ---", 0)
\ No newline at end of file
+ def suspendThread(self):
+ self.suspend_thread = True
+ self.logMsg("Pausing thread...", 0)
+
+ def resumeThread(self):
+ self.suspend_thread = False
+ self.logMsg("Resuming thread...", 0)
\ No newline at end of file
diff --git a/resources/lib/PlayUtils.py b/resources/lib/PlayUtils.py
index e8b9be58..0a74690b 100644
--- a/resources/lib/PlayUtils.py
+++ b/resources/lib/PlayUtils.py
@@ -6,227 +6,279 @@ import xbmc
import xbmcgui
import xbmcvfs
-from ClientInformation import ClientInformation
-import Utils as utils
+import clientinfo
+import utils
#################################################################################################
-class PlayUtils():
- clientInfo = ClientInformation()
- addonName = clientInfo.getAddonName()
+class PlayUtils():
+
+
+ def __init__(self, item):
+
+ self.item = item
+
+ self.clientInfo = clientinfo.ClientInfo()
+ self.addonName = self.clientInfo.getAddonName()
+
+ self.userid = utils.window('emby_currUser')
+ self.server = utils.window('emby_server%s' % self.userid)
def logMsg(self, msg, lvl=1):
-
- className = self.__class__.__name__
- utils.logMsg("%s %s" % (self.addonName, className), msg, int(lvl))
- def getPlayUrl(self, server, id, result):
+ self.className = self.__class__.__name__
+ utils.logMsg("%s %s" % (self.addonName, self.className), msg, lvl)
+
- 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")
+ def getPlayUrl(self):
- 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")
+ item = self.item
+ playurl = None
- 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
+ if item['MediaSources'][0]['Protocol'] == "Http":
+ # Only play as http
+ self.logMsg("File protocol is http.", 1)
+ playurl = self.httpPlay()
+ utils.window('emby_%s.playmethod' % playurl, value="DirectStream")
- return playurl.encode('utf-8')
+ elif self.isDirectPlay():
+ self.logMsg("File is direct playing.", 1)
+ playurl = self.directPlay()
+ playurl = playurl.encode('utf-8')
+ # Set playmethod property
+ utils.window('emby_%s.playmethod' % playurl, value="DirectPlay")
- def isDirectPlay(self, result, dialog = False):
- # Requirements for Direct play:
- # FileSystem, Accessible path
+ elif self.isDirectStream():
+
+ self.logMsg("File is direct streaming.", 1)
+ playurl = self.directStream()
+ # Set playmethod property
+ utils.window('emby_%s.playmethod' % playurl, value="DirectStream")
+
+ elif self.isTranscoding():
+
+ self.logMsg("File is transcoding.", 1)
+ playurl = self.transcoding()
+ # Set playmethod property
+ utils.window('emby_%s.playmethod' % playurl, value="Transcode")
+
+ return playurl
+
+ def httpPlay(self):
+ # Audio, Video, Photo
+ item = self.item
+ server = self.server
+
+ itemid = item['Id']
+ mediatype = item['MediaType']
+
+ if type == "Audio":
+ playurl = "%s/emby/Audio/%s/stream" % (server, itemid)
+ else:
+ playurl = "%s/emby/Videos/%s/stream?static=true" % (server, itemid)
+
+ return playurl
+
+ def isDirectPlay(self):
+
+ item = self.item
+
+ # Requirement: 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)
+ # User forcing to play via HTTP
+ self.logMsg("Can't direct play, play from HTTP enabled.", 1)
return False
- # Avoid H265 1080p
if (utils.settings('transcodeH265') == "true" and
- result['MediaSources'][0]['Name'].startswith("1080P/H265")):
+ result['MediaSources'][0]['Name'].startswith("1080P/H265")):
+ # Avoid H265 1080p
self.logMsg("Option to transcode 1080P/H265 enabled.", 1)
return False
- canDirectPlay = result['MediaSources'][0]['SupportsDirectPlay']
- # Make sure it's supported by server
+ canDirectPlay = item['MediaSources'][0]['SupportsDirectPlay']
+ # Make sure direct play is supported by the server
if not canDirectPlay:
- self.logMsg("Can't direct play: Server does not allow or support it.", 1)
+ self.logMsg("Can't direct play, server doesn't allow/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)
+ location = item['LocationType']
+ if location == "FileSystem":
+ # Verify the path
+ if not self.fileExists():
+ self.logMsg("Unable to direct play.")
+ try:
+ count = int(utils.settings('failCount'))
+ except ValueError:
+ count = 0
+ self.logMsg("Direct play failed: %s times." % count, 1)
+ if count < 2:
+ # Let the user know that direct play failed
+ utils.settings('failCount', value=str(count+1))
+ xbmcgui.Dialog().notification(
+ heading="Emby server",
+ message="Unable to direct play.",
+ 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")
+ utils.settings('failCount', value="0")
+ xbmcgui.Dialog().notification(
+ heading="Emby server",
+ message=("Direct play failed 3 times. Enabled play "
+ "from HTTP in the add-on settings."),
+ icon="special://home/addons/plugin.video.emby/icon.png",
+ sound=False)
return False
- def directPlay(self, result):
+ return True
+
+ def directPlay(self):
+
+ item = self.item
try:
- playurl = result['MediaSources'][0]['Path']
- except KeyError:
- playurl = result['Path']
+ playurl = item['MediaSources'][0]['Path']
+ except (IndexError, KeyError):
+ playurl = item['Path']
- if 'VideoType' in result:
+ if item.get('VideoType'):
# Specific format modification
- if 'Dvd' in result['VideoType']:
+ type = item['VideoType']
+
+ if type == "Dvd":
playurl = "%s/VIDEO_TS/VIDEO_TS.IFO" % playurl
- elif 'BluRay' in result['VideoType']:
+ elif type == "Bluray":
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://")
+ # Assign network protocol
+ if playurl.startswith('\\\\'):
+ playurl = playurl.replace("\\\\", "smb://")
playurl = playurl.replace("\\", "/")
-
+
if "apple.com" in playurl:
USER_AGENT = "QuickTime/7.7.4"
playurl += "?|User-Agent=%s" % USER_AGENT
return playurl
+ def fileExists(self):
- def isDirectStream(self, result):
- # Requirements for Direct stream:
- # FileSystem or Remote, BitRate, supported encoding
+ if 'Path' not in self.item:
+ # File has no path defined in server
+ return False
+
+ # Convert path to direct play
+ path = self.directPlay()
+ self.logMsg("Verifying path: %s" % path, 1)
+
+ if xbmcvfs.exists(path):
+ self.logMsg("Path exists.", 1)
+ return True
+
+ elif ":" not in path:
+ self.logMsg("Can't verify path, assumed linux. Still try to direct play.", 1)
+ return True
+
+ else:
+ self.logMsg("Failed to find file.")
+ return False
+
+ def isDirectStream(self):
+
+ item = self.item
- # Avoid H265 1080p
if (utils.settings('transcodeH265') == "true" and
- result['MediaSources'][0]['Name'].startswith("1080P/H265")):
+ result['MediaSources'][0]['Name'].startswith("1080P/H265")):
+ # Avoid H265 1080p
self.logMsg("Option to transcode 1080P/H265 enabled.", 1)
return False
- canDirectStream = result['MediaSources'][0]['SupportsDirectStream']
- # Make sure it's supported by server
+ # Requirement: BitRate, supported encoding
+ canDirectStream = item['MediaSources'][0]['SupportsDirectStream']
+ # Make sure the server supports it
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)
+ # Verify the bitrate
+ if not self.isNetworkSufficient():
+ self.logMsg("The network speed is insufficient to direct stream file.", 1)
return False
return True
-
- def directStream(self, result, server, id, type = "Video"):
- if result['Path'].endswith('.strm'):
+ def directStream(self):
+
+ item = self.item
+ server = self.server
+
+ itemid = item['Id']
+ type = item['Type']
+
+ if 'Path' in item and item['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)
+ playurl = self.directPlay()
+ elif type == "Audio":
+ playurl = "%s/emby/Audio/%s/stream.mp3" % (server, itemid)
+ else:
+ playurl = "%s/emby/Videos/%s/stream?static=true" % (server, itemid)
- 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 isNetworkSufficient(self):
- def isTranscoding(self, result):
- # Last resort, no requirements
- # BitRate
- canTranscode = result['MediaSources'][0]['SupportsTranscoding']
- # Make sure it's supported by server
+ settings = self.getBitrate()*1000
+
+ try:
+ sourceBitrate = int(self.item['MediaSources'][0]['Bitrate'])
+ except (KeyError, TypeError):
+ self.logMsg("Bitrate value is missing.", 1)
+ else:
+ self.logMsg("The add-on settings bitrate is: %s, the video bitrate required is: %s"
+ % (settings, sourceBitrate), 1)
+ if settings < sourceBitrate:
+ return False
+
+ return True
+
+ def isTranscoding(self):
+
+ item = self.item
+
+ canTranscode = item['MediaSources'][0]['SupportsTranscoding']
+ # Make sure the server supports it
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):
+ def transcoding(self):
- if result['Path'].endswith('.strm'):
+ item = self.item
+
+ if 'Path' in item and item['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)
+ playurl = self.directPlay()
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):
+ itemid = item['Id']
+ deviceId = self.clientInfo.getDeviceId()
+ playurl = (
+ "%s/emby/Videos/%s/master.m3u8?MediaSourceId=%s"
+ % (self.server, itemid, itemid)
+ )
+ playurl = (
+ "%s&VideoCodec=h264&AudioCodec=ac3&MaxAudioChannels=6&deviceId=%s&VideoBitrate=%s"
+ % (playurl, deviceId, self.getBitrate()*1000))
+
+ return playurl
+
+ def getBitrate(self):
+
# get the addon video quality
- videoQuality = utils.settings('videoBitRate')
+ videoQuality = utils.settings('videoBitrate')
bitrate = {
'0': 664,
@@ -252,35 +304,8 @@ class PlayUtils():
# 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):
+ def audioSubsPref(self, url):
# For transcoding only
# Present the list of audio to select from
audioStreamsList = {}
@@ -292,15 +317,21 @@ class PlayUtils():
selectSubsIndex = ""
playurlprefs = "%s" % url
- mediaStream = mediaSources[0].get('MediaStreams')
- for stream in mediaStream:
+ item = self.item
+ try:
+ mediasources = item['MediaSources'][0]
+ mediastreams = mediasources['MediaStreams']
+ except (TypeError, KeyError, IndexError):
+ return
+
+ for stream in mediastreams:
# Since Emby returns all possible tracks together, have to sort them.
index = stream['Index']
type = stream['Type']
if 'Audio' in type:
codec = stream['Codec']
- channelLayout = stream['ChannelLayout']
+ channelLayout = stream.get('ChannelLayout', "")
try:
track = "%s - %s - %s %s" % (index, stream['Language'], codec, channelLayout)
@@ -312,6 +343,8 @@ class PlayUtils():
audioStreams.append(track)
elif 'Subtitle' in type:
+ if stream['IsExternal']:
+ continue
try:
track = "%s - %s" % (index, stream['Language'])
except:
@@ -336,7 +369,7 @@ class PlayUtils():
selectAudioIndex = audioStreamsList[selected]
playurlprefs += "&AudioStreamIndex=%s" % selectAudioIndex
else: # User backed out of selection
- playurlprefs += "&AudioStreamIndex=%s" % mediaSources[0]['DefaultAudioStreamIndex']
+ playurlprefs += "&AudioStreamIndex=%s" % mediasources['DefaultAudioStreamIndex']
else: # There's only one audiotrack.
selectAudioIndex = audioStreamsList[audioStreams[0]]
playurlprefs += "&AudioStreamIndex=%s" % selectAudioIndex
@@ -352,7 +385,7 @@ class PlayUtils():
selectSubsIndex = subtitleStreamsList[selected]
playurlprefs += "&SubtitleStreamIndex=%s" % selectSubsIndex
else: # User backed out of selection
- playurlprefs += "&SubtitleStreamIndex=%s" % mediaSources[0].get('DefaultSubtitleStreamIndex', "")
+ playurlprefs += "&SubtitleStreamIndex=%s" % mediasources.get('DefaultSubtitleStreamIndex', "")
# Get number of channels for selected audio track
audioChannels = audioStreamsChannelsList.get(selectAudioIndex, 0)
diff --git a/resources/lib/PlaybackUtils.py b/resources/lib/PlaybackUtils.py
index 1172e52b..affa2b81 100644
--- a/resources/lib/PlaybackUtils.py
+++ b/resources/lib/PlaybackUtils.py
@@ -2,71 +2,69 @@
#################################################################################################
-import datetime
-import json as json
+import json
import sys
import xbmc
-import xbmcaddon
-import xbmcplugin
import xbmcgui
+import xbmcplugin
-from API import API
-from DownloadUtils import DownloadUtils
-from PlayUtils import PlayUtils
-from ClientInformation import ClientInformation
-import Utils as utils
+import api
+import artwork
+import clientinfo
+import downloadutils
+import playutils as putils
+import playlist
+import read_embyserver as embyserver
+import utils
#################################################################################################
+
class PlaybackUtils():
- clientInfo = ClientInformation()
- doUtils = DownloadUtils()
- api = API()
+
+ def __init__(self, item):
- addon = xbmcaddon.Addon()
- language = addon.getLocalizedString
- addonName = clientInfo.getAddonName()
+ self.item = item
+ self.API = api.API(self.item)
+
+ self.clientInfo = clientinfo.ClientInfo()
+ self.addonName = self.clientInfo.getAddonName()
+ self.doUtils = downloadutils.DownloadUtils()
+
+ self.userid = utils.window('emby_currUser')
+ self.server = utils.window('emby_server%s' % self.userid)
+
+ self.artwork = artwork.Artwork()
+ self.emby = embyserver.Read_EmbyServer()
+ self.pl = playlist.Playlist()
def logMsg(self, msg, lvl=1):
-
- className = self.__class__.__name__
- utils.logMsg("%s %s" % (self.addonName, className), msg, int(lvl))
- def PLAY(self, result, setup = "service"):
+ self.className = self.__class__.__name__
+ utils.logMsg("%s %s" % (self.addonName, self.className), msg, lvl)
- self.logMsg("PLAY Called", 1)
- api = self.api
+ def play(self, itemid, dbid=None):
+
+ self.logMsg("Play called.", 1)
+
doUtils = self.doUtils
- username = utils.window('currUser')
- server = utils.window('server%s' % username)
+ item = self.item
+ API = self.API
+ listitem = xbmcgui.ListItem()
+ playutils = putils.PlayUtils(item)
- id = result['Id']
- userdata = result['UserData']
- # Get the playurl - direct play, direct stream or transcoding
- playurl = PlayUtils().getPlayUrl(server, id, result)
- listItem = xbmcgui.ListItem()
+ playurl = playutils.getPlayUrl()
+ if not playurl:
+ return xbmcplugin.setResolvedUrl(int(sys.argv[1]), False, listitem)
- if utils.window('playurlFalse') == "true":
- # Playurl failed - set in PlayUtils.py
- utils.window('playurlFalse', clear=True)
- self.logMsg("Failed to retrieve the playback path/url or dialog was cancelled.", 1)
- return xbmcplugin.setResolvedUrl(int(sys.argv[1]), False, listItem)
-
-
- ############### -- SETUP MAIN ITEM ################
-
- # Set listitem and properties for main item
- self.logMsg("Returned playurl: %s" % playurl, 1)
- listItem.setPath(playurl)
- self.setProperties(playurl, result, listItem)
-
- mainArt = API().getArtwork(result, "Primary")
- listItem.setThumbnailImage(mainArt)
- listItem.setIconImage(mainArt)
-
+ if dbid is None:
+ # Item is not in Kodi database
+ listitem.setPath(playurl)
+ self.setProperties(playurl, listitem)
+ return xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, listitem)
############### ORGANIZE CURRENT PLAYLIST ################
@@ -74,58 +72,45 @@ class PlaybackUtils():
playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
startPos = max(playlist.getposition(), 0) # Can return -1
sizePlaylist = playlist.size()
-
- propertiesPlayback = utils.window('propertiesPlayback') == "true"
- introsPlaylist = False
- dummyPlaylist = False
currentPosition = startPos
- self.logMsg("Playlist start position: %s" % startPos, 2)
- self.logMsg("Playlist plugin position: %s" % currentPosition, 2)
- self.logMsg("Playlist size: %s" % sizePlaylist, 2)
+ propertiesPlayback = utils.window('emby_playbackProps', windowid=10101) == "true"
+ introsPlaylist = False
+ dummyPlaylist = False
+ self.logMsg("Playlist start position: %s" % startPos, 1)
+ self.logMsg("Playlist plugin position: %s" % currentPosition, 1)
+ self.logMsg("Playlist size: %s" % sizePlaylist, 1)
############### RESUME POINT ################
- # Resume point for widget only
- timeInfo = api.getTimeInfo(result)
- jumpBackSec = int(utils.settings('resumeJumpBack'))
- seekTime = round(float(timeInfo.get('ResumeTime')), 6)
- if seekTime > jumpBackSec:
- # To avoid negative bookmark
- seekTime = seekTime - jumpBackSec
-
- # Show the additional resume dialog if launched from a widget
- if homeScreen and seekTime:
- # Dialog presentation
- displayTime = str(datetime.timedelta(seconds=(int(seekTime))))
- display_list = ["%s %s" % (self.language(30106), displayTime), self.language(30107)]
- resume_result = xbmcgui.Dialog().select(self.language(30105), display_list)
-
- if resume_result == 0:
- # User selected to resume, append resume point to listitem
- listItem.setProperty('StartOffset', str(seekTime))
-
- elif resume_result > 0:
- # User selected to start from beginning
- seekTime = 0
-
- else: # User cancelled the dialog
- self.logMsg("User cancelled resume dialog.", 1)
- return xbmcplugin.setResolvedUrl(int(sys.argv[1]), False, listItem)
+ userdata = API.getUserData()
+ seektime = API.adjustResume(userdata['Resume'])
# We need to ensure we add the intro and additional parts only once.
# Otherwise we get a loop.
if not propertiesPlayback:
- utils.window('propertiesPlayback', value="true")
- self.logMsg("Setting up properties in playlist.")
+ utils.window('emby_playbackProps', value="true", windowid=10101)
+ self.logMsg("Setting up properties in playlist.", 1)
+
+ if (not homeScreen and not seektime and
+ utils.window('emby_customPlaylist', windowid=10101) != "true"):
+
+ self.logMsg("Adding dummy file to playlist.", 2)
+ dummyPlaylist = True
+ playlist.add(playurl, listitem, index=startPos)
+ # Remove the original item from playlist
+ self.pl.removefromPlaylist(startPos+1)
+ # Readd the original item to playlist - via jsonrpc so we have full metadata
+ self.pl.insertintoPlaylist(currentPosition+1, dbid, item['Type'].lower())
+ currentPosition += 1
############### -- CHECK FOR INTROS ################
- if utils.settings('disableCinema') == "false" and not seekTime:
+ if utils.settings('enableCinema') == "true" 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
+ url = "{server}/emby/Users/{UserId}/Items/%s/Intros?format=json" % itemid
intros = doUtils.downloadUrl(url)
if intros['TotalRecordCount'] != 0:
@@ -141,17 +126,15 @@ class PlaybackUtils():
if getTrailers:
for intro in intros['Items']:
# The server randomly returns intros, process them.
- introId = intro['Id']
-
- introPlayurl = PlayUtils().getPlayUrl(server, introId, intro)
introListItem = xbmcgui.ListItem()
+ introPlayurl = putils.PlayUtils(intro).getPlayUrl()
self.logMsg("Adding Intro: %s" % introPlayurl, 1)
# Set listitem and properties for intros
- self.setProperties(introPlayurl, intro, introListItem)
- self.setListItemProps(server, introId, introListItem, intro)
-
- playlist.add(introPlayurl, introListItem, index=currentPosition)
+ pbutils = PlaybackUtils(intro)
+ pbutils.setProperties(introPlayurl, introListItem)
+
+ self.pl.insertintoPlaylist(currentPosition, url=introPlayurl)
introsPlaylist = True
currentPosition += 1
@@ -159,109 +142,126 @@ class PlaybackUtils():
############### -- ADD MAIN ITEM ONLY FOR HOMESCREEN ###############
if homeScreen and not sizePlaylist:
- # Extend our current playlist with the actual item to play only if there's no playlist first
+ # Extend our current playlist with the actual item to play
+ # only if there's no playlist first
self.logMsg("Adding main item to playlist.", 1)
- self.setListItemProps(server, id, listItem, result)
- playlist.add(playurl, listItem, index=currentPosition)
-
+ self.pl.addtoPlaylist(dbid, item['Type'].lower())
+
# Ensure that additional parts are played after the main item
currentPosition += 1
-
############### -- CHECK FOR ADDITIONAL PARTS ################
- if result.get('PartCount'):
+ if item.get('PartCount'):
# Only add to the playlist after intros have played
- partcount = result['PartCount']
- url = "{server}/mediabrowser/Videos/%s/AdditionalParts" % id
+ partcount = item['PartCount']
+ url = "{server}/emby/Videos/%s/AdditionalParts?format=json" % itemid
parts = doUtils.downloadUrl(url)
-
for part in parts['Items']:
- partId = part['Id']
- additionalPlayurl = PlayUtils().getPlayUrl(server, partId, part)
additionalListItem = xbmcgui.ListItem()
+ additionalPlayurl = putils.PlayUtils(part).getPlayUrl()
self.logMsg("Adding additional part: %s" % partcount, 1)
# Set listitem and properties for each additional parts
- self.setProperties(additionalPlayurl, part, additionalListItem)
- self.setListItemProps(server, partId, additionalListItem, part)
+ pbutils = PlaybackUtils(part)
+ pbutils.setProperties(additionalPlayurl, additionalListItem)
+ pbutils.setArtwork(additionalListItem)
playlist.add(additionalPlayurl, additionalListItem, index=currentPosition)
+ self.pl.verifyPlaylist()
currentPosition += 1
-
- ############### ADD DUMMY TO PLAYLIST #################
-
- if (not homeScreen and introsPlaylist) or (homeScreen and sizePlaylist > 0):
- # Playlist will fail on the current position. Adding dummy url
- dummyPlaylist = True
- self.logMsg("Adding dummy url to counter the setResolvedUrl error.", 2)
- playlist.add(playurl, index=startPos)
- currentPosition += 1
+ if dummyPlaylist:
+ # Added a dummy file to the playlist,
+ # because the first item is going to fail automatically.
+ self.logMsg("Processed as a playlist. First item is skipped.", 1)
+ return xbmcplugin.setResolvedUrl(int(sys.argv[1]), False, listitem)
# We just skipped adding properties. Reset flag for next time.
elif propertiesPlayback:
self.logMsg("Resetting properties playback flag.", 2)
- utils.window('propertiesPlayback', clear=True)
+ utils.window('emby_playbackProps', clear=True, windowid=10101)
+ #self.pl.verifyPlaylist()
+ ########## SETUP MAIN ITEM ##########
- self.verifyPlaylist()
+ # For transcoding only, ask for audio/subs pref
+ if utils.window('emby_%s.playmethod' % playurl) == "Transcode":
+ playurl = playutils.audioSubsPref(playurl)
+ utils.window('emby_%s.playmethod' % playurl, value="Transcode")
+
+ listitem.setPath(playurl)
+ self.setProperties(playurl, listitem)
############### PLAYBACK ################
-
- if not homeScreen and not introsPlaylist:
-
- self.logMsg("Processed as a single item.", 1)
- xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, listItem)
- elif dummyPlaylist:
- # Added a dummy file to the playlist because the first item is going to fail automatically.
- self.logMsg("Processed as a playlist. First item is skipped.", 1)
- xbmcplugin.setResolvedUrl(int(sys.argv[1]), False, listItem)
+ if homeScreen and seektime:
+ self.logMsg("Play as a widget item.", 1)
+ self.setListItem(listitem)
+ xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, listitem)
+
+ elif ((introsPlaylist and utils.window('emby_customPlaylist', windowid=10101) == "true") or
+ (homeScreen and not sizePlaylist)):
+ # Playlist was created just now, play it.
+ self.logMsg("Play playlist.", 1)
+ xbmc.Player().play(playlist, startpos=startPos)
else:
self.logMsg("Play as a regular item.", 1)
- xbmc.Player().play(playlist, startpos=startPos)
+ xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, listitem)
-
- def verifyPlaylist(self):
-
- playlistitems = '{"jsonrpc": "2.0", "method": "Playlist.GetItems", "params": { "playlistid": 1 }, "id": 1}'
- items = xbmc.executeJSONRPC(playlistitems)
- self.logMsg(items, 2)
+ def setProperties(self, playurl, listitem):
+ # Set all properties necessary for plugin path playback
+ item = self.item
+ itemid = item['Id']
+ itemtype = item['Type']
- def removeFromPlaylist(self, pos):
+ embyitem = "emby_%s" % playurl
+ utils.window('%s.runtime' % embyitem, value=str(item.get('RunTimeTicks')))
+ utils.window('%s.type' % embyitem, value=itemtype)
+ utils.window('%s.itemid' % embyitem, value=itemid)
- playlistremove = '{"jsonrpc": "2.0", "method": "Playlist.Remove", "params": { "playlistid": 1, "position": %d }, "id": 1}' % pos
- result = xbmc.executeJSONRPC(playlistremove)
- self.logMsg(result, 1)
+ if itemtype == "Episode":
+ utils.window('%s.refreshid' % embyitem, value=item.get('SeriesId'))
+ else:
+ utils.window('%s.refreshid' % embyitem, value=itemid)
+ # Append external subtitles to stream
+ playmethod = utils.window('%s.playmethod' % embyitem)
+ # Only for direct play and direct stream
+ subtitles = self.externalSubs(playurl)
+ if playmethod in ("DirectStream", "Transcode"):
+ # Direct play automatically appends external
+ listitem.setSubtitles(subtitles)
- def externalSubs(self, id, playurl, mediaSources):
+ self.setArtwork(listitem)
+
+ def externalSubs(self, playurl):
- username = utils.window('currUser')
- server = utils.window('server%s' % username)
externalsubs = []
mapping = {}
- mediaStream = mediaSources[0].get('MediaStreams')
+ item = self.item
+ itemid = item['Id']
+ try:
+ mediastreams = item['MediaSources'][0]['MediaStreams']
+ except (TypeError, KeyError, IndexError):
+ return
+
kodiindex = 0
- for stream in mediaStream:
-
+ for stream in mediastreams:
+
index = stream['Index']
# Since Emby returns all possible tracks together, have to pull only external subtitles.
# IsTextSubtitleStream if true, is available to download from emby.
- if "Subtitle" in stream['Type'] and stream['IsExternal'] and stream['IsTextSubtitleStream']:
-
- playmethod = utils.window("%splaymethod" % playurl)
+ if (stream['Type'] == "Subtitle" and
+ stream['IsExternal'] and stream['IsTextSubtitleStream']):
- 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)
+ # Direct stream
+ url = ("%s/Videos/%s/%s/Subtitles/%s/Stream.srt"
+ % (self.server, itemid, itemid, index))
# map external subtitles for mapping
mapping[kodiindex] = index
@@ -269,69 +269,79 @@ class PlaybackUtils():
kodiindex += 1
mapping = json.dumps(mapping)
- utils.window('%sIndexMapping' % playurl, value=mapping)
+ utils.window('emby_%s.indexMapping' % 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):
+ def setArtwork(self, listItem):
# Set up item and item info
- api = self.api
+ item = self.item
+ artwork = self.artwork
- type = result.get('Type')
- people = api.getPeople(result)
- studios = api.getStudios(result)
+ allartwork = artwork.getAllArtwork(item, parentInfo=True)
+ # Set artwork for listitem
+ arttypes = {
+
+ 'poster': "Primary",
+ 'tvshow.poster': "Primary",
+ 'clearart': "Art",
+ 'tvshow.clearart': "Art",
+ 'clearlogo': "Logo",
+ 'tvshow.clearlogo': "Logo",
+ 'discart': "Disc",
+ 'fanart_image': "Backdrop",
+ 'landscape': "Thumb"
+ }
+ for arttype in arttypes:
+
+ art = arttypes[arttype]
+ if art == "Backdrop":
+ try: # Backdrop is a list, grab the first backdrop
+ self.setArtProp(listItem, arttype, allartwork[art][0])
+ except: pass
+ else:
+ self.setArtProp(listItem, arttype, allartwork[art])
+
+ def setArtProp(self, listItem, arttype, path):
+
+ if arttype in (
+ 'thumb', 'fanart_image', 'small_poster', 'tiny_poster',
+ 'medium_landscape', 'medium_poster', 'small_fanartimage',
+ 'medium_fanartimage', 'fanart_noindicators'):
+
+ listItem.setProperty(arttype, path)
+ else:
+ listItem.setArt({arttype: path})
+
+ def setListItem(self, listItem):
+
+ item = self.item
+ type = item['Type']
+ API = self.API
+ people = API.getPeople()
+ studios = API.getStudios()
metadata = {
- 'title': result.get('Name', "Missing name"),
- 'year': result.get('ProductionYear'),
- 'plot': api.getOverview(result),
+ 'title': item.get('Name', "Missing name"),
+ 'year': item.get('ProductionYear'),
+ 'plot': API.getOverview(),
'director': people.get('Director'),
'writer': people.get('Writer'),
- 'mpaa': api.getMpaa(result),
- 'genre': api.getGenre(result),
+ 'mpaa': API.getMpaa(),
+ 'genre': " / ".join(item['Genres']),
'studio': " / ".join(studios),
- 'aired': api.getPremiereDate(result),
- 'rating': result.get('CommunityRating'),
- 'votes': result.get('VoteCount')
+ 'aired': API.getPremiereDate(),
+ 'rating': item.get('CommunityRating'),
+ 'votes': item.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', "")
+ thumbId = item.get('SeriesId')
+ season = item.get('ParentIndexNumber', -1)
+ episode = item.get('IndexNumber', -1)
+ show = item.get('SeriesName', "")
metadata['TVShowTitle'] = show
metadata['season'] = season
@@ -340,123 +350,4 @@ class PlaybackUtils():
listItem.setProperty('IsPlayable', 'true')
listItem.setProperty('IsFolder', 'false')
listItem.setLabel(metadata['title'])
- listItem.setInfo('video', infoLabels=metadata)
-
- # Set artwork for listitem
- self.setArt(listItem,'poster', API().getArtwork(result, "Primary"))
- self.setArt(listItem,'tvshow.poster', API().getArtwork(result, "SeriesPrimary"))
- self.setArt(listItem,'clearart', API().getArtwork(result, "Art"))
- self.setArt(listItem,'tvshow.clearart', API().getArtwork(result, "Art"))
- self.setArt(listItem,'clearlogo', API().getArtwork(result, "Logo"))
- self.setArt(listItem,'tvshow.clearlogo', API().getArtwork(result, "Logo"))
- self.setArt(listItem,'discart', API().getArtwork(result, "Disc"))
- self.setArt(listItem,'fanart_image', API().getArtwork(result, "Backdrop"))
- self.setArt(listItem,'landscape', API().getArtwork(result, "Thumb"))
-
- def seekToPosition(self, seekTo):
- # Set a loop to wait for positive confirmation of playback
- count = 0
- while not xbmc.Player().isPlaying():
- count += 1
- if count >= 10:
- return
- else:
- xbmc.sleep(500)
-
- # Jump to seek position
- count = 0
- while xbmc.Player().getTime() < (seekToTime - 5) and count < 11: # only try 10 times
- count += 1
- xbmc.Player().seekTime(seekTo)
- xbmc.sleep(100)
-
- def PLAYAllItems(self, items, startPositionTicks):
-
- self.logMsg("== ENTER: PLAYAllItems ==")
- self.logMsg("Items: %s" % items)
-
- doUtils = self.doUtils
-
- playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
- playlist.clear()
- started = False
-
- for itemId in items:
- self.logMsg("Adding Item to playlist: %s" % itemId, 1)
- url = "{server}/mediabrowser/Users/{UserId}/Items/%s?format=json" % itemId
- result = doUtils.downloadUrl(url)
-
- addition = self.addPlaylistItem(playlist, result)
- if not started and addition:
- started = True
- self.logMsg("Starting Playback Pre", 1)
- xbmc.Player().play(playlist)
-
- if not started:
- self.logMsg("Starting Playback Post", 1)
- xbmc.Player().play(playlist)
-
- # Seek to position
- if startPositionTicks:
- seekTime = startPositionTicks / 10000000.0
- self.seekToPosition(seekTime)
-
- def AddToPlaylist(self, itemIds):
-
- self.logMsg("== ENTER: PLAYAllItems ==")
-
- doUtils = self.doUtils
- playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
-
- for itemId in itemIds:
- self.logMsg("Adding Item to Playlist: %s" % itemId)
- url = "{server}/mediabrowser/Users/{UserId}/Items/%s?format=json" % itemId
- result = doUtils.downloadUrl(url)
-
- self.addPlaylistItem(playlist, result)
-
- return playlist
-
- def addPlaylistItem(self, playlist, item):
-
- id = item['Id']
- username = utils.window('currUser')
- server = utils.window('server%s' % username)
-
- playurl = PlayUtils().getPlayUrl(server, id, item)
-
- if utils.window('playurlFalse') == "true":
- # Playurl failed - set in PlayUtils.py
- utils.window('playurlFalse', clear=True)
- self.logMsg("Failed to retrieve the playback path/url or dialog was cancelled.", 1)
- return
-
- self.logMsg("Playurl: %s" % playurl)
-
- thumb = API().getArtwork(item, "Primary")
- listItem = xbmcgui.ListItem(path=playurl, iconImage=thumb, thumbnailImage=thumb)
- self.setListItemProps(server, id, listItem, item)
- self.setProperties(playurl, item, listItem)
-
- playlist.add(playurl, listItem)
-
- # Not currently being used
- '''def PLAYAllEpisodes(self, items):
- WINDOW = xbmcgui.Window(10000)
-
- username = WINDOW.getProperty('currUser')
- userid = WINDOW.getProperty('userId%s' % username)
- server = WINDOW.getProperty('server%s' % username)
-
- playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
- playlist.clear()
-
- for item in items:
-
- item_url = "{server}/mediabrowser/Users/{UserId}/Items/%s?format=json&ImageTypeLimit=1" % item["Id"]
- jsonData = self.downloadUtils.downloadUrl(item_url)
-
- item_data = jsonData
- self.addPlaylistItem(playlist, item_data, server, userid)
-
- xbmc.Player().play(playlist)'''
\ No newline at end of file
+ listItem.setInfo('video', infoLabels=metadata)
\ No newline at end of file
diff --git a/resources/lib/Player.py b/resources/lib/Player.py
index 0b760a88..b6d25e19 100644
--- a/resources/lib/Player.py
+++ b/resources/lib/Player.py
@@ -2,45 +2,47 @@
#################################################################################################
-import json as json
+import json
import xbmc
import xbmcgui
-from DownloadUtils import DownloadUtils
-from WebSocketClient import WebSocketThread
-from ClientInformation import ClientInformation
-from LibrarySync import LibrarySync
-import Utils as utils
+import utils
+import clientinfo
+import downloadutils
+import kodidb_functions as kodidb
+import websocket_client as wsc
#################################################################################################
-class Player( xbmc.Player ):
+
+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 = {}
+ played_info = {}
playStats = {}
currentFile = None
- def __init__(self, *args):
+
+ def __init__(self):
self.__dict__ = self._shared_state
+
+ self.clientInfo = clientinfo.ClientInfo()
+ self.addonName = self.clientInfo.getAddonName()
+ self.doUtils = downloadutils.DownloadUtils()
+ self.ws = wsc.WebSocket_Client()
+ self.xbmcplayer = xbmc.Player()
+
self.logMsg("Starting playback monitor.", 2)
def logMsg(self, msg, lvl=1):
self.className = self.__class__.__name__
- utils.logMsg("%s %s" % (self.addonName, self.className), msg, int(lvl))
+ utils.logMsg("%s %s" % (self.addonName, self.className), msg, lvl)
+
def GetPlayStats(self):
return self.playStats
@@ -74,39 +76,50 @@ class Player( xbmc.Player ):
self.currentFile = currentFile
# We may need to wait for info to be set in kodi monitor
- itemId = utils.window("%sitem_id" % currentFile)
+ itemId = utils.window("emby_%s.itemid" % currentFile)
tryCount = 0
while not itemId:
xbmc.sleep(200)
- itemId = utils.window("%sitem_id" % currentFile)
+ itemId = utils.window("emby_%s.itemid" % currentFile)
if tryCount == 20: # try 20 times or about 10 seconds
self.logMsg("Could not find itemId, cancelling playback report...", 1)
break
else: tryCount += 1
else:
- self.logMsg("ONPLAYBACK_STARTED: %s ITEMID: %s" % (currentFile, itemId), 0)
+ 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)
+ embyitem = "emby_%s" % currentFile
+ runtime = utils.window("%s.runtime" % embyitem)
+ refresh_id = utils.window("%s.refreshid" % embyitem)
+ playMethod = utils.window("%s.playmethod" % embyitem)
+ itemType = utils.window("%s.type" % embyitem)
+ utils.window('emby_skipWatched%s' % itemId, value="true")
+
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)
+ volume_query = {
+
+ "jsonrpc": "2.0",
+ "id": 1,
+ "method": "Application.GetProperties",
+ "params": {
+
+ "properties": ["volume", "muted"]
+ }
+ }
+ result = xbmc.executeJSONRPC(json.dumps(volume_query))
result = json.loads(result)
result = result.get('result')
-
+
volume = result.get('volume')
muted = result.get('muted')
# Postdata structure to send to Emby server
- url = "{server}/mediabrowser/Sessions/Playing"
+ url = "{server}/emby/Sessions/Playing"
postdata = {
'QueueableMediaTypes': "Video",
@@ -123,12 +136,22 @@ class Player( xbmc.Player ):
if playMethod == "Transcode":
# property set in PlayUtils.py
postdata['AudioStreamIndex'] = utils.window("%sAudioStreamIndex" % currentFile)
- postdata['SubtitleStreamIndex'] = utils.window("%sSubtitleStreamIndex" % 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)
+ tracks_query = {
+
+ "jsonrpc": "2.0",
+ "id": 1,
+ "method": "Player.GetProperties",
+ "params": {
+
+ "playerid": 1,
+ "properties": ["currentsubtitle","currentaudiostream","subtitleenabled"]
+ }
+ }
+ result = xbmc.executeJSONRPC(json.dumps(tracks_query))
result = json.loads(result)
result = result.get('result')
@@ -155,9 +178,9 @@ class Player( xbmc.Player ):
# Number of audiotracks to help get Emby Index
audioTracks = len(xbmc.Player().getAvailableAudioStreams())
- mapping = utils.window("%sIndexMapping" % currentFile)
+ mapping = utils.window("%s.indexMapping" % embyitem)
- if mapping: # Set in PlaybackUtils.py
+ if mapping: # Set in playbackutils.py
self.logMsg("Mapping for external subtitles index: %s" % mapping, 2)
externalIndex = json.loads(mapping)
@@ -167,7 +190,8 @@ class Player( xbmc.Player ):
postdata['SubtitleStreamIndex'] = externalIndex[str(indexSubs)]
else:
# Internal subtitle currently selected
- postdata['SubtitleStreamIndex'] = indexSubs - len(externalIndex) + audioTracks + 1
+ subindex = indexSubs - len(externalIndex) + audioTracks + 1
+ postdata['SubtitleStreamIndex'] = subindex
else: # Direct paths enabled scenario or no external subtitles set
postdata['SubtitleStreamIndex'] = indexSubs + audioTracks + 1
@@ -184,7 +208,7 @@ class Player( xbmc.Player ):
runtime = int(runtime)
except ValueError:
runtime = xbmcplayer.getTotalTime()
- self.logMsg("Runtime is missing, grabbing runtime from Kodi player: %s" % runtime, 1)
+ self.logMsg("Runtime is missing, Kodi runtime: %s" % runtime, 1)
# Save data map for updates and position calls
data = {
@@ -200,8 +224,8 @@ class Player( xbmc.Player ):
'currentPosition': int(seekTime)
}
- self.played_information[currentFile] = data
- self.logMsg("ADDING_FILE: %s" % self.played_information, 1)
+ self.played_info[currentFile] = data
+ self.logMsg("ADDING_FILE: %s" % self.played_info, 1)
# log some playback stats
'''if(itemType != None):
@@ -225,7 +249,7 @@ class Player( xbmc.Player ):
# Get current file
currentFile = self.currentFile
- data = self.played_information.get(currentFile)
+ data = self.played_info.get(currentFile)
# only report playback if emby has initiated the playback (item_id has value)
if data:
@@ -239,15 +263,23 @@ class Player( xbmc.Player ):
# Get playback volume
- volume_query = '{"jsonrpc": "2.0", "method": "Application.GetProperties", "params": {"properties": ["volume","muted"]}, "id": 1}'
- result = xbmc.executeJSONRPC(volume_query)
+ volume_query = {
+
+ "jsonrpc": "2.0",
+ "id": 1,
+ "method": "Application.GetProperties",
+ "params": {
+
+ "properties": ["volume", "muted"]
+ }
+ }
+ result = xbmc.executeJSONRPC(json.dumps(volume_query))
result = json.loads(result)
result = result.get('result')
volume = result.get('volume')
muted = result.get('muted')
-
# Postdata for the websocketclient report
postdata = {
@@ -269,8 +301,18 @@ class Player( xbmc.Player ):
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)
+ tracks_query = {
+
+ "jsonrpc": "2.0",
+ "id": 1,
+ "method": "Player.GetProperties",
+ "params": {
+
+ "playerid": 1,
+ "properties": ["currentsubtitle","currentaudiostream","subtitleenabled"]
+ }
+ }
+ result = xbmc.executeJSONRPC(json.dumps(tracks_query))
result = json.loads(result)
result = result.get('result')
@@ -297,7 +339,7 @@ class Player( xbmc.Player ):
# Number of audiotracks to help get Emby Index
audioTracks = len(xbmc.Player().getAvailableAudioStreams())
- mapping = utils.window("%sIndexMapping" % currentFile)
+ mapping = utils.window("emby_%s.indexMapping" % currentFile)
if mapping: # Set in PlaybackUtils.py
@@ -306,13 +348,16 @@ class Player( xbmc.Player ):
if externalIndex.get(str(indexSubs)):
# If the current subtitle is in the mapping
- data['SubtitleStreamIndex'], postdata['SubtitleStreamIndex'] = [externalIndex[str(indexSubs)]] * 2
+ subindex = [externalIndex[str(indexSubs)]] * 2
+ data['SubtitleStreamIndex'], postdata['SubtitleStreamIndex'] = subindex
else:
# Internal subtitle currently selected
- data['SubtitleStreamIndex'], postdata['SubtitleStreamIndex'] = [indexSubs - len(externalIndex) + audioTracks + 1] * 2
+ subindex = [indexSubs - len(externalIndex) + audioTracks + 1] * 2
+ data['SubtitleStreamIndex'], postdata['SubtitleStreamIndex'] = subindex
else: # Direct paths enabled scenario or no external subtitles set
- data['SubtitleStreamIndex'], postdata['SubtitleStreamIndex'] = [indexSubs + audioTracks + 1] * 2
+ subindex = [indexSubs + audioTracks + 1] * 2
+ data['SubtitleStreamIndex'], postdata['SubtitleStreamIndex'] = subindex
else:
data['SubtitleStreamIndex'], postdata['SubtitleStreamIndex'] = [""] * 2
@@ -326,8 +371,8 @@ class Player( xbmc.Player ):
currentFile = self.currentFile
self.logMsg("PLAYBACK_PAUSED: %s" % currentFile, 2)
- if self.played_information.get(currentFile):
- self.played_information[currentFile]['paused'] = True
+ if self.played_info.get(currentFile):
+ self.played_info[currentFile]['paused'] = True
self.reportPlayback()
@@ -336,8 +381,8 @@ class Player( xbmc.Player ):
currentFile = self.currentFile
self.logMsg("PLAYBACK_RESUMED: %s" % currentFile, 2)
- if self.played_information.get(currentFile):
- self.played_information[currentFile]['paused'] = False
+ if self.played_info.get(currentFile):
+ self.played_info[currentFile]['paused'] = False
self.reportPlayback()
@@ -346,15 +391,17 @@ class Player( xbmc.Player ):
currentFile = self.currentFile
self.logMsg("PLAYBACK_SEEK: %s" % currentFile, 2)
- if self.played_information.get(currentFile):
+ if self.played_info.get(currentFile):
position = self.xbmcplayer.getTime()
- self.played_information[currentFile]['currentPosition'] = position
+ self.played_info[currentFile]['currentPosition'] = position
self.reportPlayback()
def onPlayBackStopped( self ):
# Will be called when user stops xbmc playing a file
self.logMsg("ONPLAYBACK_STOPPED", 2)
+ xbmcgui.Window(10101).clearProperties()
+ self.logMsg("Clear playlist properties.")
self.stopAll()
def onPlayBackEnded( self ):
@@ -364,14 +411,16 @@ class Player( xbmc.Player ):
def stopAll(self):
- if not self.played_information:
+ doUtils = self.doUtils
+
+ if not self.played_info:
return
- self.logMsg("Played_information: %s" % self.played_information, 1)
+ self.logMsg("Played_information: %s" % self.played_info, 1)
# Process each items
- for item in self.played_information:
+ for item in self.played_info:
- data = self.played_information.get(item)
+ data = self.played_info.get(item)
if data:
self.logMsg("Item path: %s" % item, 2)
@@ -379,47 +428,61 @@ class Player( xbmc.Player ):
runtime = data['runtime']
currentPosition = data['currentPosition']
- itemId = data['item_id']
+ 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)
+ try:
+ percentComplete = (currentPosition * 10000000) / int(runtime)
+ except ZeroDivisionError:
+ # Runtime is 0.
+ percentComplete = 0
+
markPlayedAt = float(utils.settings('markPlayed')) / 100
+ self.logMsg(
+ "Percent complete: %s Mark played at: %s"
+ % (percentComplete, markPlayedAt), 1)
- 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")
+ # Prevent manually mark as watched in Kodi monitor
+ utils.window('emby_skipWatched%s' % itemid, value="true")
self.stopPlayback(data)
- offerDelete = utils.settings('offerDelete') == "true"
- offerTypeDelete = False
+ # Stop transcoding
+ if playMethod == "Transcode":
+ self.logMsg("Transcoding for %s terminated." % itemid, 1)
+ deviceId = self.clientInfo.getDeviceId()
+ url = "{server}/emby/Videos/ActiveEncodings?DeviceId=%s" % deviceId
+ doUtils.downloadUrl(url, type="DELETE")
- if type == "Episode" and utils.settings('offerDeleteTV') == "true":
- offerTypeDelete = True
+ # Send the delete action to the server.
+ offerDelete = False
- elif type == "Movie" and utils.settings('offerDeleteMovies') == "true":
- offerTypeDelete = True
+ if type == "Episode" and utils.settings('deleteTV') == "true":
+ offerDelete = True
+ elif type == "Movie" and utils.settings('deleteMovies') == "true":
+ offerDelete = 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")
+ if utils.settings('offerDelete') != "true":
+ # Delete could be disabled, even if the subsetting is enabled.
+ offerDelete = False
+
+ if percentComplete >= markPlayedAt and offerDelete:
+ if utils.settings('skipConfirmDelete') != "true":
+ resp = xbmcgui.Dialog().yesno(
+ heading="Confirm delete",
+ line1="Delete file on Emby Server?")
+ if not resp:
+ self.logMsg("User skipped deletion.", 1)
+ continue
+
+ url = "{server}/emby/Items/%s?format=json" % itemid
+ self.logMsg("Deleting request: %s" % itemid)
+ doUtils.downloadUrl(url, type="DELETE")
- self.played_information.clear()
+ self.played_info.clear()
def stopPlayback(self, data):
@@ -429,12 +492,11 @@ class Player( xbmc.Player ):
currentPosition = data['currentPosition']
positionTicks = int(currentPosition * 10000000)
- url = "{server}/mediabrowser/Sessions/Playing/Stopped"
+ url = "{server}/emby/Sessions/Playing/Stopped"
postdata = {
'ItemId': itemId,
'MediaSourceId': itemId,
'PositionTicks': positionTicks
}
-
self.doUtils.downloadUrl(url, postBody=postdata, type="POST")
\ No newline at end of file
diff --git a/resources/lib/UserClient.py b/resources/lib/UserClient.py
index f95f9291..a6562a53 100644
--- a/resources/lib/UserClient.py
+++ b/resources/lib/UserClient.py
@@ -1,22 +1,21 @@
-#################################################################################################
-# UserClient thread
-#################################################################################################
+# -*- coding: utf-8 -*-
+
+##################################################################################################
+
+import hashlib
+import threading
import xbmc
import xbmcgui
import xbmcaddon
import xbmcvfs
-import threading
-import hashlib
-import json as json
+import artwork
+import utils
+import clientinfo
+import downloadutils
-import KodiMonitor
-import Utils as utils
-from ClientInformation import ClientInformation
-from DownloadUtils import DownloadUtils
-from Player import Player
-from API import API
+##################################################################################################
class UserClient(threading.Thread):
@@ -24,16 +23,7 @@ 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
@@ -44,25 +34,25 @@ class UserClient(threading.Thread):
HasAccess = True
AdditionalUser = []
- def __init__(self, *args):
+ userSettings = None
+
+
+ def __init__(self):
self.__dict__ = self._shared_state
- threading.Thread.__init__(self, *args)
+ self.addon = xbmcaddon.Addon()
+
+ self.addonName = clientinfo.ClientInfo().getAddonName()
+ self.doUtils = downloadutils.DownloadUtils()
+ self.logLevel = int(utils.settings('logLevel'))
+
+ threading.Thread.__init__(self)
def logMsg(self, msg, lvl=1):
className = self.__class__.__name__
- utils.logMsg("%s %s" % (self.addonName, className), str(msg), int(lvl))
+ utils.logMsg("%s %s" % (self.addonName, className), msg, lvl)
- def getUsername(self):
-
- username = utils.settings('username')
-
- if (username == ""):
- self.logMsg("No username saved.", 2)
- return ""
-
- return username
def getAdditionalUsers(self):
@@ -71,11 +61,21 @@ class UserClient(threading.Thread):
if additionalUsers:
self.AdditionalUser = additionalUsers.split(',')
+ def getUsername(self):
+
+ username = utils.settings('username')
+
+ if not username:
+ self.logMsg("No username saved.", 2)
+ return ""
+
+ return username
+
def getLogLevel(self):
try:
logLevel = int(utils.settings('logLevel'))
- except:
+ except ValueError:
logLevel = 0
return logLevel
@@ -83,71 +83,84 @@ class UserClient(threading.Thread):
def getUserId(self):
username = self.getUsername()
- w_userId = self.WINDOW.getProperty('userId%s' % username)
+ w_userId = utils.window('emby_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)
+ if w_userId:
+ if not s_userId:
+ # Save access token if it's missing from settings
+ utils.settings('userId%s' % username, value=w_userId)
+ self.logMsg(
+ "Returning userId from WINDOW for username: %s UserId: %s"
+ % (username, w_userId), 2)
return w_userId
# Verify the settings
- elif (s_userId != ""):
- self.logMsg("Returning userId from SETTINGS for username: %s userId: %s" % (username, s_userId), 2)
+ 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
+ self.logMsg("No userId saved for username: %s." % username, 1)
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')
+ # Alternate host
+ HTTPS = utils.settings('secondhttps') == "true"
host = utils.settings('secondipaddress')
port = utils.settings('secondport')
-
+ else:
+ # Original host
+ HTTPS = utils.settings('https') == "true"
+ host = utils.settings('ipaddress')
+ port = utils.settings('port')
+
server = host + ":" + port
- if host == "":
+ if not host:
self.logMsg("No server information saved.", 2)
- return ""
+ return False
# If https is true
- if prefix and (HTTPS == "true"):
+ if prefix and HTTPS:
server = "https://%s" % server
return server
# If https is false
- elif prefix and (HTTPS == "false"):
+ elif prefix and not HTTPS:
server = "http://%s" % server
return server
# If only the host:port is required
- elif (prefix == False):
+ elif not prefix:
return server
def getToken(self):
username = self.getUsername()
- w_token = self.WINDOW.getProperty('accessToken%s' % username)
+ w_token = utils.window('emby_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)
+ if w_token:
+ if not s_token:
+ # Save access token if it's missing from settings
+ utils.settings('accessToken', value=w_token)
+ self.logMsg(
+ "Returning accessToken from WINDOW for username: %s accessToken: %s"
+ % (username, w_token), 2)
return w_token
# Verify the settings
- elif (s_token != ""):
- self.logMsg("Returning accessToken from SETTINGS for username: %s accessToken: %s" % (username, s_token), 2)
- self.WINDOW.setProperty('accessToken%s' % username, s_token)
+ elif s_token:
+ self.logMsg(
+ "Returning accessToken from SETTINGS for username: %s accessToken: %s"
+ % (username, s_token), 2)
+ utils.window('emby_accessToken%s' % username, value=s_token)
return s_token
else:
- self.logMsg("No token found.")
+ self.logMsg("No token found.", 1)
return ""
def getSSLverify(self):
@@ -174,71 +187,63 @@ class UserClient(threading.Thread):
def setUserPref(self):
- player = Player()
- server = self.getServer()
- userId = self.getUserId()
-
- url = "{server}/mediabrowser/Users/{UserId}?format=json"
- result = self.doUtils.downloadUrl(url)
+ doUtils = self.doUtils
+ url = "{server}/emby/Users/{UserId}?format=json"
+ result = doUtils.downloadUrl(url)
+ self.userSettings = result
# Set user image for skin display
- self.WINDOW.setProperty("EmbyUserImage",API().getUserArtwork(result,"Primary"))
+ if result.get('PrimaryImageTag'):
+ utils.window('EmbyUserImage', value=artwork.Artwork().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)
+ # Set resume point max
+ url = "{server}/emby/System/Configuration?format=json"
+ result = 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
+ url = "%s/emby/Users/Public?format=json" % server
result = self.doUtils.downloadUrl(url, authenticate=False)
- users = []
-
- if (result != ""):
- users = result
+ if result != "":
+ return result
else:
# Server connection failed
return False
- return users
-
def hasAccess(self):
-
- url = "{server}/mediabrowser/Users"
+ # hasAccess is verified in service.py
+ url = "{server}/emby/Users?format=json"
result = self.doUtils.downloadUrl(url)
- if result is False:
- # Access is restricted
- self.logMsg("Access is restricted.")
+ if result == False:
+ # Access is restricted, set in downloadutils.py via exception
+ self.logMsg("Access is restricted.", 1)
self.HasAccess = False
- return
- elif self.WINDOW.getProperty('Server_online') != "true":
+
+ elif utils.window('emby_online') != "true":
# Server connection failed
- return
+ pass
- if self.WINDOW.getProperty("Server_status") == "restricted":
- self.logMsg("Access is granted.")
+ elif utils.window('emby_serverStatus') == "restricted":
+ self.logMsg("Access is granted.", 1)
self.HasAccess = True
- self.WINDOW.setProperty("Server_status", "")
+ utils.window('emby_serverStatus', clear=True)
xbmcgui.Dialog().notification("Emby server", "Access is enabled.")
- return
def loadCurrUser(self, authenticated=False):
- WINDOW = self.WINDOW
doUtils = self.doUtils
username = self.getUsername()
-
+ userId = self.getUserId()
+
# Only to be used if token exists
- self.currUserId = self.getUserId()
+ self.currUserId = userId
self.currServer = self.getServer()
self.currToken = self.getToken()
self.ssl = self.getSSLverify()
@@ -246,21 +251,21 @@ class UserClient(threading.Thread):
# 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)
+ url = "%s/emby/Users/%s?format=json" % (self.currServer, userId)
+ utils.window('emby_currUser', value=userId)
+ utils.window('emby_accessToken%s' % userId, value=self.currToken)
result = doUtils.downloadUrl(url)
+
if result == 401:
# Token is no longer valid
self.resetClient()
return False
# Set to windows property
- WINDOW.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)
+ utils.window('emby_currUser', value=userId)
+ utils.window('emby_accessToken%s' % userId, value=self.currToken)
+ utils.window('emby_server%s' % userId, value=self.currServer)
+ utils.window('emby_server_%s' % userId, value=self.getServer(prefix=False))
# Set DownloadUtils values
doUtils.setUsername(username)
@@ -273,188 +278,194 @@ class UserClient(threading.Thread):
# Start DownloadUtils session
doUtils.startSession()
self.getAdditionalUsers()
-
- self.currUser = username
# Set user preferences in settings
+ self.currUser = username
self.setUserPref()
+
def authenticate(self):
-
- WINDOW = self.WINDOW
- addon = self.addon
+ # Get /profile/addon_data
+ addondir = xbmc.translatePath(self.addon.getAddonInfo('profile')).decode('utf-8')
+ hasSettings = xbmcvfs.exists("%ssettings.xml" % addondir)
username = self.getUsername()
server = self.getServer()
- 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.")
+ if not hasSettings:
+ self.logMsg("No settings.xml found.", 1)
self.auth = False
return
# If no user information
- if (server == "") or (username == ""):
- self.logMsg("Missing server information.")
+ elif not server or not username:
+ self.logMsg("Missing server information.", 1)
self.auth = False
return
- # If there's a token
- if (self.getToken() != ""):
+ # If there's a token, load the user
+ elif 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)
+ self.logMsg("Current user: %s" % self.currUser, 1)
+ self.logMsg("Current userId: %s" % self.currUserId, 1)
+ self.logMsg("Current accessToken: %s" % self.currToken, 2)
return
+ ##### AUTHENTICATE USER #####
+
users = self.getPublicUsers()
password = ""
# Find user in list
for user in users:
- name = user[u'Name']
- userHasPassword = False
+ name = user['Name']
- if (unicode(username, 'utf-8') in name):
- # Verify if user has a password
- if (user.get("HasPassword") == True):
- userHasPassword = True
+ if username.decode('utf-8') in name:
# If user has password
- if (userHasPassword):
- password = xbmcgui.Dialog().input("Enter password for user: %s" % username, option=xbmcgui.ALPHANUM_HIDE_INPUT)
+ if user['HasPassword'] == True:
+ password = xbmcgui.Dialog().input(
+ heading="Enter password for user: %s" % username,
+ option=xbmcgui.ALPHANUM_HIDE_INPUT)
# If password dialog is cancelled
- if (password == ""):
+ if not password:
self.logMsg("No password entered.", 0)
- self.WINDOW.setProperty("Server_status", "Stop")
+ utils.window('emby_serverStatus', value="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)
-
+ password = xbmcgui.Dialog().input(
+ heading="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
+ url = "%s/emby/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
+ self.logMsg("Auth response: %s" % result, 1)
+ accessToken = result['AccessToken']
+
+ except (KeyError, TypeError):
+ self.logMsg("Failed to retrieve the api key.", 1)
+ accessToken = None
- if (result != None and accessToken != None):
+ if accessToken is not 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)
+ userId = result['User']['Id']
+ utils.settings('accessToken', value=accessToken)
+ utils.settings('userId%s' % username, value=userId)
+ self.logMsg("User Authenticated: %s" % accessToken, 1)
self.loadCurrUser(authenticated=True)
- self.WINDOW.setProperty("Server_status", "")
+ utils.window('emby_serverStatus', clear=True)
self.retry = 0
- return
else:
- self.logMsg("User authentication failed.")
- utils.settings("accessToken", "")
- utils.settings("userId%s" % username, "")
+ self.logMsg("User authentication failed.", 1)
+ utils.settings('accessToken', value="")
+ utils.settings('userId%s' % username, value="")
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.logMsg(
+ """Too many retries. You can retry by resetting
+ attempts in the addon settings.""", 1)
+ utils.window('emby_serverStatus', value="Stop")
+ xbmcgui.Dialog().ok(
+ heading="Error connecting",
+ line1="Failed to authenticate too many times.",
+ line2="You can retry by resetting attempts in the addon settings.")
+
+ self.retry += 1
self.auth = False
- return
def resetClient(self):
- username = self.getUsername()
self.logMsg("Reset UserClient authentication.", 1)
- if (self.currToken != None):
+ username = self.getUsername()
+
+ if self.currToken is not None:
# In case of 401, removed saved token
- utils.settings("accessToken", "")
- self.WINDOW.setProperty("accessToken%s" % username, "")
+ utils.settings('accessToken', value="")
+ utils.window('emby_accessToken%s' % username, clear=True)
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)
+ monitor = xbmc.Monitor()
+ self.logMsg("----===## Starting UserClient ##===----", 0)
- while not self.KodiMonitor.abortRequested():
+ while not monitor.abortRequested():
# Verify the log level
currLogLevel = self.getLogLevel()
if self.logLevel != currLogLevel:
# Set new log level
self.logLevel = currLogLevel
+ utils.window('emby_logLevel', value=str(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")
-
+
+ status = utils.window('emby_serverStatus')
+ if status:
+ # Verify the connection status to server
if status == "restricted":
# Parental control is restricting access
self.HasAccess = False
elif status == "401":
- self.WINDOW.setProperty("Server_status", "Auth")
- # Revoked token
+ # Unauthorized access, revoke token
+ utils.window('emby_serverStatus', value="Auth")
self.resetClient()
- if self.auth and (self.currUser == None):
- status = self.WINDOW.getProperty("Server_status")
-
- if (status == "") or (status == "Auth"):
+ if self.auth and (self.currUser is None):
+ # Try to authenticate user
+ status = utils.window('emby_serverStatus')
+ if not status or status == "Auth":
+ # Set auth flag because we no longer need
+ # to authenticate the user
self.auth = False
self.authenticate()
- if (self.auth == False) and (self.currUser == None):
- # Only if there's information found to login
+
+ if not self.auth and (self.currUser is None):
+ # If authenticate failed.
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
+ status = utils.window('emby_serverStatus')
- elif (server != "") and (username != ""):
- self.logMsg("Server found: %s" % server)
- self.logMsg("Username found: %s" % username)
+ # The status Stop is for when user cancelled password dialog.
+ if server and username and status != "Stop":
+ # Only if there's information found to login
+ self.logMsg("Server found: %s" % server, 2)
+ self.logMsg("Username found: %s" % username, 2)
self.auth = True
- # If stopping the client didn't work
+
if self.stopClient == True:
+ # If stopping the client didn't work
break
- if self.KodiMonitor.waitForAbort(1):
+ if monitor.waitForAbort(1):
# Abort was requested while waiting. We should exit
break
self.doUtils.stopSession()
- self.logMsg("|---- UserClient Stopped ----|", 0)
+ self.logMsg("##===---- UserClient Stopped ----===##", 0)
def stopClient(self):
- # As last resort
+ # When emby for kodi terminates
self.stopClient = True
\ No newline at end of file
diff --git a/resources/lib/Utils.py b/resources/lib/Utils.py
index 73c49c95..83e73e1d 100644
--- a/resources/lib/Utils.py
+++ b/resources/lib/Utils.py
@@ -1,59 +1,76 @@
-#################################################################################################
-# utils
+# -*- coding: utf-8 -*-
+
#################################################################################################
-import xbmc
-import xbmcgui
-import xbmcaddon
-import xbmcvfs
-import json
-import os
import cProfile
-import pstats
-import time
import inspect
+import pstats
import sqlite3
-import string
+import time
import unicodedata
import xml.etree.ElementTree as etree
-from API import API
-from PlayUtils import PlayUtils
-from DownloadUtils import DownloadUtils
+import xbmc
+import xbmcaddon
+import xbmcgui
+import xbmcvfs
-downloadUtils = DownloadUtils()
-addon = xbmcaddon.Addon()
-language = addon.getLocalizedString
+#################################################################################################
-
-def logMsg(title, msg, level = 1):
+
+def logMsg(title, msg, level=1):
- WINDOW = xbmcgui.Window(10000)
# Get the logLevel set in UserClient
- logLevel = int(WINDOW.getProperty('getLogLevel'))
+ try:
+ logLevel = int(window('emby_logLevel'))
+ except ValueError:
+ logLevel = 0
- if(logLevel >= level):
- if(logLevel == 2): # inspect.stack() is expensive
+ if logLevel >= level:
+
+ if logLevel == 2: # inspect.stack() is expensive
try:
- xbmc.log(title + " -> " + inspect.stack()[1][3] + " : " + str(msg))
+ xbmc.log("%s -> %s : %s" % (title, inspect.stack()[1][3], msg))
except UnicodeEncodeError:
- xbmc.log(title + " -> " + inspect.stack()[1][3] + " : " + str(msg.encode('utf-8')))
+ xbmc.log("%s -> %s : %s" % (title, inspect.stack()[1][3], msg.encode('utf-8')))
else:
try:
- xbmc.log(title + " -> " + str(msg))
+ xbmc.log("%s -> %s" % (title, msg))
except UnicodeEncodeError:
- xbmc.log(title + " -> " + str(msg.encode('utf-8')))
+ xbmc.log("%s -> %s" % (title, msg.encode('utf-8')))
-def convertEncoding(data):
- #nasty hack to make sure we have a unicode string
- try:
- return data.decode('utf-8')
- except:
- return data
-
-def KodiSQL(type="video"):
+def window(property, value=None, clear=False, windowid=10000):
+ # Get or set window property
+ WINDOW = xbmcgui.Window(windowid)
- if type == "music":
+ if clear:
+ WINDOW.clearProperty(property)
+ elif value is not None:
+ WINDOW.setProperty(property, value)
+ else:
+ return WINDOW.getProperty(property)
+
+def settings(setting, value=None):
+ # Get or add addon setting
+ addon = xbmcaddon.Addon(id='plugin.video.emby')
+
+ if value is not None:
+ addon.setSetting(setting, value)
+ else:
+ return addon.getSetting(setting)
+
+def language(stringid):
+ # Central string retrieval
+ addon = xbmcaddon.Addon(id='plugin.video.emby')
+ string = addon.getLocalizedString(stringid)
+
+ return string
+
+def kodiSQL(type="video"):
+
+ if type == "emby":
+ dbPath = xbmc.translatePath("special://database/emby.db").decode('utf-8')
+ elif type == "music":
dbPath = getKodiMusicDBPath()
elif type == "texture":
dbPath = xbmc.translatePath("special://database/Textures13.db").decode('utf-8')
@@ -94,219 +111,140 @@ def getKodiMusicDBPath():
"special://database/MyMusic%s.db"
% dbVersion.get(kodibuild, "")).decode('utf-8')
return dbPath
+
+def reset():
+
+ dialog = xbmcgui.Dialog()
+
+ resp = dialog.yesno("Warning", "Are you sure you want to reset your local Kodi database?")
+ if resp == 0:
+ return
+
+ # first stop any db sync
+ window('emby_shouldStop', value="true")
+ count = 10
+ while window('emby_dbScan') == "true":
+ logMsg("EMBY", "Sync is running, will retry: %s..." % count)
+ count -= 1
+ if count == 0:
+ dialog.ok("Warning", "Could not stop the database from running. Try again.")
+ return
+ xbmc.sleep(1000)
+
+ # Clean up the playlists
+ path = xbmc.translatePath("special://profile/playlists/video/").decode('utf-8')
+ dirs, files = xbmcvfs.listdir(path)
+ for file in files:
+ if file.startswith('Emby'):
+ xbmcvfs.delete("%s%s" % (path, file))
+
+ # Clean up the video nodes
+ import shutil
+ path = xbmc.translatePath("special://profile/library/video/").decode('utf-8')
+ dirs, files = xbmcvfs.listdir(path)
+ for dir in dirs:
+ if dir.startswith('Emby'):
+ shutil.rmtree("%s%s" % (path, dir))
+ for file in files:
+ if file.startswith('emby'):
+ xbmcvfs.delete("%s%s" % (path, file))
+
+ # Wipe the kodi databases
+ logMsg("EMBY", "Resetting the Kodi video database.")
+ connection = kodiSQL('video')
+ cursor = connection.cursor()
+ cursor.execute('SELECT tbl_name FROM sqlite_master WHERE type="table"')
+ rows = cursor.fetchall()
+ for row in rows:
+ tablename = row[0]
+ if tablename != "version":
+ cursor.execute("DELETE FROM " + tablename)
+ connection.commit()
+ cursor.close()
+
+ if settings('disableMusic') != "true":
+ logMsg("EMBY", "Resetting the Kodi music database.")
+ connection = kodiSQL('music')
+ cursor = connection.cursor()
+ cursor.execute('SELECT tbl_name FROM sqlite_master WHERE type="table"')
+ rows = cursor.fetchall()
+ for row in rows:
+ tablename = row[0]
+ if tablename != "version":
+ cursor.execute("DELETE FROM " + tablename)
+ connection.commit()
+ cursor.close()
+
+ # Wipe the emby database
+ logMsg("EMBY", "Resetting the Emby database.")
+ connection = kodiSQL('emby')
+ cursor = connection.cursor()
+ cursor.execute('SELECT tbl_name FROM sqlite_master WHERE type="table"')
+ rows = cursor.fetchall()
+ for row in rows:
+ tablename = row[0]
+ if tablename != "version":
+ cursor.execute("DELETE FROM " + tablename)
+ connection.commit()
+ cursor.close()
-def prettifyXml(elem):
- rough_string = etree.tostring(elem, "utf-8")
- reparsed = minidom.parseString(rough_string)
- return reparsed.toprettyxml(indent="\t")
+ # reset the install run flag
+ settings('SyncInstallRunDone', value="false")
+
+ # Remove emby info
+ resp = dialog.yesno("Warning", "Reset all Emby Addon settings?")
+ if resp == 1:
+ # Delete the settings
+ addon = xbmcaddon.Addon()
+ addondir = xbmc.translatePath(addon.getAddonInfo('profile')).decode('utf-8')
+ dataPath = "%ssettings.xml" % addondir
+ xbmcvfs.delete(dataPath)
+ logMsg("EMBY", "Deleting: settings.xml", 1)
+
+ dialog.ok(
+ heading="Emby for Kodi",
+ line1="Database reset has completed, Kodi will now restart to apply the changes.")
+ xbmc.executebuiltin('RestartApp')
def startProfiling():
+
pr = cProfile.Profile()
pr.enable()
- return pr
+ return pr
+
def stopProfiling(pr, profileName):
+
pr.disable()
ps = pstats.Stats(pr)
- addondir = xbmc.translatePath(xbmcaddon.Addon(id='plugin.video.emby').getAddonInfo('profile'))
+ profiles = xbmc.translatePath("%sprofiles/"
+ % xbmcaddon.Addon().getAddonInfo('profile')).decode('utf-8')
+
+ if not xbmcvfs.exists(profiles):
+ # Create the profiles folder
+ xbmcvfs.mkdir(profiles)
+
+ timestamp = time.strftime("%Y-%m-%d %H-%M-%S")
+ profile = "%s%s_profile_(%s).tab" % (profiles, profileName, timestamp)
- fileTimeStamp = time.strftime("%Y-%m-%d %H-%M-%S")
- tabFileNamepath = os.path.join(addondir, "profiles")
- tabFileName = os.path.join(addondir, "profiles" , profileName + "_profile_(" + fileTimeStamp + ").tab")
-
- if not xbmcvfs.exists(tabFileNamepath):
- xbmcvfs.mkdir(tabFileNamepath)
-
- f = open(tabFileName, 'wb')
+ f = open(profile, 'wb')
f.write("NumbCalls\tTotalTime\tCumulativeTime\tFunctionName\tFileName\r\n")
for (key, value) in ps.stats.items():
(filename, count, func_name) = key
(ccalls, ncalls, total_time, cumulative_time, callers) = value
try:
- f.write(str(ncalls) + "\t" + "{:10.4f}".format(total_time) + "\t" + "{:10.4f}".format(cumulative_time) + "\t" + func_name + "\t" + filename + "\r\n")
+ f.write(
+ "%s\t%s\t%s\t%s\t%s\r\n"
+ % (ncalls, "{:10.4f}".format(total_time),
+ "{:10.4f}".format(cumulative_time), func_name, filename))
except ValueError:
- f.write(str(ncalls) + "\t" + "{0}".format(total_time) + "\t" + "{0}".format(cumulative_time) + "\t" + func_name + "\t" + filename + "\r\n")
+ f.write(
+ "%s\t%s\t%s\t%s\t%s\r\n"
+ % (ncalls, "{0}".format(total_time),
+ "{0}".format(cumulative_time), func_name, filename))
f.close()
-def indent(elem, level=0):
- # Prettify xml trees
- i = "\n" + level*" "
- if len(elem):
- if not elem.text or not elem.text.strip():
- elem.text = i + " "
- if not elem.tail or not elem.tail.strip():
- elem.tail = i
- for elem in elem:
- indent(elem, level+1)
- if not elem.tail or not elem.tail.strip():
- elem.tail = i
- else:
- if level and (not elem.tail or not elem.tail.strip()):
- elem.tail = i
-
-def createSources():
- # To make Master lock compatible
- path = xbmc.translatePath("special://profile/").decode("utf-8")
- xmlpath = "%ssources.xml" % path
-
- if xbmcvfs.exists(xmlpath):
- # Modify the existing file
- try:
- xmlparse = etree.parse(xmlpath)
- except:
- root = etree.Element('sources')
- else:
- root = xmlparse.getroot()
-
- video = root.find('video')
- if video is None:
- video = etree.SubElement(root, 'video')
- else:
- # We need to create the file
- root = etree.Element('sources')
- video = etree.SubElement(root, 'video')
-
-
- # Add elements
- etree.SubElement(video, 'default', attrib={'pathversion': "1"})
-
- # First dummy source
- source_one = etree.SubElement(video, 'source')
- etree.SubElement(source_one, 'name').text = "Emby"
- etree.SubElement(source_one, 'path', attrib={'pathversion': "1"}).text = (
-
- "smb://embydummy/dummypath1/"
- )
- etree.SubElement(source_one, 'allowsharing').text = "true"
-
- # Second dummy source
- source_two = etree.SubElement(video, 'source')
- etree.SubElement(source_two, 'name').text = "Emby"
- etree.SubElement(source_two, 'path', attrib={'pathversion': "1"}).text = (
-
- "smb://embydummy/dummypath2/"
- )
- etree.SubElement(source_two, 'allowsharing').text = "true"
-
- try:
- indent(root)
- except:pass
- etree.ElementTree(root).write(xmlpath)
-
-def pathsubstitution(add=True):
-
- path = xbmc.translatePath('special://userdata').decode('utf-8')
- xmlpath = "%sadvancedsettings.xml" % path
- xmlpathexists = xbmcvfs.exists(xmlpath)
-
- # original address
- originalServer = settings('ipaddress')
- originalPort = settings('port')
- originalHttp = settings('https') == "true"
-
- if originalHttp:
- originalHttp = "https"
- else:
- originalHttp = "http"
-
- # Process add or deletion
- if add:
- # second address
- secondServer = settings('secondipaddress')
- secondPort = settings('secondport')
- secondHttp = settings('secondhttps') == "true"
-
- if secondHttp:
- secondHttp = "https"
- else:
- secondHttp = "http"
-
- logMsg("EMBY", "Original address: %s://%s:%s, alternate is: %s://%s:%s" % (originalHttp, originalServer, originalPort, secondHttp, secondServer, secondPort), 1)
-
- if xmlpathexists:
- # we need to modify the file.
- try:
- xmlparse = etree.parse(xmlpath)
- except: # Document is blank
- root = etree.Element('advancedsettings')
- else:
- root = xmlparse.getroot()
-
- pathsubs = root.find('pathsubstitution')
- if pathsubs is None:
- pathsubs = etree.SubElement(root, 'pathsubstitution')
- else:
- # we need to create the file.
- root = etree.Element('advancedsettings')
- pathsubs = etree.SubElement(root, 'pathsubstitution')
-
- substitute = etree.SubElement(pathsubs, 'substitute')
- # From original address
- etree.SubElement(substitute, 'from').text = "%s://%s:%s" % (originalHttp, originalServer, originalPort)
- # To secondary address
- etree.SubElement(substitute, 'to').text = "%s://%s:%s" % (secondHttp, secondServer, secondPort)
-
- etree.ElementTree(root).write(xmlpath)
- settings('pathsub', "true")
-
- else: # delete the path substitution, we don't need it anymore.
- logMsg("EMBY", "Alternate address is disabled, removing path substitution for: %s://%s:%s" % (originalHttp, originalServer, originalPort), 1)
-
- xmlparse = etree.parse(xmlpath)
- root = xmlparse.getroot()
-
- iterator = root.getiterator("pathsubstitution")
-
- for substitutes in iterator:
- for substitute in substitutes:
- frominsert = substitute.find(".//from").text == "%s://%s:%s" % (originalHttp, originalServer, originalPort)
-
- if frominsert:
- # Found a match, in case there's more than one substitution.
- substitutes.remove(substitute)
-
- etree.ElementTree(root).write(xmlpath)
- settings('pathsub', "false")
-
-
-def settings(setting, value = None):
- # Get or add addon setting
- addon = xbmcaddon.Addon()
- if value:
- addon.setSetting(setting, value)
- else:
- return addon.getSetting(setting)
-
-def window(property, value = None, clear = False):
- # Get or set window property
- WINDOW = xbmcgui.Window(10000)
- if clear:
- WINDOW.clearProperty(property)
- elif value:
- WINDOW.setProperty(property, value)
- else:
- return WINDOW.getProperty(property)
-
-def normalize_string(text):
- # For theme media, do not modify unless
- # modified in TV Tunes
- text = text.replace(":", "")
- text = text.replace("/", "-")
- text = text.replace("\\", "-")
- text = text.replace("<", "")
- text = text.replace(">", "")
- text = text.replace("*", "")
- text = text.replace("?", "")
- text = text.replace('|', "")
- text = text.strip()
- # Remove dots from the last character as windows can not have directories
- # with dots at the end
- text = text.rstrip('.')
- text = unicodedata.normalize('NFKD', unicode(text, 'utf-8')).encode('ascii', 'ignore')
-
- return text
-
def normalize_nodes(text):
# For video nodes
text = text.replace(":", "")
@@ -327,99 +265,223 @@ def normalize_nodes(text):
return text
-def reloadProfile():
- # Useful to reload the add-on without restarting Kodi.
- profile = xbmc.getInfoLabel('System.ProfileName')
- xbmc.executebuiltin("LoadProfile(%s)" % profile)
-
+def normalize_string(text):
+ # For theme media, do not modify unless
+ # modified in TV Tunes
+ text = text.replace(":", "")
+ text = text.replace("/", "-")
+ text = text.replace("\\", "-")
+ text = text.replace("<", "")
+ text = text.replace(">", "")
+ text = text.replace("*", "")
+ text = text.replace("?", "")
+ text = text.replace('|', "")
+ text = text.strip()
+ # Remove dots from the last character as windows can not have directories
+ # with dots at the end
+ text = text.rstrip('.')
+ text = unicodedata.normalize('NFKD', unicode(text, 'utf-8')).encode('ascii', 'ignore')
-def reset():
+ return text
- WINDOW = xbmcgui.Window( 10000 )
- return_value = xbmcgui.Dialog().yesno("Warning", "Are you sure you want to reset your local Kodi database?")
+def indent(elem, level=0):
+ # Prettify xml trees
+ i = "\n" + level*" "
+ if len(elem):
+ if not elem.text or not elem.text.strip():
+ elem.text = i + " "
+ if not elem.tail or not elem.tail.strip():
+ elem.tail = i
+ for elem in elem:
+ indent(elem, level+1)
+ if not elem.tail or not elem.tail.strip():
+ elem.tail = i
+ else:
+ if level and (not elem.tail or not elem.tail.strip()):
+ elem.tail = i
- if return_value == 0:
- return
+def sourcesXML():
+ # To make Master lock compatible
+ path = xbmc.translatePath("special://profile/").decode('utf-8')
+ xmlpath = "%ssources.xml" % path
- # Because the settings dialog could be open
- # it seems to override settings so we need to close it before we reset settings.
- xbmc.executebuiltin("Dialog.Close(all,true)")
-
- #cleanup video nodes
- import shutil
- path = "special://profile/library/video/"
- if xbmcvfs.exists(path):
- allDirs, allFiles = xbmcvfs.listdir(path)
- for dir in allDirs:
- if dir.startswith("Emby "):
- shutil.rmtree(xbmc.translatePath("special://profile/library/video/" + dir))
- for file in allFiles:
- if file.startswith("emby"):
- xbmcvfs.delete(path + file)
-
- settings('SyncInstallRunDone', "false")
-
- # Ask if user information should be deleted too.
- return_user = xbmcgui.Dialog().yesno("Warning", "Reset all Emby Addon settings?")
- if return_user == 1:
- WINDOW.setProperty('deletesettings', "true")
- addon = xbmcaddon.Addon()
- addondir = xbmc.translatePath(addon.getAddonInfo('profile')).decode('utf-8')
- dataPath = "%ssettings.xml" % addondir
- xbmcvfs.delete(dataPath)
- logMsg("EMBY", "Deleting: settings.xml", 1)
-
- # first stop any db sync
- WINDOW.setProperty("SyncDatabaseShouldStop", "true")
-
- count = 0
- while(WINDOW.getProperty("SyncDatabaseRunning") == "true"):
- xbmc.log("Sync Running, will wait : " + str(count))
- count += 1
- if(count > 10):
- dialog = xbmcgui.Dialog()
- dialog.ok('Warning', 'Could not stop DB sync, you should try again.')
- return
- xbmc.sleep(1000)
-
- # delete video db table data
- print "Doing Video DB Reset"
- connection = KodiSQL("video")
- cursor = connection.cursor( )
- cursor.execute('SELECT tbl_name FROM sqlite_master WHERE type="table"')
- rows = cursor.fetchall()
- for row in rows:
- tableName = row[0]
- if(tableName != "version"):
- cursor.execute("DELETE FROM " + tableName)
- cursor.execute("DROP TABLE IF EXISTS emby")
- connection.commit()
- cursor.close()
-
- if settings('enableMusicSync') == "true":
- # delete video db table data
- print "Doing Music DB Reset"
- connection = KodiSQL("music")
- cursor = connection.cursor( )
- cursor.execute('SELECT tbl_name FROM sqlite_master WHERE type="table"')
- rows = cursor.fetchall()
- for row in rows:
- tableName = row[0]
- if(tableName != "version"):
- cursor.execute("DELETE FROM " + tableName)
- cursor.execute("DROP TABLE IF EXISTS emby")
- connection.commit()
- cursor.close()
+ try:
+ xmlparse = etree.parse(xmlpath)
+ except: # Document is blank or missing
+ root = etree.Element('sources')
+ else:
+ root = xmlparse.getroot()
-
- # reset the install run flag
- #settings('SyncInstallRunDone', "false")
- #WINDOW.setProperty("SyncInstallRunDone", "false")
+
+ video = root.find('video')
+ if video is None:
+ video = etree.SubElement(root, 'video')
+ etree.SubElement(video, 'default', attrib={'pathversion': "1"})
+
+ # Add elements
+ for i in range(1, 3):
+
+ for source in root.findall('.//path'):
+ if source.text == "smb://embydummy/dummypath%s/" % i:
+ # Already there, skip
+ break
+ else:
+ source = etree.SubElement(video, 'source')
+ etree.SubElement(source, 'name').text = "Emby"
+ etree.SubElement(source, 'path', attrib={'pathversion': "1"}).text = (
+
+ "smb://embydummy/dummypath%s/" % i
+ )
+ etree.SubElement(source, 'allowsharing').text = "true"
+ # Prettify and write to file
+ try:
+ indent(root)
+ except: pass
+ etree.ElementTree(root).write(xmlpath)
+
+def passwordsXML():
+
+ # To add network credentials
+ path = xbmc.translatePath("special://userdata/").decode('utf-8')
+ xmlpath = "%spasswords.xml" % path
+
+ try:
+ xmlparse = etree.parse(xmlpath)
+ except: # Document is blank or missing
+ root = etree.Element('passwords')
+ else:
+ root = xmlparse.getroot()
dialog = xbmcgui.Dialog()
- # Reload would work instead of restart since the add-on is a service.
- #dialog.ok('Emby Reset', 'Database reset has completed, Kodi will now restart to apply the changes.')
- #WINDOW.clearProperty("SyncDatabaseShouldStop")
- #reloadProfile()
- dialog.ok('Emby Reset', 'Database reset has completed, Kodi will now restart to apply the changes.')
- xbmc.executebuiltin("RestartApp")
\ No newline at end of file
+ credentials = settings('networkCreds')
+ if credentials:
+ # Present user with options
+ option = dialog.select("Modify/Remove network credentials", ["Modify", "Remove"])
+
+ if option < 0:
+ # User cancelled dialog
+ return
+
+ elif option == 1:
+ # User selected remove
+ iterator = root.getiterator('passwords')
+
+ for paths in iterator:
+ for path in paths:
+ if path.find('.//from').text == "smb://%s/" % credentials:
+ paths.remove(path)
+ logMsg("EMBY", "Successfully removed credentials for: %s"
+ % credentials, 1)
+ etree.ElementTree(root).write(xmlpath)
+ break
+ else:
+ logMsg("EMBY", "Failed to find saved server: %s in passwords.xml" % credentials, 1)
+
+ settings('networkCreds', value="")
+ xbmcgui.Dialog().notification(
+ heading="Emby for Kodi",
+ message="%s removed from passwords.xml!" % credentials,
+ icon="special://home/addons/plugin.video.emby/icon.png",
+ time=1000,
+ sound=False)
+ return
+
+ elif option == 0:
+ # User selected to modify
+ server = dialog.input("Modify the computer name or ip address", credentials)
+ if not server:
+ return
+ else:
+ # No credentials added
+ dialog.ok(
+ heading="Network credentials",
+ line1= (
+ "Input the server name or IP address as indicated in your emby library paths. "
+ 'For example, the server name: \\\\SERVER-PC\\path\\ is "SERVER-PC".'))
+ server = dialog.input("Enter the server name or IP address", settings('ipaddress'))
+ if not server:
+ return
+
+ # Network username
+ user = dialog.input("Enter the network username")
+ if not user:
+ return
+ # Network password
+ password = dialog.input(
+ heading="Enter the network password",
+ option=xbmcgui.ALPHANUM_HIDE_INPUT)
+ if not password:
+ return
+
+ # Add elements
+ for path in root.findall('.//path'):
+ if path.find('.//from').text.lower() == "smb://%s/" % server.lower():
+ # Found the server, rewrite credentials
+ path.find('.//to').text = "smb://%s:%s@%s/" % (user, password, server)
+ break
+ else:
+ # Server not found, add it.
+ path = etree.SubElement(root, 'path')
+ etree.SubElement(path, 'from', attrib={'pathversion': "1"}).text = "smb://%s/" % server
+ topath = "smb://%s:%s@%s/" % (user, password, server)
+ etree.SubElement(path, 'to', attrib={'pathversion': "1"}).text = topath
+ # Force Kodi to see the credentials without restarting
+ xbmcvfs.exists(topath)
+
+ # Add credentials
+ settings('networkCreds', value="%s" % server)
+ logMsg("EMBY", "Added server: %s to passwords.xml" % server, 1)
+ # Prettify and write to file
+ try:
+ indent(root)
+ except: pass
+ etree.ElementTree(root).write(xmlpath)
+
+ dialog.notification(
+ heading="Emby for Kodi",
+ message="%s added to passwords.xml!" % server,
+ icon="special://home/addons/plugin.video.emby/icon.png",
+ time=1000,
+ sound=False)
+
+def playlistXSP(mediatype, tagname, viewtype="", delete=False):
+ # Tagname is in unicode - actions: add or delete
+ tagname = tagname.encode('utf-8')
+ cleantagname = normalize_nodes(tagname)
+ path = xbmc.translatePath("special://profile/playlists/video/").decode('utf-8')
+ if viewtype == "mixed":
+ plname = "%s - %s" % (tagname, mediatype)
+ xsppath = "%sEmby %s - %s.xsp" % (path, cleantagname, mediatype)
+ else:
+ plname = tagname
+ xsppath = "%sEmby %s.xsp" % (path, cleantagname)
+
+ # Create the playlist directory
+ if not xbmcvfs.exists(path):
+ xbmcvfs.mkdirs(path)
+
+ # Only add the playlist if it doesn't already exists
+ if xbmcvfs.exists(xsppath):
+
+ if delete:
+ xbmcvfs.delete(xsppath)
+ logMsg("EMBY", "Successfully removed playlist: %s." % tagname, 1)
+
+ return
+
+ # Using write process since there's no guarantee the xml declaration works with etree
+ itemtypes = {
+ 'homevideos': "movies"
+ }
+ f = open(xsppath, 'w')
+ f.write(
+ '\n'
+ '\n\t'
+ 'Emby %s\n\t'
+ 'all\n\t'
+ '\n\t\t'
+ '%s\n\t'
+ ''
+ % (itemtypes.get(mediatype, mediatype), plname, tagname))
+ f.close()
+ logMsg("EMBY", "Successfully added playlist: %s" % tagname)
\ No newline at end of file
diff --git a/resources/lib/VideoNodes.py b/resources/lib/VideoNodes.py
index 056ee13f..1dc9d5a6 100644
--- a/resources/lib/VideoNodes.py
+++ b/resources/lib/VideoNodes.py
@@ -1,466 +1,344 @@
-#################################################################################################
-# VideoNodes - utils to create video nodes listings in kodi for the emby addon
+# -*- coding: utf-8 -*-
+
#################################################################################################
-
-import xbmc
-import xbmcgui
-import xbmcaddon
-import xbmcvfs
-import json
-import os
import shutil
-#import common elementree because cElementree has issues with kodi
import xml.etree.ElementTree as etree
-import Utils as utils
+import xbmc
+import xbmcaddon
+import xbmcvfs
-from ReadEmbyDB import ReadEmbyDB
-WINDOW = xbmcgui.Window(10000)
+import clientinfo
+import utils
-addonSettings = xbmcaddon.Addon()
-language = addonSettings.getLocalizedString
+#################################################################################################
-class VideoNodes():
-
-
- def buildVideoNodeForView(self, tagname, type, windowPropId):
- #this method will build a video node for a particular Emby view (= tag in kodi)
- #we set some window props here to for easy future reference and to be used in skins (for easy access only)
- tagname_normalized = utils.normalize_nodes(tagname.encode('utf-8'))
-
- libraryPath = xbmc.translatePath("special://profile/library/video/Emby - %s/" %tagname_normalized)
- kodiVersion = 14
- if xbmc.getInfoLabel("System.BuildVersion").startswith("15") or xbmc.getInfoLabel("System.BuildVersion").startswith("16"):
- kodiVersion = 15
-
- #create tag node - index
- xbmcvfs.mkdir(libraryPath)
- nodefile = os.path.join(libraryPath, "index.xml")
- root = etree.Element("node", {"order":"0"})
- etree.SubElement(root, "label").text = tagname
- etree.SubElement(root, "icon").text = "special://home/addons/plugin.video.emby/icon.png"
- path = "library://video/Emby - %s/" %tagname_normalized
- WINDOW.setProperty("Emby.nodes.%s.index" %str(windowPropId),path)
- try:
- etree.ElementTree(root).write(nodefile, xml_declaration=True)
- except:
- etree.ElementTree(root).write(nodefile)
-
- #create tag node - all items
- nodefile = os.path.join(libraryPath, tagname_normalized + "_all.xml")
- root = etree.Element("node", {"order":"1", "type":"filter"})
- etree.SubElement(root, "label").text = tagname
- etree.SubElement(root, "match").text = "all"
- etree.SubElement(root, "content").text = type
- etree.SubElement(root, "icon").text = "special://home/addons/plugin.video.emby/icon.png"
- etree.SubElement(root, "order", {"direction":"ascending"}).text = "sorttitle"
- Rule = etree.SubElement(root, "rule", {"field":"tag","operator":"is"})
- WINDOW.setProperty("Emby.nodes.%s.title" %str(windowPropId),tagname)
- path = "library://video/Emby - %s/%s_all.xml"%(tagname_normalized,tagname_normalized)
- WINDOW.setProperty("Emby.nodes.%s.path" %str(windowPropId),"ActivateWindow(Video,%s,return)"%path)
- WINDOW.setProperty("Emby.nodes.%s.content" %str(windowPropId),path)
- WINDOW.setProperty("Emby.nodes.%s.type" %str(windowPropId),type)
- etree.SubElement(Rule, "value").text = tagname
- try:
- etree.ElementTree(root).write(nodefile, xml_declaration=True)
- except:
- etree.ElementTree(root).write(nodefile)
-
- #create tag node - recent items
- nodefile = os.path.join(libraryPath, tagname_normalized + "_recent.xml")
- root = etree.Element("node", {"order":"2", "type":"filter"})
- if type == "tvshows":
- label = language(30170)
+
+class VideoNodes(object):
+
+
+ def __init__(self):
+
+ clientInfo = clientinfo.ClientInfo()
+ self.addonName = clientInfo.getAddonName()
+
+ self.kodiversion = int(xbmc.getInfoLabel("System.BuildVersion")[:2])
+
+ def logMsg(self, msg, lvl=1):
+
+ className = self.__class__.__name__
+ utils.logMsg("%s %s" % (self.addonName, className), msg, lvl)
+
+
+ def commonRoot(self, order, label, tagname, roottype=1):
+
+ if roottype == 0:
+ # Index
+ root = etree.Element('node', attrib={'order': "%s" % order})
+ elif roottype == 1:
+ # Filter
+ root = etree.Element('node', attrib={'order': "%s" % order, 'type': "filter"})
+ etree.SubElement(root, 'match').text = "all"
+ # Add tag rule
+ rule = etree.SubElement(root, 'rule', attrib={'field': "tag", 'operator': "is"})
+ etree.SubElement(rule, 'value').text = tagname
else:
- label = language(30174)
- etree.SubElement(root, "label").text = label
- etree.SubElement(root, "match").text = "all"
- etree.SubElement(root, "content").text = type
- etree.SubElement(root, "icon").text = "special://home/addons/plugin.video.emby/icon.png"
- Rule = etree.SubElement(root, "rule", {"field":"tag","operator":"is"})
- etree.SubElement(Rule, "value").text = tagname
- etree.SubElement(root, "order", {"direction":"descending"}).text = "dateadded"
- #set limit to 25 --> currently hardcoded --> TODO: add a setting for this ?
- etree.SubElement(root, "limit").text = "25"
- #exclude watched items --> currently hardcoded --> TODO: add a setting for this ?
- Rule2 = etree.SubElement(root, "rule", {"field":"playcount","operator":"is"})
- etree.SubElement(Rule2, "value").text = "0"
- WINDOW.setProperty("Emby.nodes.%s.recent.title" %str(windowPropId),label)
- path = "library://video/Emby - %s/%s_recent.xml"%(tagname_normalized,tagname_normalized)
- WINDOW.setProperty("Emby.nodes.%s.recent.path" %str(windowPropId),"ActivateWindow(Video,%s,return)"%path)
- WINDOW.setProperty("Emby.nodes.%s.recent.content" %str(windowPropId),path)
- try:
- etree.ElementTree(root).write(nodefile, xml_declaration=True)
- except:
- etree.ElementTree(root).write(nodefile)
-
- #create tag node - inprogress items
- nodefile = os.path.join(libraryPath, tagname_normalized + "_progress.xml")
- root = etree.Element("node", {"order":"3", "type":"filter"})
- if type == "tvshows":
- label = language(30171)
+ # Folder
+ root = etree.Element('node', attrib={'order': "%s" % order, 'type': "folder"})
+
+ etree.SubElement(root, 'label').text = label
+ etree.SubElement(root, 'icon').text = "special://home/addons/plugin.video.emby/icon.png"
+
+ return root
+
+ def viewNode(self, indexnumber, tagname, mediatype, viewtype, delete=False):
+
+ kodiversion = self.kodiversion
+
+ if mediatype == "homevideos":
+ # Treat homevideos as movies
+ mediatype = "movies"
+
+ tagname = tagname.encode('utf-8')
+ cleantagname = utils.normalize_nodes(tagname)
+ if viewtype == "mixed":
+ dirname = "%s - %s" % (cleantagname, mediatype)
else:
- label = language(30177)
- etree.SubElement(root, "label").text = label
- etree.SubElement(root, "match").text = "all"
- etree.SubElement(root, "content").text = type
- etree.SubElement(root, "icon").text = "special://home/addons/plugin.video.emby/icon.png"
- Rule = etree.SubElement(root, "rule", {"field":"tag","operator":"is"})
- etree.SubElement(Rule, "value").text = tagname
- #set limit to 25 --> currently hardcoded --> TODO: add a setting for this ?
- etree.SubElement(root, "limit").text = "25"
- Rule2 = etree.SubElement(root, "rule", {"field":"inprogress","operator":"true"})
- WINDOW.setProperty("Emby.nodes.%s.inprogress.title" %str(windowPropId),label)
- path = "library://video/Emby - %s/%s_progress.xml"%(tagname_normalized,tagname_normalized)
- WINDOW.setProperty("Emby.nodes.%s.inprogress.path" %str(windowPropId),"ActivateWindow(Video,%s,return)"%path)
- WINDOW.setProperty("Emby.nodes.%s.inprogress.content" %str(windowPropId),path)
- try:
- etree.ElementTree(root).write(nodefile, xml_declaration=True)
- except:
- etree.ElementTree(root).write(nodefile)
+ dirname = cleantagname
- #some movies-only nodes
- if type == "movies":
-
- #unwatched movies
- nodefile = os.path.join(libraryPath, tagname_normalized + "_unwatched.xml")
- root = etree.Element("node", {"order":"4", "type":"filter"})
- label = language(30189)
- etree.SubElement(root, "label").text = label
- etree.SubElement(root, "match").text = "all"
- etree.SubElement(root, "content").text = "movies"
- etree.SubElement(root, "icon").text = "special://home/addons/plugin.video.emby/icon.png"
- Rule = etree.SubElement(root, "rule", {"field":"tag","operator":"is"})
- etree.SubElement(Rule, "value").text = tagname
- Rule = etree.SubElement(root, "rule", {"field":"playcount","operator":"is"})
- etree.SubElement(Rule, "value").text = "0"
- etree.SubElement(root, "order", {"direction":"ascending"}).text = "sorttitle"
- Rule2 = etree.SubElement(root, "rule", {"field":"playcount","operator":"is"})
- etree.SubElement(Rule2, "value").text = "0"
- WINDOW.setProperty("Emby.nodes.%s.unwatched.title" %str(windowPropId),label)
- path = "library://video/Emby - %s/%s_unwatched.xml"%(tagname_normalized,tagname_normalized)
- WINDOW.setProperty("Emby.nodes.%s.unwatched.path" %str(windowPropId),"ActivateWindow(Video,%s,return)"%path)
- WINDOW.setProperty("Emby.nodes.%s.unwatched.content" %str(windowPropId),path)
- try:
- etree.ElementTree(root).write(nodefile, xml_declaration=True)
- except:
- etree.ElementTree(root).write(nodefile)
-
- #sets
- nodefile = os.path.join(libraryPath, tagname_normalized + "_sets.xml")
- root = etree.Element("node", {"order":"9", "type":"filter"})
- label = xbmc.getLocalizedString(20434)
- etree.SubElement(root, "label").text = label
- etree.SubElement(root, "match").text = "all"
- etree.SubElement(root, "group").text = "sets"
- etree.SubElement(root, "content").text = type
- etree.SubElement(root, "icon").text = "special://home/addons/plugin.video.emby/icon.png"
- etree.SubElement(root, "order", {"direction":"ascending"}).text = "sorttitle"
- Rule = etree.SubElement(root, "rule", {"field":"tag","operator":"is"})
- etree.SubElement(Rule, "value").text = tagname
- WINDOW.setProperty("Emby.nodes.%s.sets.title" %str(windowPropId),label)
- path = "library://video/Emby - %s/%s_sets.xml"%(tagname_normalized,tagname_normalized)
- WINDOW.setProperty("Emby.nodes.%s.sets.path" %str(windowPropId),"ActivateWindow(Video,%s,return)"%path)
- WINDOW.setProperty("Emby.nodes.%s.sets.content" %str(windowPropId),path)
- try:
- etree.ElementTree(root).write(nodefile, xml_declaration=True)
- except:
- etree.ElementTree(root).write(nodefile)
+ path = xbmc.translatePath("special://profile/library/video/").decode('utf-8')
+ nodepath = xbmc.translatePath(
+ "special://profile/library/video/Emby - %s/" % dirname).decode('utf-8')
- #create tag node - genres
- nodefile = os.path.join(libraryPath, tagname_normalized + "_genres.xml")
- root = etree.Element("node", {"order":"9", "type":"filter"})
- label = xbmc.getLocalizedString(135)
- etree.SubElement(root, "label").text = label
- etree.SubElement(root, "match").text = "all"
- etree.SubElement(root, "group").text = "genres"
- etree.SubElement(root, "content").text = type
- etree.SubElement(root, "icon").text = "special://home/addons/plugin.video.emby/icon.png"
- etree.SubElement(root, "order", {"direction":"ascending"}).text = "sorttitle"
- Rule = etree.SubElement(root, "rule", {"field":"tag","operator":"is"})
- etree.SubElement(Rule, "value").text = tagname
- WINDOW.setProperty("Emby.nodes.%s.genres.title" %str(windowPropId),label)
- path = "library://video/Emby - %s/%s_genres.xml"%(tagname_normalized,tagname_normalized)
- WINDOW.setProperty("Emby.nodes.%s.genres.path" %str(windowPropId),"ActivateWindow(Video,%s,return)"%path)
- WINDOW.setProperty("Emby.nodes.%s.genres.content" %str(windowPropId),path)
-
- try:
- etree.ElementTree(root).write(nodefile, xml_declaration=True)
- except:
- etree.ElementTree(root).write(nodefile)
-
- #create tag node - random items
- nodefile = os.path.join(libraryPath, tagname_normalized + "_random.xml")
- root = etree.Element("node", {"order":"10", "type":"filter"})
- label = language(30229)
- etree.SubElement(root, "label").text = label
- etree.SubElement(root, "match").text = "all"
- etree.SubElement(root, "content").text = type
- etree.SubElement(root, "icon").text = "special://home/addons/plugin.video.emby/icon.png"
- Rule = etree.SubElement(root, "rule", {"field":"tag","operator":"is"})
- etree.SubElement(Rule, "value").text = tagname
- #set limit to 25 --> currently hardcoded --> TODO: add a setting for this ?
- etree.SubElement(root, "limit").text = "25"
- etree.SubElement(root, "order", {"direction":"ascending"}).text = "random"
- WINDOW.setProperty("Emby.nodes.%s.random.title" %str(windowPropId),label)
- path = "library://video/Emby - %s/%s_random.xml"%(tagname_normalized,tagname_normalized)
- WINDOW.setProperty("Emby.nodes.%s.random.path" %str(windowPropId),"ActivateWindow(Video,%s,return)"%path)
- WINDOW.setProperty("Emby.nodes.%s.random.content" %str(windowPropId),path)
- try:
- etree.ElementTree(root).write(nodefile, xml_declaration=True)
- except:
- etree.ElementTree(root).write(nodefile)
-
- #create tag node - recommended items
- nodefile = os.path.join(libraryPath, tagname_normalized + "_recommended.xml")
- root = etree.Element("node", {"order":"10", "type":"filter"})
- label = language(30230)
- etree.SubElement(root, "label").text = label
- etree.SubElement(root, "match").text = "all"
- etree.SubElement(root, "content").text = type
- etree.SubElement(root, "icon").text = "special://home/addons/plugin.video.emby/icon.png"
- Rule = etree.SubElement(root, "rule", {"field":"tag","operator":"is"})
- etree.SubElement(Rule, "value").text = tagname
- Rule2 = etree.SubElement(root, "rule", {"field":"playcount","operator":"is"})
- etree.SubElement(Rule2, "value").text = "0"
- Rule3 = etree.SubElement(root, "rule", {"field":"rating","operator":"greaterthan"})
- etree.SubElement(Rule3, "value").text = "7"
- #set limit to 25 --> currently hardcoded --> TODO: add a setting for this ?
- etree.SubElement(root, "limit").text = "25"
- etree.SubElement(root, "order", {"direction":"descending"}).text = "rating"
- WINDOW.setProperty("Emby.nodes.%s.random.title" %str(windowPropId),label)
- path = "library://video/Emby - %s/%s_recommended.xml"%(tagname_normalized,tagname_normalized)
- WINDOW.setProperty("Emby.nodes.%s.recommended.path" %str(windowPropId),"ActivateWindow(Video,%s,return)"%path)
- WINDOW.setProperty("Emby.nodes.%s.recommended.content" %str(windowPropId),path)
- try:
- etree.ElementTree(root).write(nodefile, xml_declaration=True)
- except:
- etree.ElementTree(root).write(nodefile)
-
- #### TAGS ONLY FOR TV SHOWS COLLECTIONS ####
- if type == "tvshows":
-
- #as from kodi isengard you can use tags for episodes to filter
- #for below isengard we still use the plugin's entrypoint to build a listing
- if kodiVersion == 15:
- #create tag node - recent episodes
- nodefile = os.path.join(libraryPath, tagname_normalized + "_recent_episodes.xml")
- root = etree.Element("node", {"order":"3", "type":"filter"})
- label = language(30175)
- etree.SubElement(root, "label").text = label
- etree.SubElement(root, "match").text = "all"
- etree.SubElement(root, "content").text = "episodes"
- etree.SubElement(root, "icon").text = "special://home/addons/plugin.video.emby/icon.png"
- etree.SubElement(root, "order", {"direction":"descending"}).text = "dateadded"
- Rule = etree.SubElement(root, "rule", {"field":"tag","operator":"is"})
- etree.SubElement(Rule, "value").text = tagname
- #set limit to 25 --> currently hardcoded --> TODO: add a setting for this ?
- etree.SubElement(root, "limit").text = "25"
- #exclude watched items --> currently hardcoded --> TODO: add a setting for this ?
- Rule2 = etree.SubElement(root, "rule", {"field":"playcount","operator":"is"})
- etree.SubElement(Rule2, "value").text = "0"
- WINDOW.setProperty("Emby.nodes.%s.recentepisodes.title" %str(windowPropId),label)
- path = "library://video/Emby - %s/%s_recent_episodes.xml"%(tagname_normalized,tagname_normalized)
- WINDOW.setProperty("Emby.nodes.%s.recentepisodes.path" %str(windowPropId),"ActivateWindow(Video,%s,return)"%path)
- WINDOW.setProperty("Emby.nodes.%s.recentepisodes.content" %str(windowPropId),path)
- try:
- etree.ElementTree(root).write(nodefile, xml_declaration=True)
- except:
- etree.ElementTree(root).write(nodefile)
-
- #create tag node - inprogress episodes
- nodefile = os.path.join(libraryPath, tagname_normalized + "_progress_episodes.xml")
- root = etree.Element("node", {"order":"4", "type":"filter"})
- label = language(30178)
- etree.SubElement(root, "label").text = label
- etree.SubElement(root, "match").text = "all"
- etree.SubElement(root, "content").text = "episodes"
- etree.SubElement(root, "icon").text = "special://home/addons/plugin.video.emby/icon.png"
- Rule = etree.SubElement(root, "rule", {"field":"tag","operator":"is"})
- etree.SubElement(Rule, "value").text = tagname
- #set limit to 25 --> currently hardcoded --> TODO: add a setting for this ?
- etree.SubElement(root, "limit").text = "25"
- Rule2 = etree.SubElement(root, "rule", {"field":"inprogress","operator":"true"})
- WINDOW.setProperty("Emby.nodes.%s.inprogressepisodes.title" %str(windowPropId),label)
- path = "library://video/Emby - %s/%s_progress_episodes.xml"%(tagname_normalized,tagname_normalized)
- WINDOW.setProperty("Emby.nodes.%s.inprogressepisodes.path" %str(windowPropId),"ActivateWindow(Video,%s,return)"%path)
- WINDOW.setProperty("Emby.nodes.%s.inprogressepisodes.content" %str(windowPropId),path)
- try:
- etree.ElementTree(root).write(nodefile, xml_declaration=True)
- except:
- etree.ElementTree(root).write(nodefile)
-
- if kodiVersion == 14:
- #create tag node - recent episodes
- nodefile = os.path.join(libraryPath, tagname_normalized + "_recent_episodes.xml")
- root = etree.Element("node", {"order":"4", "type":"folder"})
- label = language(30175)
- etree.SubElement(root, "label").text = label
- etree.SubElement(root, "content").text = "episodes"
- etree.SubElement(root, "icon").text = "special://home/addons/plugin.video.emby/icon.png"
- path = "plugin://plugin.video.emby/?id=%s&mode=recentepisodes&limit=25" %tagname
- etree.SubElement(root, "path").text = path
- WINDOW.setProperty("Emby.nodes.%s.recentepisodes.title" %str(windowPropId),label)
- path = "library://video/Emby - %s/%s_recent_episodes.xml"%(tagname_normalized,tagname_normalized)
- WINDOW.setProperty("Emby.nodes.%s.recentepisodes.path" %str(windowPropId),"ActivateWindow(Video,%s,return)"%path)
- WINDOW.setProperty("Emby.nodes.%s.recentepisodes.content" %str(windowPropId),path)
- try:
- etree.ElementTree(root).write(nodefile, xml_declaration=True)
- except:
- etree.ElementTree(root).write(nodefile)
-
- #create tag node - inprogress items
- nodefile = os.path.join(libraryPath, tagname_normalized + "_progress_episodes.xml")
- root = etree.Element("node", {"order":"5", "type":"folder"})
- label = language(30178)
- etree.SubElement(root, "label").text = label
- etree.SubElement(root, "content").text = "episodes"
- etree.SubElement(root, "icon").text = "special://home/addons/plugin.video.emby/icon.png"
- path = "plugin://plugin.video.emby/?id=%s&mode=inprogressepisodes&limit=25" %tagname
- etree.SubElement(root, "path").text = path
- WINDOW.setProperty("Emby.nodes.%s.inprogressepisodes.title" %str(windowPropId),label)
- path = "library://video/Emby - %s/%s_progress_episodes.xml"%(tagname_normalized,tagname_normalized)
- WINDOW.setProperty("Emby.nodes.%s.inprogressepisodes.path" %str(windowPropId),"ActivateWindow(Video,%s,return)"%path)
- WINDOW.setProperty("Emby.nodes.%s.inprogressepisodes.content" %str(windowPropId),path)
- try:
- etree.ElementTree(root).write(nodefile, xml_declaration=True)
- except:
- etree.ElementTree(root).write(nodefile)
-
- #create tag node - nextup items
- #for nextup we always use the dynamic content approach with the plugin's entrypoint because it involves a custom query
- nodefile = os.path.join(libraryPath, tagname_normalized + "_nextup_episodes.xml")
- root = etree.Element("node", {"order":"6", "type":"folder"})
- label = language(30179)
- etree.SubElement(root, "label").text = label
- etree.SubElement(root, "content").text = "episodes"
- path = "plugin://plugin.video.emby/?id=%s&mode=nextup&limit=25" %tagname
- etree.SubElement(root, "path").text = path
- etree.SubElement(root, "icon").text = "special://home/addons/plugin.video.emby/icon.png"
- WINDOW.setProperty("Emby.nodes.%s.nextepisodes.title" %str(windowPropId),label)
- path = "library://video/Emby - %s/%s_nextup_episodes.xml"%(tagname_normalized,tagname_normalized)
- WINDOW.setProperty("Emby.nodes.%s.nextepisodes.path" %str(windowPropId),"ActivateWindow(Video,%s,return)"%path)
- WINDOW.setProperty("Emby.nodes.%s.nextepisodes.content" %str(windowPropId),path)
- try:
- etree.ElementTree(root).write(nodefile, xml_declaration=True)
- except:
- etree.ElementTree(root).write(nodefile)
-
- def buildVideoNodesListing(self):
-
- try:
-
- # the library path doesn't exist on all systems
- if not xbmcvfs.exists("special://profile/library/"):
- xbmcvfs.mkdir("special://profile/library")
- if not xbmcvfs.exists("special://profile/library/video/"):
- #we need to copy over the default items
- shutil.copytree(xbmc.translatePath("special://xbmc/system/library/video"), xbmc.translatePath("special://profile/library/video"))
-
- #always cleanup existing Emby video nodes first because we don't want old stuff to stay in there
- path = "special://profile/library/video/"
- if xbmcvfs.exists(path):
- allDirs, allFiles = xbmcvfs.listdir(path)
- for dir in allDirs:
- if dir.startswith("Emby "):
- shutil.rmtree(xbmc.translatePath("special://profile/library/video/" + dir))
- for file in allFiles:
- if file.startswith("emby"):
- xbmcvfs.delete(path + file)
-
- #we build up a listing and set window props for all nodes we created
- #the window props will be used by the main entry point to quickly build up the listing and can be used in skins (like titan) too for quick reference
- #comment marcelveldt: please leave the window props as-is because I will be referencing them in titan skin...
- totalNodesCount = 0
-
- #build the listing for all views
- views_movies = ReadEmbyDB().getCollections("movies")
- if views_movies:
- for view in views_movies:
- title = view.get('title')
- content = view.get('content')
- if content == "mixed":
- title = "%s - Movies" % title
- self.buildVideoNodeForView(title, "movies", totalNodesCount)
- totalNodesCount +=1
-
- views_shows = ReadEmbyDB().getCollections("tvshows")
- if views_shows:
- for view in views_shows:
- title = view.get('title')
- content = view.get('content')
- if content == "mixed":
- title = "%s - TV Shows" % title
- self.buildVideoNodeForView(title, "tvshows", totalNodesCount)
- totalNodesCount +=1
+ # Verify the video directory
+ if not xbmcvfs.exists(path):
+ shutil.copytree(
+ src=xbmc.translatePath("special://xbmc/system/library/video/").decode('utf-8'),
+ dst=xbmc.translatePath("special://profile/library/video/").decode('utf-8'))
+ xbmcvfs.exists(path)
- #create tag node for emby channels
- nodefile = os.path.join(xbmc.translatePath("special://profile/library/video"), "emby_channels.xml")
- root = etree.Element("node", {"order":"1", "type":"folder"})
- label = language(30173)
- etree.SubElement(root, "label").text = label
- etree.SubElement(root, "content").text = "movies"
- etree.SubElement(root, "path").text = "plugin://plugin.video.emby/?id=0&mode=channels"
- etree.SubElement(root, "icon").text = "special://home/addons/plugin.video.emby/icon.png"
- WINDOW.setProperty("Emby.nodes.%s.title" %str(totalNodesCount),label)
- WINDOW.setProperty("Emby.nodes.%s.type" %str(totalNodesCount),"channels")
- path = "library://video/emby_channels.xml"
- WINDOW.setProperty("Emby.nodes.%s.path" %str(totalNodesCount),"ActivateWindow(Video,%s,return)"%path)
- WINDOW.setProperty("Emby.nodes.%s.content" %str(totalNodesCount),path)
- totalNodesCount +=1
- try:
- etree.ElementTree(root).write(nodefile, xml_declaration=True)
- except:
- etree.ElementTree(root).write(nodefile)
-
- #create tag node - favorite shows
- nodefile = os.path.join(xbmc.translatePath("special://profile/library/video"),"emby_favorite_shows.xml")
- root = etree.Element("node", {"order":"1", "type":"filter"})
- label = language(30181)
- etree.SubElement(root, "label").text = label
- etree.SubElement(root, "match").text = "all"
- etree.SubElement(root, "content").text = "tvshows"
- etree.SubElement(root, "icon").text = "special://home/addons/plugin.video.emby/icon.png"
- etree.SubElement(root, "order", {"direction":"ascending"}).text = "sorttitle"
- Rule = etree.SubElement(root, "rule", {"field":"tag","operator":"is"})
- etree.SubElement(Rule, "value").text = "Favorite tvshows" #do not localize the tagname itself
- WINDOW.setProperty("Emby.nodes.%s.title" %str(totalNodesCount),label)
- WINDOW.setProperty("Emby.nodes.%s.type" %str(totalNodesCount),"favourites")
- path = "library://video/emby_favorite_shows.xml"
- WINDOW.setProperty("Emby.nodes.%s.path" %str(totalNodesCount),"ActivateWindow(Video,%s,return)"%path)
- WINDOW.setProperty("Emby.nodes.%s.content" %str(totalNodesCount),path)
- totalNodesCount +=1
- try:
- etree.ElementTree(root).write(nodefile, xml_declaration=True)
- except:
- etree.ElementTree(root).write(nodefile)
-
- #create tag node - favorite movies
- nodefile = os.path.join(xbmc.translatePath("special://profile/library/video"),"emby_favorite_movies.xml")
- root = etree.Element("node", {"order":"1", "type":"filter"})
- label = language(30180)
- etree.SubElement(root, "label").text = label
- etree.SubElement(root, "match").text = "all"
- etree.SubElement(root, "content").text = "movies"
- etree.SubElement(root, "icon").text = "special://home/addons/plugin.video.emby/icon.png"
- etree.SubElement(root, "order", {"direction":"ascending"}).text = "sorttitle"
- Rule = etree.SubElement(root, "rule", {"field":"tag","operator":"is"})
- etree.SubElement(Rule, "value").text = "Favorite movies" #do not localize the tagname itself
- WINDOW.setProperty("Emby.nodes.%s.title" %str(totalNodesCount),label)
- WINDOW.setProperty("Emby.nodes.%s.type" %str(totalNodesCount),"favourites")
- path = "library://video/emby_favorite_movies.xml"
- WINDOW.setProperty("Emby.nodes.%s.path" %str(totalNodesCount),"ActivateWindow(Video,%s,return)"%path)
- WINDOW.setProperty("Emby.nodes.%s.content" %str(totalNodesCount),path)
- totalNodesCount +=1
- try:
- etree.ElementTree(root).write(nodefile, xml_declaration=True)
- except:
- etree.ElementTree(root).write(nodefile)
-
- WINDOW.setProperty("Emby.nodes.total", str(totalNodesCount))
-
+ # Create the node directory
+ if not xbmcvfs.exists(nodepath):
+ # We need to copy over the default items
+ xbmcvfs.mkdirs(nodepath)
+ else:
+ if delete:
+ dirs, files = xbmcvfs.listdir(nodepath)
+ for file in files:
+ xbmcvfs.delete(nodepath + file)
- except Exception as e:
- utils.logMsg("Emby addon","Error while creating videonodes listings, restart required ?")
- print e
\ No newline at end of file
+ self.logMsg("Sucessfully removed videonode: %s." % tagname, 1)
+ return
+
+ # Create index entry
+ nodeXML = "%sindex.xml" % nodepath
+ # Set windows property
+ path = "library://video/Emby - %s/" % dirname
+ for i in range(1, indexnumber):
+ # Verify to make sure we don't create duplicates
+ if utils.window('Emby.nodes.%s.index' % i) == path:
+ return
+
+ utils.window('Emby.nodes.%s.index' % indexnumber, value=path)
+ # Root
+ root = self.commonRoot(order=0, label=dirname, tagname=tagname, roottype=0)
+ try:
+ utils.indent(root)
+ except: pass
+ etree.ElementTree(root).write(nodeXML)
+
+
+ nodetypes = {
+
+ '1': "all",
+ '2': "recent",
+ '3': "recentepisodes",
+ '4': "inprogress",
+ '5': "inprogressepisodes",
+ '6': "unwatched",
+ '7': "nextupepisodes",
+ '8': "sets",
+ '9': "genres",
+ '10': "random",
+ '11': "recommended"
+ }
+ mediatypes = {
+ # label according to nodetype per mediatype
+ 'movies': {
+ '1': tagname,
+ '2': 30174,
+ '4': 30177,
+ '6': 30189,
+ '8': 20434,
+ '9': 135,
+ '10': 30229,
+ '11': 30230},
+
+ 'tvshows': {
+ '1': tagname,
+ '2': 30170,
+ '3': 30175,
+ '4': 30171,
+ '5': 30178,
+ '7': 30179,
+ '9': 135,
+ '10': 30229,
+ '11': 30230},
+ }
+
+ nodes = mediatypes[mediatype]
+ for node in nodes:
+
+ nodetype = nodetypes[node]
+ nodeXML = "%s%s_%s.xml" % (nodepath, cleantagname, nodetype)
+ # Get label
+ stringid = nodes[node]
+ if node != '1':
+ label = utils.language(stringid)
+ if not label:
+ label = xbmc.getLocalizedString(stringid)
+ else:
+ label = stringid
+
+ # Set window properties
+ if nodetype == "nextupepisodes":
+ # Custom query
+ path = "plugin://plugin.video.emby/?id=%s&mode=nextup&limit=25" % tagname
+ elif kodiversion == 14 and nodetype == "recentepisodes":
+ # Custom query
+ path = "plugin://plugin.video.emby/?id=%s&mode=recentepisodes&limit=25" % tagname
+ elif kodiversion == 14 and nodetype == "inprogressepisodes":
+ # Custom query
+ path = "plugin://plugin.video.emby/?id=%s&mode=inprogressepisodes&limit=25"% tagname
+ else:
+ path = "library://video/Emby - %s/%s_%s.xml" % (dirname, cleantagname, nodetype)
+ windowpath = "ActivateWindow(Video, %s, return)" % path
+
+ if nodetype == "all":
+
+ if viewtype == "mixed":
+ templabel = dirname
+ else:
+ templabel = label
+
+ embynode = "Emby.nodes.%s" % indexnumber
+ utils.window('%s.title' % embynode, value=templabel)
+ utils.window('%s.path' % embynode, value=windowpath)
+ utils.window('%s.content' % embynode, value=path)
+ utils.window('%s.type' % embynode, value=mediatype)
+ else:
+ embynode = "Emby.nodes.%s.%s" % (indexnumber, nodetype)
+ utils.window('%s.title' % embynode, value=label)
+ utils.window('%s.path' % embynode, value=windowpath)
+ utils.window('%s.content' % embynode, value=path)
+
+ if xbmcvfs.exists(nodeXML):
+ # Don't recreate xml if already exists
+ continue
+
+
+ # Create the root
+ if nodetype == "nextupepisodes" or (kodiversion == 14 and
+ nodetype in ('recentepisodes', 'inprogressepisodes')):
+ # Folder type with plugin path
+ root = self.commonRoot(order=node, label=label, tagname=tagname, roottype=2)
+ etree.SubElement(root, 'path').text = path
+ etree.SubElement(root, 'content').text = "episodes"
+ else:
+ root = self.commonRoot(order=node, label=label, tagname=tagname)
+ if nodetype in ('recentepisodes', 'inprogressepisodes'):
+ etree.SubElement(root, 'content').text = "episodes"
+ else:
+ etree.SubElement(root, 'content').text = mediatype
+
+ limit = "25"
+ # Elements per nodetype
+ if nodetype == "all":
+ etree.SubElement(root, 'order', {'direction': "ascending"}).text = "sorttitle"
+
+ elif nodetype == "recent":
+ etree.SubElement(root, 'order', {'direction': "descending"}).text = "dateadded"
+ etree.SubElement(root, 'limit').text = limit
+ rule = etree.SubElement(root, 'rule', {'field': "playcount", 'operator': "is"})
+ etree.SubElement(rule, 'value').text = "0"
+
+ elif nodetype == "inprogress":
+ etree.SubElement(root, 'rule', {'field': "inprogress", 'operator': "true"})
+ etree.SubElement(root, 'limit').text = limit
+
+ elif nodetype == "genres":
+ etree.SubElement(root, 'order', {'direction': "ascending"}).text = "sorttitle"
+ etree.SubElement(root, 'group').text = "genres"
+
+ elif nodetype == "unwatched":
+ etree.SubElement(root, 'order', {'direction': "ascending"}).text = "sorttitle"
+ rule = etree.SubElement(root, "rule", {'field': "playcount", 'operator': "is"})
+ etree.SubElement(rule, 'value').text = "0"
+
+ elif nodetype == "sets":
+ etree.SubElement(root, 'order', {'direction': "ascending"}).text = "sorttitle"
+ etree.SubElement(root, 'group').text = "sets"
+
+ elif nodetype == "random":
+ etree.SubElement(root, 'order', {'direction': "ascending"}).text = "random"
+ etree.SubElement(root, 'limit').text = limit
+
+ elif nodetype == "recommended":
+ etree.SubElement(root, 'order', {'direction': "descending"}).text = "rating"
+ etree.SubElement(root, 'limit').text = limit
+ rule = etree.SubElement(root, 'rule', {'field': "playcount", 'operator': "is"})
+ etree.SubElement(rule, 'value').text = "0"
+ rule2 = etree.SubElement(root, 'rule',
+ attrib={'field': "rating", 'operator': "greaterthan"})
+ etree.SubElement(rule2, 'value').text = "7"
+
+ elif nodetype == "recentepisodes":
+ # Kodi Isengard, Jarvis
+ etree.SubElement(root, 'order', {'direction': "descending"}).text = "dateadded"
+ etree.SubElement(root, 'limit').text = limit
+ rule = etree.SubElement(root, 'rule', {'field': "playcount", 'operator': "is"})
+ etree.SubElement(rule, 'value').text = "0"
+
+ elif nodetype == "inprogressepisodes":
+ # Kodi Isengard, Jarvis
+ etree.SubElement(root, 'limit').text = "25"
+ rule = etree.SubElement(root, 'rule',
+ attrib={'field': "inprogress", 'operator':"true"})
+
+ try:
+ utils.indent(root)
+ except: pass
+ etree.ElementTree(root).write(nodeXML)
+
+ def singleNode(self, indexnumber, tagname, mediatype, itemtype):
+
+ tagname = tagname.encode('utf-8')
+ cleantagname = utils.normalize_nodes(tagname)
+ nodepath = xbmc.translatePath("special://profile/library/video/").decode('utf-8')
+ nodeXML = "%semby_%s.xml" % (nodepath, cleantagname)
+ path = "library://video/emby_%s.xml" % (cleantagname)
+ windowpath = "ActivateWindow(Video, %s, return)" % path
+
+ # Create the video node directory
+ if not xbmcvfs.exists(nodepath):
+ # We need to copy over the default items
+ shutil.copytree(
+ src=xbmc.translatePath("special://xbmc/system/library/video").decode('utf-8'),
+ dst=xbmc.translatePath("special://profile/library/video").decode('utf-8'))
+ xbmcvfs.exists(path)
+
+ labels = {
+
+ 'Favorite movies': 30180,
+ 'Favorite tvshows': 30181,
+ 'channels': 30173
+ }
+ label = utils.language(labels[tagname])
+ embynode = "Emby.nodes.%s" % indexnumber
+ utils.window('%s.title' % embynode, value=label)
+ utils.window('%s.path' % embynode, value=windowpath)
+ utils.window('%s.content' % embynode, value=path)
+ utils.window('%s.type' % embynode, value=itemtype)
+
+ if xbmcvfs.exists(nodeXML):
+ # Don't recreate xml if already exists
+ return
+
+ if itemtype == "channels":
+ root = self.commonRoot(order=1, label=label, tagname=tagname, roottype=2)
+ etree.SubElement(root, 'path').text = "plugin://plugin.video.emby/?id=0&mode=channels"
+ else:
+ root = self.commonRoot(order=1, label=label, tagname=tagname)
+ etree.SubElement(root, 'order', {'direction': "ascending"}).text = "sorttitle"
+
+ etree.SubElement(root, 'content').text = mediatype
+
+ try:
+ utils.indent(root)
+ except: pass
+ etree.ElementTree(root).write(nodeXML)
+
+ def clearProperties(self):
+
+ self.logMsg("Clearing nodes properties.", 1)
+ embyprops = 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 embyprops:
+ totalnodes = int(embyprops)
+ for i in range(totalnodes):
+ for prop in propnames:
+ utils.window('Emby.nodes.%s.%s' % (str(i), prop), clear=True)
\ No newline at end of file