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