Revert "Replace"

This reverts commit f28c9fe069.
This commit is contained in:
angelblue05 2015-12-24 13:56:11 -06:00
parent f28c9fe069
commit 08f9add813
10 changed files with 3351 additions and 3682 deletions

View file

@ -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)
try:
self.downloadUrl(url, postBody=data, type="POST")
self.logMsg("Posted capabilities to %s" % self.server, 2)
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)
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}/emby/Users?format=json"
url = "{server}/mediabrowser/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
if result:
for user in result:
username = user['Name'].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")
for additional in additionalUsers:
addUser = additional.decode('utf-8').lower()
if username in addUser:
url = "{server}/mediabrowser/Sessions/%s/Users/%s" % (sessionId, userId)
postdata = {}
self.downloadUrl(url, postBody=postdata, type="POST")
#xbmcgui.Dialog().notification("Success!", "%s added to viewing session" % username, time=1000)
def startSession(self):
self.deviceId = self.clientInfo.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 = {
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}
'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 = {
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}
'Content-type': 'application/json',
'Accept-encoding': 'gzip',
'Accept-Charset': 'UTF-8,*',
'Authorization': auth,
'X-MediaBrowser-Token': token
}
self.logMsg("Header: %s" % header, 2)
return header
def downloadUrl(self, url, postBody=None, type="GET", parameters=None, authenticate=True):
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
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:

File diff suppressed because it is too large Load diff

View file

@ -1,82 +1,72 @@
# -*- 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):
self.clientInfo = clientinfo.ClientInfo()
self.addonName = self.clientInfo.getAddonName()
self.doUtils = downloadutils.DownloadUtils()
self.logMsg("Kodi monitor started.", 1)
def __init__(self, *args, **kwargs):
xbmc.Monitor.__init__(self)
def logMsg(self, msg, lvl = 1):
self.className = self.__class__.__name__
utils.logMsg("%s %s" % (self.addonName, self.className), msg, lvl)
className = self.__class__.__name__
utils.logMsg("%s %s" % ("EMBY", className), msg, int(lvl))
def onScanStarted(self, library):
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):
doUtils = self.doUtils
if method not in ("Playlist.OnAdd"):
self.logMsg("Method: %s Data: %s" % (method, data), 1)
WINDOW = self.WINDOW
downloadUtils = DownloadUtils()
#player started playing an item -
if ("Playlist.OnAdd" in method or "Player.OnPlay" in method):
if data:
data = json.loads(data)
jsondata = json.loads(data)
if jsondata:
if jsondata.has_key("item"):
if jsondata.get("item").has_key("id") and jsondata.get("item").has_key("type"):
id = jsondata.get("item").get("id")
type = jsondata.get("item").get("type")
if (utils.settings('useDirectPaths')=='true' and not type == "song") or (type == "song" and utils.settings('enableMusicSync') == "true"):
if 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)
if type == "song":
connection = utils.KodiSQL('music')
cursor = connection.cursor()
embyid = ReadKodiDB().getEmbyIdByKodiId(id, type, connection, cursor)
cursor.close()
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)
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
@ -84,112 +74,77 @@ class KodiMonitor(xbmc.Monitor):
try:
playurl = xbmc.Player().getPlayingFile()
except RuntimeError:
count += 1
xbmc.sleep(200)
else:
listItem = xbmcgui.ListItem()
playback = pbutils.PlaybackUtils(result)
PlaybackUtils().setProperties(playurl, result, listItem)
if type == "song" and utils.settings('streamMusic') == "true":
utils.window('emby_%s.playmethod' % playurl,
value="DirectStream")
if type == "song" and utils.settings('directstreammusic') == "true":
utils.window('%splaymethod' % playurl, value="DirectStream")
else:
utils.window('emby_%s.playmethod' % playurl,
value="DirectPlay")
# Set properties for player.py
playback.setProperties(playurl, listItem)
finally:
embycursor.close()
utils.window('%splaymethod' % playurl, value="DirectPlay")
count += 1
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":
if method == "VideoLibrary.OnUpdate":
# Triggers 4 times, the following is only for manually marking as watched/unwatched
jsondata = json.loads(data)
try:
kodiid = data['id']
type = data['type']
except (KeyError, TypeError):
self.logMsg("Item is invalid for emby deletion.", 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:
# Send the delete action to the server.
offerDelete = False
self.logMsg("VideoLibrary.OnUpdate: %s" % data, 2)
if prop != "true":
# Set property to prevent the multi triggering
utils.window('Played%s%s' % (type, item), "true")
WriteKodiVideoDB().updatePlayCountFromKodi(item, type, playcount)
if type == "episode" and utils.settings('deleteTV') == "true":
offerDelete = True
elif type == "movie" and utils.settings('deleteMovies') == "true":
offerDelete = True
self.clearProperty(type, item)
if utils.settings('offerDelete') != "true":
# Delete could be disabled, even if the subsetting is enabled.
offerDelete = False
if method == "System.OnWake":
xbmc.sleep(10000) #Allow network to wake up
WINDOW.setProperty("OnWakeSync", "true")
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()
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
url = "{server}/emby/Items/%s?format=json" % itemid
self.logMsg("Deleting request: %s" % itemid)
doUtils.downloadUrl(url, type="DELETE")
finally:
embycursor.close()
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 == "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.")
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")

File diff suppressed because it is too large Load diff

View file

@ -6,156 +6,117 @@ 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):
self.className = self.__class__.__name__
utils.logMsg("%s %s" % (self.addonName, self.className), msg, lvl)
className = self.__class__.__name__
utils.logMsg("%s %s" % (self.addonName, className), msg, int(lvl))
def getPlayUrl(self, server, id, result):
def getPlayUrl(self):
item = self.item
playurl = None
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.isDirectPlay():
if self.isDirectPlay(result,True):
# Try direct play
playurl = self.directPlay(result)
if playurl:
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():
utils.window("%splaymethod" % playurl.encode('utf-8'), value="DirectPlay")
elif self.isDirectStream(result):
# Try direct stream
playurl = self.directStream(result, server, id)
if playurl:
self.logMsg("File is direct streaming.", 1)
playurl = self.directStream()
# Set playmethod property
utils.window('emby_%s.playmethod' % playurl, value="DirectStream")
elif self.isTranscoding():
utils.window("%splaymethod" % playurl, value="DirectStream")
elif self.isTranscoding(result):
# Try transcoding
playurl = self.transcoding(result, server, id)
if playurl:
self.logMsg("File is transcoding.", 1)
playurl = self.transcoding()
# Set playmethod property
utils.window('emby_%s.playmethod' % playurl, value="Transcode")
utils.window("%splaymethod" % playurl, value="Transcode")
return playurl
else: # Error
utils.window("playurlFalse", value="true")
return
def httpPlay(self):
# Audio, Video, Photo
item = self.item
server = self.server
return playurl.encode('utf-8')
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
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:
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)
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")
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)
xbmcgui.Dialog().notification("Emby server", "Enabled play from HTTP in add-on settings.", icon="special://home/addons/plugin.video.emby/icon.png", sound=False)
return False
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('\\\\'):
# 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("\\", "/")
@ -165,120 +126,107 @@ class PlayUtils():
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
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):
def directStream(self, result, server, id, type = "Video"):
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,
@ -305,7 +253,34 @@ class PlayUtils():
# max bit rate supported by server (max signed 32bit integer)
return bitrate.get(videoQuality, 2147483)
def audioSubsPref(self, url):
def fileExists(self, result):
if 'Path' not in result:
# File has no path in server
return False
# Convert Emby path to a path we can verify
path = self.directPlay(result)
try:
pathexists = xbmcvfs.exists(path)
except:
pathexists = False
# Verify the device has access to the direct path
if pathexists:
# Local or Network path
self.logMsg("Path exists.", 2)
return True
elif ":" not in path:
# Give benefit of the doubt for nfs.
self.logMsg("Can't verify path (assumed NFS). Still try direct play.", 2)
return True
else:
self.logMsg("Path is detected as follow: %s. Try direct streaming." % path, 2)
return False
def audioSubsPref(self, url, mediaSources):
# For transcoding only
# Present the list of audio to select from
audioStreamsList = {}
@ -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)

View file

@ -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():
clientInfo = ClientInformation()
doUtils = DownloadUtils()
api = API()
def __init__(self, item):
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):
self.className = self.__class__.__name__
utils.logMsg("%s %s" % (self.addonName, self.className), msg, lvl)
className = self.__class__.__name__
utils.logMsg("%s %s" % (self.addonName, className), msg, int(lvl))
def PLAY(self, result, setup = "service"):
def play(self, itemid, dbid=None):
self.logMsg("Play called.", 1)
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 utils.window('playurlFalse') == "true":
# Playurl failed - set in PlayUtils.py
utils.window('playurlFalse', clear=True)
self.logMsg("Failed to retrieve the playback path/url or dialog was cancelled.", 1)
return xbmcplugin.setResolvedUrl(int(sys.argv[1]), False, listItem)
############### -- SETUP MAIN ITEM ################
# Set listitem and properties for main item
self.logMsg("Returned playurl: %s" % playurl, 1)
listItem.setPath(playurl)
self.setProperties(playurl, result, listItem)
mainArt = API().getArtwork(result, "Primary")
listItem.setThumbnailImage(mainArt)
listItem.setIconImage(mainArt)
if dbid is None:
# Item is not in Kodi database
listitem.setPath(playurl)
self.setProperties(playurl, listitem)
return xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, listitem)
############### ORGANIZE CURRENT PLAYLIST ################
@ -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.setProperties(introPlayurl, intro, introListItem)
self.setListItemProps(server, introId, introListItem, intro)
self.pl.insertintoPlaylist(currentPosition, url=introPlayurl)
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 homeScreen and seektime:
self.logMsg("Play as a widget item.", 1)
self.setListItem(listitem)
xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, listitem)
if not homeScreen and not introsPlaylist:
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)
self.logMsg("Processed as a single item.", 1)
xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, listItem)
elif dummyPlaylist:
# Added a dummy file to the playlist because the first item is going to fail automatically.
self.logMsg("Processed as a playlist. First item is skipped.", 1)
xbmcplugin.setResolvedUrl(int(sys.argv[1]), False, listItem)
else:
self.logMsg("Play as a regular item.", 1)
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']
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 verifyPlaylist(self):
if itemtype == "Episode":
utils.window('%s.refreshid' % embyitem, value=item.get('SeriesId'))
else:
utils.window('%s.refreshid' % embyitem, value=itemid)
playlistitems = '{"jsonrpc": "2.0", "method": "Playlist.GetItems", "params": { "playlistid": 1 }, "id": 1}'
items = xbmc.executeJSONRPC(playlistitems)
self.logMsg(items, 2)
# Append external subtitles to stream
playmethod = utils.window('%s.playmethod' % embyitem)
# Only for direct play and direct stream
subtitles = self.externalSubs(playurl)
if playmethod in ("DirectStream", "Transcode"):
# Direct play automatically appends external
listitem.setSubtitles(subtitles)
def removeFromPlaylist(self, pos):
self.setArtwork(listitem)
playlistremove = '{"jsonrpc": "2.0", "method": "Playlist.Remove", "params": { "playlistid": 1, "position": %d }, "id": 1}' % pos
result = xbmc.executeJSONRPC(playlistremove)
self.logMsg(result, 1)
def externalSubs(self, 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']:
# Direct stream
url = ("%s/Videos/%s/%s/Subtitles/%s/Stream.srt"
% (self.server, itemid, itemid, index))
playmethod = utils.window("%splaymethod" % playurl)
if "DirectPlay" in playmethod:
# Direct play, get direct path
url = PlayUtils().directPlay(stream)
elif "DirectStream" in playmethod: # Direct stream
url = "%s/Videos/%s/%s/Subtitles/%s/Stream.srt" % (server, id, id, index)
# map external subtitles for mapping
mapping[kodiindex] = index
@ -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):
def setProperties(self, playurl, result, listItem):
# Set runtimeticks, type, refresh_id and item_id
id = result.get('Id')
type = result.get('Type', "")
utils.window("%sruntimeticks" % playurl, value=str(result.get('RunTimeTicks')))
utils.window("%stype" % playurl, value=type)
utils.window("%sitem_id" % playurl, value=id)
if type == "Episode":
utils.window("%srefresh_id" % playurl, value=result.get('SeriesId'))
else:
utils.window("%srefresh_id" % playurl, value=id)
if utils.window("%splaymethod" % playurl) != "Transcode":
# Only for direct play and direct stream
# Append external subtitles to stream
subtitleList = self.externalSubs(id, playurl, result['MediaSources'])
listItem.setSubtitles(subtitleList)
def setArt(self, list, name, path):
if name in ("thumb", "fanart_image", "small_poster", "tiny_poster", "medium_landscape", "medium_poster", "small_fanartimage", "medium_fanartimage", "fanart_noindicators"):
list.setProperty(name, path)
else:
list.setArt({name:path})
return list
def setListItemProps(self, server, id, listItem, result):
# Set up item and item info
item = self.item
artwork = self.artwork
api = self.api
allartwork = artwork.getAllArtwork(item, parentInfo=True)
# Set artwork for listitem
arttypes = {
'poster': "Primary",
'tvshow.poster': "Primary",
'clearart': "Art",
'tvshow.clearart': "Art",
'clearlogo': "Logo",
'tvshow.clearlogo': "Logo",
'discart': "Disc",
'fanart_image': "Backdrop",
'landscape': "Thumb"
}
for arttype in arttypes:
art = arttypes[arttype]
if art == "Backdrop":
try: # Backdrop is a list, grab the first backdrop
self.setArtProp(listItem, arttype, allartwork[art][0])
except: pass
else:
self.setArtProp(listItem, arttype, allartwork[art])
def setArtProp(self, listItem, arttype, path):
if arttype in (
'thumb', 'fanart_image', 'small_poster', 'tiny_poster',
'medium_landscape', 'medium_poster', 'small_fanartimage',
'medium_fanartimage', 'fanart_noindicators'):
listItem.setProperty(arttype, path)
else:
listItem.setArt({arttype: path})
def setListItem(self, listItem):
item = self.item
type = item['Type']
API = self.API
people = API.getPeople()
studios = API.getStudios()
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
@ -351,3 +341,122 @@ class PlaybackUtils():
listItem.setProperty('IsFolder', 'false')
listItem.setLabel(metadata['title'])
listItem.setInfo('video', infoLabels=metadata)
# Set artwork for listitem
self.setArt(listItem,'poster', API().getArtwork(result, "Primary"))
self.setArt(listItem,'tvshow.poster', API().getArtwork(result, "SeriesPrimary"))
self.setArt(listItem,'clearart', API().getArtwork(result, "Art"))
self.setArt(listItem,'tvshow.clearart', API().getArtwork(result, "Art"))
self.setArt(listItem,'clearlogo', API().getArtwork(result, "Logo"))
self.setArt(listItem,'tvshow.clearlogo', API().getArtwork(result, "Logo"))
self.setArt(listItem,'discart', API().getArtwork(result, "Disc"))
self.setArt(listItem,'fanart_image', API().getArtwork(result, "Backdrop"))
self.setArt(listItem,'landscape', API().getArtwork(result, "Thumb"))
def seekToPosition(self, seekTo):
# Set a loop to wait for positive confirmation of playback
count = 0
while not xbmc.Player().isPlaying():
count += 1
if count >= 10:
return
else:
xbmc.sleep(500)
# Jump to seek position
count = 0
while xbmc.Player().getTime() < (seekToTime - 5) and count < 11: # only try 10 times
count += 1
xbmc.Player().seekTime(seekTo)
xbmc.sleep(100)
def PLAYAllItems(self, items, startPositionTicks):
self.logMsg("== ENTER: PLAYAllItems ==")
self.logMsg("Items: %s" % items)
doUtils = self.doUtils
playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
playlist.clear()
started = False
for itemId in items:
self.logMsg("Adding Item to playlist: %s" % itemId, 1)
url = "{server}/mediabrowser/Users/{UserId}/Items/%s?format=json" % itemId
result = doUtils.downloadUrl(url)
addition = self.addPlaylistItem(playlist, result)
if not started and addition:
started = True
self.logMsg("Starting Playback Pre", 1)
xbmc.Player().play(playlist)
if not started:
self.logMsg("Starting Playback Post", 1)
xbmc.Player().play(playlist)
# Seek to position
if startPositionTicks:
seekTime = startPositionTicks / 10000000.0
self.seekToPosition(seekTime)
def AddToPlaylist(self, itemIds):
self.logMsg("== ENTER: PLAYAllItems ==")
doUtils = self.doUtils
playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
for itemId in itemIds:
self.logMsg("Adding Item to Playlist: %s" % itemId)
url = "{server}/mediabrowser/Users/{UserId}/Items/%s?format=json" % itemId
result = doUtils.downloadUrl(url)
self.addPlaylistItem(playlist, result)
return playlist
def addPlaylistItem(self, playlist, item):
id = item['Id']
username = utils.window('currUser')
server = utils.window('server%s' % username)
playurl = PlayUtils().getPlayUrl(server, id, item)
if utils.window('playurlFalse') == "true":
# Playurl failed - set in PlayUtils.py
utils.window('playurlFalse', clear=True)
self.logMsg("Failed to retrieve the playback path/url or dialog was cancelled.", 1)
return
self.logMsg("Playurl: %s" % playurl)
thumb = API().getArtwork(item, "Primary")
listItem = xbmcgui.ListItem(path=playurl, iconImage=thumb, thumbnailImage=thumb)
self.setListItemProps(server, id, listItem, item)
self.setProperties(playurl, item, listItem)
playlist.add(playurl, listItem)
# Not currently being used
'''def PLAYAllEpisodes(self, items):
WINDOW = xbmcgui.Window(10000)
username = WINDOW.getProperty('currUser')
userid = WINDOW.getProperty('userId%s' % username)
server = WINDOW.getProperty('server%s' % username)
playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
playlist.clear()
for item in items:
item_url = "{server}/mediabrowser/Users/{UserId}/Items/%s?format=json&ImageTypeLimit=1" % item["Id"]
jsonData = self.downloadUtils.downloadUrl(item_url)
item_data = jsonData
self.addPlaylistItem(playlist, item_data, server, userid)
xbmc.Player().play(playlist)'''

View file

@ -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 ):
# 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,42 +74,31 @@ 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')
@ -119,7 +106,7 @@ class Player(xbmc.Player):
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
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)
offerDelete = utils.settings('offerDelete') == "true"
offerTypeDelete = False
if type == "Episode" and utils.settings('offerDeleteTV') == "true":
offerTypeDelete = True
elif type == "Movie" and utils.settings('offerDeleteMovies') == "true":
offerTypeDelete = True
if percentComplete >= markPlayedAt and offerDelete and offerTypeDelete:
# Make the bigger setting be able to disable option easily.
self.logMsg("Offering deletion for: %s." % itemId, 1)
return_value = xbmcgui.Dialog().yesno("Offer Delete", "Delete %s" % currentFile.split("/")[-1], "on Emby Server?")
if return_value:
# Delete Kodi entry before Emby
listItem = [itemId]
LibrarySync().removefromDB(listItem, True)
# Stop transcoding
if playMethod == "Transcode":
self.logMsg("Transcoding for %s terminated." % itemid, 1)
deviceId = self.clientInfo.getDeviceId()
url = "{server}/emby/Videos/ActiveEncodings?DeviceId=%s" % deviceId
doUtils.downloadUrl(url, type="DELETE")
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")
# 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 percentComplete >= markPlayedAt and offerDelete:
if utils.settings('skipConfirmDelete') != "true":
resp = xbmcgui.Dialog().yesno(
heading="Confirm delete",
line1="Delete file on Emby Server?")
if not resp:
self.logMsg("User skipped deletion.", 1)
continue
url = "{server}/emby/Items/%s?format=json" % itemid
self.logMsg("Deleting request: %s" % itemid)
doUtils.downloadUrl(url, type="DELETE")
self.played_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")

View file

@ -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"
if alternate:
# Alternate host
HTTPS = utils.settings('secondhttps') == "true"
host = utils.settings('secondipaddress')
port = utils.settings('secondport')
else:
# Original host
HTTPS = utils.settings('https') == "true"
# For https support
HTTPS = utils.settings('https')
host = utils.settings('ipaddress')
port = utils.settings('port')
# Alternate host
if alternate:
HTTPS = utils.settings('secondhttps')
host = utils.settings('secondipaddress')
port = utils.settings('secondport')
server = host + ":" + port
if 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)
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
try:
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))
if (self.WINDOW.getProperty("Server_status") != ""):
status = self.WINDOW.getProperty("Server_status")
status = utils.window('emby_serverStatus')
if status:
# Verify the connection status to server
if status == "restricted":
# Parental control is restricting access
self.HasAccess = False
elif status == "401":
# 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")
# 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)
# If user didn't enter a password when prompted
if status == "Stop":
pass
elif (server != "") and (username != ""):
self.logMsg("Server found: %s" % server)
self.logMsg("Username found: %s" % username)
self.auth = True
if self.stopClient == True:
# If stopping the client didn't work
if self.stopClient == True:
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

View file

@ -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):
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
if clear:
WINDOW.clearProperty(property)
elif value is not None:
WINDOW.setProperty(property, value)
else:
return WINDOW.getProperty(property)
def KodiSQL(type="video"):
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')
@ -112,139 +95,218 @@ def getKodiMusicDBPath():
% 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):
pr.disable()
ps = pstats.Stats(pr)
profiles = xbmc.translatePath("%sprofiles/"
% xbmcaddon.Addon().getAddonInfo('profile')).decode('utf-8')
addondir = xbmc.translatePath(xbmcaddon.Addon(id='plugin.video.emby').getAddonInfo('profile'))
if not xbmcvfs.exists(profiles):
# Create the profiles folder
xbmcvfs.mkdir(profiles)
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")
timestamp = time.strftime("%Y-%m-%d %H-%M-%S")
profile = "%s%s_profile_(%s).tab" % (profiles, profileName, timestamp)
if not xbmcvfs.exists(tabFileNamepath):
xbmcvfs.mkdir(tabFileNamepath)
f = open(profile, 'wb')
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')
return text
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 sourcesXML():
# To make Master lock compatible
path = xbmc.translatePath("special://profile/").decode('utf-8')
xmlpath = "%ssources.xml" % path
try:
xmlparse = etree.parse(xmlpath)
except: # Document is blank or missing
root = etree.Element('sources')
else:
root = xmlparse.getroot()
def reloadProfile():
# Useful to reload the add-on without restarting Kodi.
profile = xbmc.getInfoLabel('System.ProfileName')
xbmc.executebuiltin("LoadProfile(%s)" % profile)
video = root.find('video')
if video is None:
video = etree.SubElement(root, 'video')
etree.SubElement(video, 'default', attrib={'pathversion': "1"})
def reset():
# Add elements
for i in range(1, 3):
WINDOW = xbmcgui.Window( 10000 )
return_value = xbmcgui.Dialog().yesno("Warning", "Are you sure you want to reset your local Kodi database?")
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 = (
if return_value == 0:
return
"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)
# 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)")
def passwordsXML():
#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)
# To add network credentials
path = xbmc.translatePath("special://userdata/").decode('utf-8')
xmlpath = "%spasswords.xml" % path
settings('SyncInstallRunDone', "false")
try:
xmlparse = etree.parse(xmlpath)
except: # Document is blank or missing
root = etree.Element('passwords')
else:
root = xmlparse.getroot()
# Ask if user information should be deleted too.
return_user = xbmcgui.Dialog().yesno("Warning", "Reset all Emby Addon settings?")
if return_user == 1:
WINDOW.setProperty('deletesettings', "true")
addon = xbmcaddon.Addon()
addondir = xbmc.translatePath(addon.getAddonInfo('profile')).decode('utf-8')
dataPath = "%ssettings.xml" % addondir
xbmcvfs.delete(dataPath)
logMsg("EMBY", "Deleting: settings.xml", 1)
# first stop any db sync
WINDOW.setProperty("SyncDatabaseShouldStop", "true")
count = 0
while(WINDOW.getProperty("SyncDatabaseRunning") == "true"):
xbmc.log("Sync Running, will wait : " + str(count))
count += 1
if(count > 10):
dialog = xbmcgui.Dialog()
dialog.ok('Warning', 'Could not stop DB sync, you should try again.')
return
xbmc.sleep(1000)
# delete video db table data
print "Doing Video DB Reset"
connection = KodiSQL("video")
cursor = connection.cursor( )
cursor.execute('SELECT tbl_name FROM sqlite_master WHERE type="table"')
rows = cursor.fetchall()
for row in rows:
tableName = row[0]
if(tableName != "version"):
cursor.execute("DELETE FROM " + tableName)
cursor.execute("DROP TABLE IF EXISTS emby")
connection.commit()
cursor.close()
if settings('enableMusicSync') == "true":
# delete video db table data
print "Doing Music DB Reset"
connection = KodiSQL("music")
cursor = connection.cursor( )
cursor.execute('SELECT tbl_name FROM sqlite_master WHERE type="table"')
rows = cursor.fetchall()
for row in rows:
tableName = row[0]
if(tableName != "version"):
cursor.execute("DELETE FROM " + tableName)
cursor.execute("DROP TABLE IF EXISTS emby")
connection.commit()
cursor.close()
# reset the install run flag
#settings('SyncInstallRunDone', "false")
#WINDOW.setProperty("SyncInstallRunDone", "false")
dialog = xbmcgui.Dialog()
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(
'<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>\n'
'<smartplaylist type="%s">\n\t'
'<name>Emby %s</name>\n\t'
'<match>all</match>\n\t'
'<rule field="tag" operator="is">\n\t\t'
'<value>%s</value>\n\t'
'</rule>'
% (itemtypes.get(mediatype, mediatype), plname, tagname))
f.close()
logMsg("EMBY", "Successfully added playlist: %s" % tagname)
# 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")

View file

@ -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():
class VideoNodes(object):
def buildVideoNodeForView(self, tagname, type, windowPropId):
#this method will build a video node for a particular Emby view (= tag in kodi)
#we set some window props here to for easy future reference and to be used in skins (for easy access only)
tagname_normalized = utils.normalize_nodes(tagname.encode('utf-8'))
libraryPath = xbmc.translatePath("special://profile/library/video/Emby - %s/" %tagname_normalized)
kodiVersion = 14
if xbmc.getInfoLabel("System.BuildVersion").startswith("15") or xbmc.getInfoLabel("System.BuildVersion").startswith("16"):
kodiVersion = 15
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
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)
#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)
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)
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)
#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 = stringid
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)
# 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
#create tag node - inprogress items
nodefile = os.path.join(libraryPath, tagname_normalized + "_progress.xml")
root = etree.Element("node", {"order":"3", "type":"filter"})
if type == "tvshows":
label = language(30171)
else:
label = language(30177)
etree.SubElement(root, "label").text = label
etree.SubElement(root, "match").text = "all"
etree.SubElement(root, "content").text = type
etree.SubElement(root, "icon").text = "special://home/addons/plugin.video.emby/icon.png"
Rule = etree.SubElement(root, "rule", {"field":"tag","operator":"is"})
etree.SubElement(Rule, "value").text = tagname
#set limit to 25 --> currently hardcoded --> TODO: add a setting for this ?
etree.SubElement(root, "limit").text = "25"
Rule2 = etree.SubElement(root, "rule", {"field":"inprogress","operator":"true"})
WINDOW.setProperty("Emby.nodes.%s.inprogress.title" %str(windowPropId),label)
path = "library://video/Emby - %s/%s_progress.xml"%(tagname_normalized,tagname_normalized)
WINDOW.setProperty("Emby.nodes.%s.inprogress.path" %str(windowPropId),"ActivateWindow(Video,%s,return)"%path)
WINDOW.setProperty("Emby.nodes.%s.inprogress.content" %str(windowPropId),path)
try:
etree.ElementTree(root).write(nodefile, xml_declaration=True)
except:
etree.ElementTree(root).write(nodefile)
#some movies-only nodes
if type == "movies":
#unwatched movies
nodefile = os.path.join(libraryPath, tagname_normalized + "_unwatched.xml")
root = etree.Element("node", {"order":"4", "type":"filter"})
label = language(30189)
etree.SubElement(root, "label").text = label
etree.SubElement(root, "match").text = "all"
etree.SubElement(root, "content").text = "movies"
etree.SubElement(root, "icon").text = "special://home/addons/plugin.video.emby/icon.png"
Rule = etree.SubElement(root, "rule", {"field":"tag","operator":"is"})
etree.SubElement(Rule, "value").text = tagname
Rule = etree.SubElement(root, "rule", {"field":"playcount","operator":"is"})
etree.SubElement(Rule, "value").text = "0"
etree.SubElement(root, "order", {"direction":"ascending"}).text = "sorttitle"
Rule2 = etree.SubElement(root, "rule", {"field":"playcount","operator":"is"})
etree.SubElement(Rule2, "value").text = "0"
WINDOW.setProperty("Emby.nodes.%s.unwatched.title" %str(windowPropId),label)
path = "library://video/Emby - %s/%s_unwatched.xml"%(tagname_normalized,tagname_normalized)
WINDOW.setProperty("Emby.nodes.%s.unwatched.path" %str(windowPropId),"ActivateWindow(Video,%s,return)"%path)
WINDOW.setProperty("Emby.nodes.%s.unwatched.content" %str(windowPropId),path)
try:
etree.ElementTree(root).write(nodefile, xml_declaration=True)
except:
etree.ElementTree(root).write(nodefile)
#sets
nodefile = os.path.join(libraryPath, tagname_normalized + "_sets.xml")
root = etree.Element("node", {"order":"9", "type":"filter"})
label = xbmc.getLocalizedString(20434)
etree.SubElement(root, "label").text = label
etree.SubElement(root, "match").text = "all"
etree.SubElement(root, "group").text = "sets"
etree.SubElement(root, "content").text = type
etree.SubElement(root, "icon").text = "special://home/addons/plugin.video.emby/icon.png"
etree.SubElement(root, "order", {"direction":"ascending"}).text = "sorttitle"
Rule = etree.SubElement(root, "rule", {"field":"tag","operator":"is"})
etree.SubElement(Rule, "value").text = tagname
WINDOW.setProperty("Emby.nodes.%s.sets.title" %str(windowPropId),label)
path = "library://video/Emby - %s/%s_sets.xml"%(tagname_normalized,tagname_normalized)
WINDOW.setProperty("Emby.nodes.%s.sets.path" %str(windowPropId),"ActivateWindow(Video,%s,return)"%path)
WINDOW.setProperty("Emby.nodes.%s.sets.content" %str(windowPropId),path)
try:
etree.ElementTree(root).write(nodefile, xml_declaration=True)
except:
etree.ElementTree(root).write(nodefile)
#create tag node - genres
nodefile = os.path.join(libraryPath, tagname_normalized + "_genres.xml")
root = etree.Element("node", {"order":"9", "type":"filter"})
label = xbmc.getLocalizedString(135)
etree.SubElement(root, "label").text = label
etree.SubElement(root, "match").text = "all"
etree.SubElement(root, "group").text = "genres"
etree.SubElement(root, "content").text = type
etree.SubElement(root, "icon").text = "special://home/addons/plugin.video.emby/icon.png"
etree.SubElement(root, "order", {"direction":"ascending"}).text = "sorttitle"
Rule = etree.SubElement(root, "rule", {"field":"tag","operator":"is"})
etree.SubElement(Rule, "value").text = tagname
WINDOW.setProperty("Emby.nodes.%s.genres.title" %str(windowPropId),label)
path = "library://video/Emby - %s/%s_genres.xml"%(tagname_normalized,tagname_normalized)
WINDOW.setProperty("Emby.nodes.%s.genres.path" %str(windowPropId),"ActivateWindow(Video,%s,return)"%path)
WINDOW.setProperty("Emby.nodes.%s.genres.content" %str(windowPropId),path)
try:
etree.ElementTree(root).write(nodefile, xml_declaration=True)
except:
etree.ElementTree(root).write(nodefile)
#create tag node - random items
nodefile = os.path.join(libraryPath, tagname_normalized + "_random.xml")
root = etree.Element("node", {"order":"10", "type":"filter"})
label = language(30229)
etree.SubElement(root, "label").text = label
etree.SubElement(root, "match").text = "all"
etree.SubElement(root, "content").text = type
etree.SubElement(root, "icon").text = "special://home/addons/plugin.video.emby/icon.png"
Rule = etree.SubElement(root, "rule", {"field":"tag","operator":"is"})
etree.SubElement(Rule, "value").text = tagname
#set limit to 25 --> currently hardcoded --> TODO: add a setting for this ?
etree.SubElement(root, "limit").text = "25"
etree.SubElement(root, "order", {"direction":"ascending"}).text = "random"
WINDOW.setProperty("Emby.nodes.%s.random.title" %str(windowPropId),label)
path = "library://video/Emby - %s/%s_random.xml"%(tagname_normalized,tagname_normalized)
WINDOW.setProperty("Emby.nodes.%s.random.path" %str(windowPropId),"ActivateWindow(Video,%s,return)"%path)
WINDOW.setProperty("Emby.nodes.%s.random.content" %str(windowPropId),path)
try:
etree.ElementTree(root).write(nodefile, xml_declaration=True)
except:
etree.ElementTree(root).write(nodefile)
#create tag node - recommended items
nodefile = os.path.join(libraryPath, tagname_normalized + "_recommended.xml")
root = etree.Element("node", {"order":"10", "type":"filter"})
label = language(30230)
etree.SubElement(root, "label").text = label
etree.SubElement(root, "match").text = "all"
etree.SubElement(root, "content").text = type
etree.SubElement(root, "icon").text = "special://home/addons/plugin.video.emby/icon.png"
Rule = etree.SubElement(root, "rule", {"field":"tag","operator":"is"})
etree.SubElement(Rule, "value").text = tagname
Rule2 = etree.SubElement(root, "rule", {"field":"playcount","operator":"is"})
etree.SubElement(Rule2, "value").text = "0"
Rule3 = etree.SubElement(root, "rule", {"field":"rating","operator":"greaterthan"})
etree.SubElement(Rule3, "value").text = "7"
#set limit to 25 --> currently hardcoded --> TODO: add a setting for this ?
etree.SubElement(root, "limit").text = "25"
etree.SubElement(root, "order", {"direction":"descending"}).text = "rating"
WINDOW.setProperty("Emby.nodes.%s.random.title" %str(windowPropId),label)
path = "library://video/Emby - %s/%s_recommended.xml"%(tagname_normalized,tagname_normalized)
WINDOW.setProperty("Emby.nodes.%s.recommended.path" %str(windowPropId),"ActivateWindow(Video,%s,return)"%path)
WINDOW.setProperty("Emby.nodes.%s.recommended.content" %str(windowPropId),path)
try:
etree.ElementTree(root).write(nodefile, xml_declaration=True)
except:
etree.ElementTree(root).write(nodefile)
#### TAGS ONLY FOR TV SHOWS COLLECTIONS ####
if type == "tvshows":
#as from kodi isengard you can use tags for episodes to filter
#for below isengard we still use the plugin's entrypoint to build a listing
if kodiVersion == 15:
#create tag node - recent episodes
nodefile = os.path.join(libraryPath, tagname_normalized + "_recent_episodes.xml")
root = etree.Element("node", {"order":"3", "type":"filter"})
label = language(30175)
etree.SubElement(root, "label").text = label
etree.SubElement(root, "match").text = "all"
etree.SubElement(root, "content").text = "episodes"
etree.SubElement(root, "icon").text = "special://home/addons/plugin.video.emby/icon.png"
etree.SubElement(root, "order", {"direction":"descending"}).text = "dateadded"
Rule = etree.SubElement(root, "rule", {"field":"tag","operator":"is"})
etree.SubElement(Rule, "value").text = tagname
#set limit to 25 --> currently hardcoded --> TODO: add a setting for this ?
etree.SubElement(root, "limit").text = "25"
#exclude watched items --> currently hardcoded --> TODO: add a setting for this ?
Rule2 = etree.SubElement(root, "rule", {"field":"playcount","operator":"is"})
etree.SubElement(Rule2, "value").text = "0"
WINDOW.setProperty("Emby.nodes.%s.recentepisodes.title" %str(windowPropId),label)
path = "library://video/Emby - %s/%s_recent_episodes.xml"%(tagname_normalized,tagname_normalized)
WINDOW.setProperty("Emby.nodes.%s.recentepisodes.path" %str(windowPropId),"ActivateWindow(Video,%s,return)"%path)
WINDOW.setProperty("Emby.nodes.%s.recentepisodes.content" %str(windowPropId),path)
try:
etree.ElementTree(root).write(nodefile, xml_declaration=True)
except:
etree.ElementTree(root).write(nodefile)
#create tag node - inprogress episodes
nodefile = os.path.join(libraryPath, tagname_normalized + "_progress_episodes.xml")
root = etree.Element("node", {"order":"4", "type":"filter"})
label = language(30178)
etree.SubElement(root, "label").text = label
etree.SubElement(root, "match").text = "all"
etree.SubElement(root, "content").text = "episodes"
etree.SubElement(root, "icon").text = "special://home/addons/plugin.video.emby/icon.png"
Rule = etree.SubElement(root, "rule", {"field":"tag","operator":"is"})
etree.SubElement(Rule, "value").text = tagname
#set limit to 25 --> currently hardcoded --> TODO: add a setting for this ?
etree.SubElement(root, "limit").text = "25"
Rule2 = etree.SubElement(root, "rule", {"field":"inprogress","operator":"true"})
WINDOW.setProperty("Emby.nodes.%s.inprogressepisodes.title" %str(windowPropId),label)
path = "library://video/Emby - %s/%s_progress_episodes.xml"%(tagname_normalized,tagname_normalized)
WINDOW.setProperty("Emby.nodes.%s.inprogressepisodes.path" %str(windowPropId),"ActivateWindow(Video,%s,return)"%path)
WINDOW.setProperty("Emby.nodes.%s.inprogressepisodes.content" %str(windowPropId),path)
try:
etree.ElementTree(root).write(nodefile, xml_declaration=True)
except:
etree.ElementTree(root).write(nodefile)
if kodiVersion == 14:
#create tag node - recent episodes
nodefile = os.path.join(libraryPath, tagname_normalized + "_recent_episodes.xml")
root = etree.Element("node", {"order":"4", "type":"folder"})
label = language(30175)
etree.SubElement(root, "label").text = label
etree.SubElement(root, "content").text = "episodes"
etree.SubElement(root, "icon").text = "special://home/addons/plugin.video.emby/icon.png"
path = "plugin://plugin.video.emby/?id=%s&mode=recentepisodes&limit=25" %tagname
elif kodiversion == 14 and nodetype == "inprogressepisodes":
# Custom query
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
else:
path = "library://video/Emby - %s/%s_%s.xml" % (dirname, cleantagname, nodetype)
windowpath = "ActivateWindow(Video, %s, return)" % path
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)
if nodetype == "all":
#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)
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"})
def buildVideoNodesListing(self):
try:
utils.indent(root)
except: pass
etree.ElementTree(root).write(nodeXML)
def singleNode(self, indexnumber, tagname, mediatype, itemtype):
# 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"))
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
#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)
# 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)
#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
labels = {
#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
'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
views_shows = ReadEmbyDB().getCollections("tvshows")
if views_shows:
for view in views_shows:
title = view.get('title')
content = view.get('content')
if content == "mixed":
title = "%s - TV Shows" % title
self.buildVideoNodeForView(title, "tvshows", totalNodesCount)
totalNodesCount +=1
#create tag node for emby channels
nodefile = os.path.join(xbmc.translatePath("special://profile/library/video"), "emby_channels.xml")
root = etree.Element("node", {"order":"1", "type":"folder"})
label = language(30173)
etree.SubElement(root, "label").text = label
etree.SubElement(root, "content").text = "movies"
etree.SubElement(root, "path").text = "plugin://plugin.video.emby/?id=0&mode=channels"
etree.SubElement(root, "icon").text = "special://home/addons/plugin.video.emby/icon.png"
WINDOW.setProperty("Emby.nodes.%s.title" %str(totalNodesCount),label)
WINDOW.setProperty("Emby.nodes.%s.type" %str(totalNodesCount),"channels")
path = "library://video/emby_channels.xml"
WINDOW.setProperty("Emby.nodes.%s.path" %str(totalNodesCount),"ActivateWindow(Video,%s,return)"%path)
WINDOW.setProperty("Emby.nodes.%s.content" %str(totalNodesCount),path)
totalNodesCount +=1
try:
utils.indent(root)
except: pass
etree.ElementTree(root).write(nodeXML)
etree.ElementTree(root).write(nodefile, xml_declaration=True)
except:
etree.ElementTree(root).write(nodefile)
def clearProperties(self):
#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)
self.logMsg("Clearing nodes properties.", 1)
embyprops = utils.window('Emby.nodes.total')
propnames = [
#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)
"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"
]
WINDOW.setProperty("Emby.nodes.total", str(totalNodesCount))
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)
except Exception as e:
utils.logMsg("Emby addon","Error while creating videonodes listings, restart required ?")
print e