Seems I have to rename the filename to lowercase manually.
This commit is contained in:
angelblue05 2015-12-24 13:54:34 -06:00
parent 3f6fe0a9e7
commit f28c9fe069
10 changed files with 3695 additions and 3364 deletions

View file

@ -1,32 +1,38 @@
import xbmc
import xbmcgui
import xbmcaddon
# -*- coding: utf-8 -*-
##################################################################################################
import requests
import json
import requests
import logging
import Utils as utils
from ClientInformation import ClientInformation
from requests.packages.urllib3.exceptions import InsecureRequestWarning
import xbmc
import xbmcgui
import utils
import clientinfo
##################################################################################################
# Disable requests logging
from requests.packages.urllib3.exceptions import InsecureRequestWarning
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
#logging.getLogger("requests").setLevel(logging.WARNING)
#logging.getLogger('requests').setLevel(logging.WARNING)
##################################################################################################
class DownloadUtils():
# Borg - multiple instances, shared state
_shared_state = {}
clientInfo = ClientInformation()
clientInfo = clientinfo.ClientInfo()
addonName = clientInfo.getAddonName()
addon = xbmcaddon.Addon()
WINDOW = xbmcgui.Window(10000)
# Requests session
s = None
timeout = 60
timeout = 30
def __init__(self):
@ -34,41 +40,44 @@ class DownloadUtils():
def logMsg(self, msg, lvl=1):
self.className = self.__class__.__name__
utils.logMsg("%s %s" % (self.addonName, self.className), msg, int(lvl))
className = self.__class__.__name__
utils.logMsg("%s %s" % (self.addonName, className), msg, lvl)
def setUsername(self, username):
# Reserved for UserClient only
# Reserved for userclient only
self.username = username
self.logMsg("Set username: %s" % username, 2)
def setUserId(self, userId):
# Reserved for UserClient only
# Reserved for userclient only
self.userId = userId
self.logMsg("Set userId: %s" % userId, 2)
def setServer(self, server):
# Reserved for UserClient only
# Reserved for userclient only
self.server = server
self.logMsg("Set server: %s" % server, 2)
def setToken(self, token):
# Reserved for UserClient only
# Reserved for userclient only
self.token = token
self.logMsg("Set token: %s" % token, 2)
def setSSL(self, ssl, sslclient):
# Reserved for UserClient only
# Reserved for userclient only
self.sslverify = ssl
self.sslclient = sslclient
self.logMsg("Verify SSL host certificate: %s" % ssl, 2)
self.logMsg("SSL client side certificate: %s" % sslclient, 2)
def postCapabilities(self, deviceId):
# Post settings to session
url = "{server}/mediabrowser/Sessions/Capabilities/Full"
url = "{server}/emby/Sessions/Capabilities/Full?format=json"
data = {
'PlayableMediaTypes': "Audio,Video",
'SupportsMediaControl': True,
'SupportedCommands': (
@ -86,49 +95,57 @@ class DownloadUtils():
}
self.logMsg("Capabilities URL: %s" % url, 2)
self.logMsg("PostData: %s" % data, 2)
self.logMsg("Postdata: %s" % data, 2)
try:
self.downloadUrl(url, postBody=data, type="POST")
self.logMsg("Posted capabilities to %s" % self.server, 1)
except:
self.logMsg("Posted capabilities failed.")
self.logMsg("Posted capabilities to %s" % self.server, 2)
# Attempt at getting sessionId
url = "{server}/mediabrowser/Sessions?DeviceId=%s&format=json" % deviceId
url = "{server}/emby/Sessions?DeviceId=%s&format=json" % deviceId
result = self.downloadUrl(url)
try:
result = self.downloadUrl(url)
self.logMsg("Session: %s" % result, 2)
sessionId = result[0]['Id']
sessionId = result[0][u'Id']
self.logMsg("SessionId: %s" % sessionId)
self.WINDOW.setProperty("sessionId%s" % self.username, sessionId)
except:
except (KeyError, TypeError):
self.logMsg("Failed to retrieve sessionId.", 1)
else:
self.logMsg("Session: %s" % result, 2)
self.logMsg("SessionId: %s" % sessionId, 1)
utils.window('emby_sessionId', value=sessionId)
# Post any permanent additional users
additionalUsers = utils.settings('additionalUsers').split(',')
self.logMsg("List of permanent users that should be added to the session: %s" % str(additionalUsers), 1)
additionalUsers = utils.settings('additionalUsers')
if additionalUsers:
additionalUsers = additionalUsers.split(',')
self.logMsg(
"List of permanent users added to the session: %s"
% additionalUsers, 1)
# Get the user list from server to get the userId
url = "{server}/mediabrowser/Users?format=json"
url = "{server}/emby/Users?format=json"
result = self.downloadUrl(url)
if result:
for user in result:
username = user['Name'].lower()
userId = user['Id']
for additional in additionalUsers:
addUser = additional.decode('utf-8').lower()
# Compare to server users to list of permanent additional users
for user in result:
username = user['Name'].lower()
if username in addUser:
url = "{server}/mediabrowser/Sessions/%s/Users/%s" % (sessionId, userId)
postdata = {}
self.downloadUrl(url, postBody=postdata, type="POST")
#xbmcgui.Dialog().notification("Success!", "%s added to viewing session" % username, time=1000)
userId = user['Id']
url = (
"{server}/emby/Sessions/%s/Users/%s?format=json"
% (sessionId, userId)
)
self.downloadUrl(url, postBody={}, type="POST")
def startSession(self):
self.deviceId = self.clientInfo.getMachineId()
self.deviceId = self.clientInfo.getDeviceId()
# User is identified from this point
# Attach authenticated header to the session
@ -152,7 +169,7 @@ class DownloadUtils():
self.s.mount("http://", requests.adapters.HTTPAdapter(max_retries=1))
self.s.mount("https://", requests.adapters.HTTPAdapter(max_retries=1))
self.logMsg("Requests session started on: %s" % self.server)
self.logMsg("Requests session started on: %s" % self.server, 1)
def stopSession(self):
try:
@ -165,93 +182,116 @@ class DownloadUtils():
clientInfo = self.clientInfo
deviceName = clientInfo.getDeviceName()
deviceId = clientInfo.getMachineId()
deviceId = clientInfo.getDeviceId()
version = clientInfo.getVersion()
if not authenticate:
# If user is not authenticated
auth = 'MediaBrowser Client="Kodi", Device="%s", DeviceId="%s", Version="%s"' % (deviceName, deviceId, version)
header = {'Content-type': 'application/json', 'Accept-encoding': 'gzip', 'Accept-Charset': 'UTF-8,*', 'Authorization': auth}
auth = (
'MediaBrowser Client="Kodi", Device="%s", DeviceId="%s", Version="%s"'
% (deviceName, deviceId, version))
header = {
'Content-type': 'application/json',
'Accept-encoding': 'gzip',
'Accept-Charset': 'UTF-8,*',
'Authorization': auth
}
self.logMsg("Header: %s" % header, 2)
return header
else:
userId = self.userId
token = self.token
# Attached to the requests session
auth = 'MediaBrowser UserId="%s", Client="Kodi", Device="%s", DeviceId="%s", Version="%s"' % (userId, deviceName, deviceId, version)
header = {'Content-type': 'application/json', 'Accept-encoding': 'gzip', 'Accept-Charset': 'UTF-8,*', 'Authorization': auth, 'X-MediaBrowser-Token': token}
auth = (
'MediaBrowser UserId="%s", Client="Kodi", Device="%s", DeviceId="%s", Version="%s"'
% (userId, deviceName, deviceId, version))
header = {
'Content-type': 'application/json',
'Accept-encoding': 'gzip',
'Accept-Charset': 'UTF-8,*',
'Authorization': auth,
'X-MediaBrowser-Token': token
}
self.logMsg("Header: %s" % header, 2)
return header
def downloadUrl(self, url, postBody=None, type="GET", authenticate=True):
def downloadUrl(self, url, postBody=None, type="GET", parameters=None, authenticate=True):
self.logMsg("=== ENTER downloadUrl ===", 2)
WINDOW = self.WINDOW
timeout = self.timeout
default_link = ""
try:
# If user is authenticated
if (authenticate):
# Get requests session
try:
s = self.s
# Replace for the real values and append api_key
url = url.replace("{server}", self.server, 1)
url = url.replace("{UserId}", self.userId, 1)
# Replace for the real values
url = url.replace("{server}", self.server)
url = url.replace("{UserId}", self.userId)
self.logMsg("URL: %s" % url, 2)
# Prepare request
if type == "GET":
r = s.get(url, json=postBody, timeout=timeout)
r = s.get(url, json=postBody, params=parameters, timeout=timeout)
elif type == "POST":
r = s.post(url, json=postBody, timeout=timeout)
elif type == "DELETE":
r = s.delete(url, json=postBody, timeout=timeout)
except AttributeError:
# request session does not exists
# Get user information
self.username = WINDOW.getProperty('currUser')
self.userId = WINDOW.getProperty('userId%s' % self.username)
self.server = WINDOW.getProperty('server%s' % self.username)
self.token = WINDOW.getProperty('accessToken%s' % self.username)
self.userId = utils.window('emby_currUser')
self.server = utils.window('emby_server%s' % self.userId)
self.token = utils.window('emby_accessToken%s' % self.userId)
header = self.getHeader()
verifyssl = False
cert = None
# IF user enables ssl verification
try:
if utils.settings('sslverify') == "true":
verifyssl = True
if utils.settings('sslcert') != "None":
cert = utils.settings('sslcert')
except:
self.logMsg("Could not load SSL settings.", 1)
pass
# Replace for the real values and append api_key
url = url.replace("{server}", self.server, 1)
url = url.replace("{UserId}", self.userId, 1)
# Replace for the real values
url = url.replace("{server}", self.server)
url = url.replace("{UserId}", self.userId)
self.logMsg("URL: %s" % url, 2)
# Prepare request
if type == "GET":
r = requests.get(url, json=postBody, headers=header, timeout=timeout, cert=cert, verify=verifyssl)
r = requests.get(url,
json=postBody,
params=parameters,
headers=header,
timeout=timeout,
cert=cert,
verify=verifyssl)
elif type == "POST":
r = requests.post(url, json=postBody, headers=header, timeout=timeout, cert=cert, verify=verifyssl)
r = requests.post(url,
json=postBody,
headers=header,
timeout=timeout,
cert=cert,
verify=verifyssl)
elif type == "DELETE":
r = requests.delete(url, json=postBody, headers=header, timeout=timeout, cert=cert, verify=verifyssl)
r = requests.delete(url,
json=postBody,
headers=header,
timeout=timeout,
cert=cert,
verify=verifyssl)
# If user is not authenticated
elif not authenticate:
self.logMsg("URL: %s" % url, 2)
header = self.getHeader(authenticate=False)
verifyssl = False
@ -263,41 +303,49 @@ class DownloadUtils():
# Prepare request
if type == "GET":
r = requests.get(url, json=postBody, headers=header, timeout=timeout, verify=verifyssl)
elif type == "POST":
r = requests.post(url, json=postBody, headers=header, timeout=timeout, verify=verifyssl)
r = requests.get(url,
json=postBody,
params=parameters,
headers=header,
timeout=timeout,
verify=verifyssl)
# Process the response
elif type == "POST":
r = requests.post(url,
json=postBody,
headers=header,
timeout=timeout,
verify=verifyssl)
##### THE RESPONSE #####
self.logMsg(r.url, 2)
if r.status_code == 204:
# No body in the response
self.logMsg("====== 204 Success ======", 2)
return default_link
elif r.status_code == requests.codes.ok:
try:
# UTF-8 - JSON object
r = r.json()
self.logMsg("====== 200 Success ======", 2)
self.logMsg("Response: %s" % r, 2)
return r
except:
if r.headers.get('content-type') == "text/html":
pass
else:
if r.headers.get('content-type') != "text/html":
self.logMsg("Unable to convert the response for: %s" % url, 1)
else:
r.raise_for_status()
return default_link
##### EXCEPTIONS #####
# TO REVIEW EXCEPTIONS
except requests.exceptions.ConnectionError as e:
# Make the addon aware of status
if WINDOW.getProperty("Server_online") != "false":
if utils.window('emby_online') != "false":
self.logMsg("Server unreachable at: %s" % url, 0)
self.logMsg(e, 2)
WINDOW.setProperty("Server_online", "false")
pass
utils.window('emby_online', value="false")
except requests.exceptions.ConnectTimeout as e:
self.logMsg("Server timeout at: %s" % url, 0)
@ -307,29 +355,35 @@ class DownloadUtils():
if r.status_code == 401:
# Unauthorized
status = WINDOW.getProperty("Server_status")
status = utils.window('emby_serverStatus')
if 'x-application-error-code' in r.headers:
if 'X-Application-Error-Code' in r.headers:
# Emby server errors
if r.headers['X-Application-Error-Code'] == "ParentalControl":
# Parental control - access restricted
WINDOW.setProperty("Server_status", "restricted")
xbmcgui.Dialog().notification("Emby server", "Access restricted.", xbmcgui.NOTIFICATION_ERROR, time=5000)
utils.window('emby_serverStatus', value="restricted")
xbmcgui.Dialog().notification(
heading="Emby server",
message="Access restricted.",
icon=xbmcgui.NOTIFICATION_ERROR,
time=5000)
return False
elif r.headers['X-Application-Error-Code'] == "UnauthorizedAccessException":
# User tried to do something his emby account doesn't allow - admin restricted in some way
# User tried to do something his emby account doesn't allow
pass
elif (status == "401") or (status == "Auth"):
pass
else:
# Tell UserClient token has been revoked.
WINDOW.setProperty("Server_status", "401")
elif status not in ("401", "Auth"):
# Tell userclient token has been revoked.
utils.window('emby_serverStatus', value="401")
self.logMsg("HTTP Error: %s" % e, 0)
xbmcgui.Dialog().notification("Error connecting", "Unauthorized.", xbmcgui.NOTIFICATION_ERROR)
xbmcgui.Dialog().notification(
heading="Error connecting",
message="Unauthorized.",
icon=xbmcgui.NOTIFICATION_ERROR)
return 401
elif (r.status_code == 301) or (r.status_code == 302):
elif r.status_code in (301, 302):
# Redirects
pass
elif r.status_code == 400:

File diff suppressed because it is too large Load diff

View file

@ -1,72 +1,82 @@
# -*- coding: utf-8 -*-
#################################################################################################
# Kodi Monitor
# Watched events that occur in Kodi, like setting media watched
#################################################################################################
import json
import xbmc
import xbmcgui
import xbmcaddon
import json
import Utils as utils
from WriteKodiVideoDB import WriteKodiVideoDB
from ReadKodiDB import ReadKodiDB
from PlayUtils import PlayUtils
from DownloadUtils import DownloadUtils
from PlaybackUtils import PlaybackUtils
import clientinfo
import downloadutils
import embydb_functions as embydb
import playbackutils as pbutils
import utils
#################################################################################################
class Kodi_Monitor( xbmc.Monitor ):
class KodiMonitor(xbmc.Monitor):
WINDOW = xbmcgui.Window(10000)
def __init__(self, *args, **kwargs):
xbmc.Monitor.__init__(self)
def __init__(self):
def logMsg(self, msg, lvl = 1):
self.clientInfo = clientinfo.ClientInfo()
self.addonName = self.clientInfo.getAddonName()
self.doUtils = downloadutils.DownloadUtils()
self.logMsg("Kodi monitor started.", 1)
def logMsg(self, msg, lvl=1):
self.className = self.__class__.__name__
utils.logMsg("%s %s" % (self.addonName, self.className), msg, lvl)
className = self.__class__.__name__
utils.logMsg("%s %s" % ("EMBY", className), msg, int(lvl))
def onScanStarted(self, library):
utils.window('kodiScan', value="true")
self.logMsg("Kodi library scan running.", 2)
self.logMsg("Kodi library scan %s running." % library, 2)
if library == "video":
utils.window('emby_kodiScan', value="true")
def onScanFinished(self, library):
utils.window('kodiScan', clear=True)
self.logMsg("Kodi library scan finished.", 2)
self.logMsg("Kodi library scan %s finished." % library, 2)
if library == "video":
utils.window('emby_kodiScan', clear=True)
#this library monitor is used to detect a watchedstate change by the user through the library
#as well as detect when a library item has been deleted to pass the delete to the Emby server
def onNotification (self, sender, method, data):
def onNotification(self, sender, method, data):
WINDOW = self.WINDOW
downloadUtils = DownloadUtils()
#player started playing an item -
if ("Playlist.OnAdd" in method or "Player.OnPlay" in method):
doUtils = self.doUtils
if method not in ("Playlist.OnAdd"):
self.logMsg("Method: %s Data: %s" % (method, data), 1)
jsondata = json.loads(data)
if jsondata:
if jsondata.has_key("item"):
if jsondata.get("item").has_key("id") and jsondata.get("item").has_key("type"):
id = jsondata.get("item").get("id")
type = jsondata.get("item").get("type")
if data:
data = json.loads(data)
if (utils.settings('useDirectPaths')=='true' and not type == "song") or (type == "song" and utils.settings('enableMusicSync') == "true"):
if type == "song":
connection = utils.KodiSQL('music')
cursor = connection.cursor()
embyid = ReadKodiDB().getEmbyIdByKodiId(id, type, connection, cursor)
cursor.close()
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)
else:
embyid = ReadKodiDB().getEmbyIdByKodiId(id,type)
if embyid:
url = "{server}/mediabrowser/Users/{UserId}/Items/%s?format=json" % embyid
result = downloadUtils.downloadUrl(url)
self.logMsg("Result: %s" % result, 2)
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)
playurl = None
count = 0
@ -74,77 +84,112 @@ class Kodi_Monitor( xbmc.Monitor ):
try:
playurl = xbmc.Player().getPlayingFile()
except RuntimeError:
count += 1
xbmc.sleep(200)
else:
listItem = xbmcgui.ListItem()
PlaybackUtils().setProperties(playurl, result, listItem)
playback = pbutils.PlaybackUtils(result)
if type == "song" and utils.settings('directstreammusic') == "true":
utils.window('%splaymethod' % playurl, value="DirectStream")
if type == "song" and utils.settings('streamMusic') == "true":
utils.window('emby_%s.playmethod' % playurl,
value="DirectStream")
else:
utils.window('%splaymethod' % playurl, value="DirectPlay")
utils.window('emby_%s.playmethod' % playurl,
value="DirectPlay")
# Set properties for player.py
playback.setProperties(playurl, listItem)
finally:
embycursor.close()
count += 1
if method == "VideoLibrary.OnUpdate":
# Triggers 4 times, the following is only for manually marking as watched/unwatched
jsondata = json.loads(data)
elif method == "VideoLibrary.OnUpdate":
# Manually marking as watched/unwatched
playcount = data.get('playcount')
item = data.get('item')
try:
kodiid = item['id']
type = item['type']
except (KeyError, TypeError):
self.logMsg("Item is invalid for playstate update.", 1)
else:
# Send notification to the server.
embyconn = utils.kodiSQL('emby')
embycursor = embyconn.cursor()
emby_db = embydb.Embydb_Functions(embycursor)
emby_dbitem = emby_db.getItem_byKodiId(kodiid, type)
try:
itemid = emby_dbitem[0]
except TypeError:
self.logMsg("Could not find itemid in emby database.", 1)
else:
# Stop from manually marking as watched unwatched, with actual playback.
if utils.window('emby_skipWatched%s' % itemid) == "true":
# property is set in player.py
utils.window('emby_skipWatched%s' % itemid, clear=True)
else:
# notify the server
url = "{server}/emby/Users/{UserId}/PlayedItems/%s?format=json" % itemid
if playcount != 0:
doUtils.downloadUrl(url, type="POST")
self.logMsg("Mark as watched for itemid: %s" % itemid, 1)
else:
doUtils.downloadUrl(url, type="DELETE")
self.logMsg("Mark as unwatched for itemid: %s" % itemid, 1)
finally:
embycursor.close()
elif method == "VideoLibrary.OnRemove":
try:
playcount = jsondata.get('playcount')
item = jsondata['item']['id']
type = jsondata['item']['type']
prop = utils.window('Played%s%s' % (type, item))
except:
self.logMsg("Could not process VideoLibrary.OnUpdate data.", 1)
kodiid = data['id']
type = data['type']
except (KeyError, TypeError):
self.logMsg("Item is invalid for emby deletion.", 1)
else:
self.logMsg("VideoLibrary.OnUpdate: %s" % data, 2)
if prop != "true":
# Set property to prevent the multi triggering
utils.window('Played%s%s' % (type, item), "true")
WriteKodiVideoDB().updatePlayCountFromKodi(item, type, playcount)
# Send the delete action to the server.
offerDelete = False
self.clearProperty(type, item)
if type == "episode" and utils.settings('deleteTV') == "true":
offerDelete = True
elif type == "movie" and utils.settings('deleteMovies') == "true":
offerDelete = True
if method == "System.OnWake":
xbmc.sleep(10000) #Allow network to wake up
WINDOW.setProperty("OnWakeSync", "true")
if utils.settings('offerDelete') != "true":
# Delete could be disabled, even if the subsetting is enabled.
offerDelete = False
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:
if offerDelete:
embyconn = utils.kodiSQL('emby')
embycursor = embyconn.cursor()
emby_db = embydb.Embydb_Functions(embycursor)
emby_dbitem = emby_db.getItem_byKodiId(kodiid, type)
try:
itemid = emby_dbitem[0]
except TypeError:
self.logMsg("Could not find itemid in emby database.", 1)
else:
if utils.settings('skipConfirmDelete') != "true":
resp = xbmcgui.Dialog().yesno(
heading="Confirm delete",
line1="Delete file on Emby Server?")
if not resp:
self.logMsg("User skipped deletion.", 1)
embycursor.close()
return
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
url = "{server}/emby/Items/%s?format=json" % itemid
self.logMsg("Deleting request: %s" % itemid)
doUtils.downloadUrl(url, type="DELETE")
finally:
embycursor.close()
if jsondata:
if jsondata.get("type") == "episode" or "movie":
url='{server}/mediabrowser/Items?Ids=' + id + '&format=json'
#This is a check to see if the item exists on the server, if it doesn't it may have already been deleted by another client
result = DownloadUtils().downloadUrl(url)
item = result.get("Items")[0]
if data:
return_value = xbmcgui.Dialog().yesno("Confirm Delete", "Delete file on Emby Server?")
if return_value:
url='{server}/mediabrowser/Items/' + id
xbmc.log('Deleting via URL: ' + url)
DownloadUtils().downloadUrl(url, type="DELETE")
elif method == "System.OnWake":
# Allow network to wake up
xbmc.sleep(10000)
utils.window('emby_onWake', value="true")
elif method == "Playlist.OnClear":
self.logMsg("Clear playback properties.", 2)
utils.window('propertiesPlayback', clear=True)
def clearProperty(self, type, id):
# The sleep is necessary since VideoLibrary.OnUpdate
# triggers 4 times in a row.
xbmc.sleep(100)
utils.window('Played%s%s' % (type,id), clear=True)
# Clear the widget cache
utils.window('clearwidgetcache', value="clear")
utils.window('emby_customPlaylist', clear=True, windowid=10101)
#xbmcgui.Window(10101).clearProperties()
self.logMsg("Clear playlist properties.")

File diff suppressed because it is too large Load diff

View file

@ -6,117 +6,156 @@ import xbmc
import xbmcgui
import xbmcvfs
from ClientInformation import ClientInformation
import Utils as utils
import clientinfo
import utils
#################################################################################################
class PlayUtils():
clientInfo = ClientInformation()
addonName = clientInfo.getAddonName()
def __init__(self, item):
self.item = item
self.clientInfo = clientinfo.ClientInfo()
self.addonName = self.clientInfo.getAddonName()
self.userid = utils.window('emby_currUser')
self.server = utils.window('emby_server%s' % self.userid)
def logMsg(self, msg, lvl=1):
className = self.__class__.__name__
utils.logMsg("%s %s" % (self.addonName, className), msg, int(lvl))
self.className = self.__class__.__name__
utils.logMsg("%s %s" % (self.addonName, self.className), msg, lvl)
def getPlayUrl(self, server, id, result):
if self.isDirectPlay(result,True):
# Try direct play
playurl = self.directPlay(result)
if playurl:
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():
self.logMsg("File is direct playing.", 1)
utils.window("%splaymethod" % playurl.encode('utf-8'), value="DirectPlay")
playurl = self.directPlay()
playurl = playurl.encode('utf-8')
# Set playmethod property
utils.window('emby_%s.playmethod' % playurl, value="DirectPlay")
elif self.isDirectStream():
elif self.isDirectStream(result):
# Try direct stream
playurl = self.directStream(result, server, id)
if playurl:
self.logMsg("File is direct streaming.", 1)
utils.window("%splaymethod" % playurl, value="DirectStream")
playurl = self.directStream()
# Set playmethod property
utils.window('emby_%s.playmethod' % playurl, value="DirectStream")
elif self.isTranscoding():
elif self.isTranscoding(result):
# Try transcoding
playurl = self.transcoding(result, server, id)
if playurl:
self.logMsg("File is transcoding.", 1)
utils.window("%splaymethod" % playurl, value="Transcode")
playurl = self.transcoding()
# Set playmethod property
utils.window('emby_%s.playmethod' % playurl, value="Transcode")
else: # Error
utils.window("playurlFalse", value="true")
return
return playurl
return playurl.encode('utf-8')
def httpPlay(self):
# Audio, Video, Photo
item = self.item
server = self.server
itemid = item['Id']
mediatype = item['MediaType']
def isDirectPlay(self, result, dialog = False):
# Requirements for Direct play:
# FileSystem, Accessible path
if type == "Audio":
playurl = "%s/emby/Audio/%s/stream" % (server, itemid)
else:
playurl = "%s/emby/Videos/%s/stream?static=true" % (server, itemid)
return playurl
def isDirectPlay(self):
item = self.item
# Requirement: Filesystem, Accessible path
if utils.settings('playFromStream') == "true":
# User forcing to play via HTTP instead of SMB
self.logMsg("Can't direct play: Play from HTTP is enabled.", 1)
# User forcing to play via HTTP
self.logMsg("Can't direct play, play from HTTP enabled.", 1)
return False
# Avoid H265 1080p
if (utils.settings('transcodeH265') == "true" and
result['MediaSources'][0]['Name'].startswith("1080P/H265")):
# Avoid H265 1080p
self.logMsg("Option to transcode 1080P/H265 enabled.", 1)
return False
canDirectPlay = result['MediaSources'][0]['SupportsDirectPlay']
# Make sure it's supported by server
canDirectPlay = item['MediaSources'][0]['SupportsDirectPlay']
# Make sure direct play is supported by the server
if not canDirectPlay:
self.logMsg("Can't direct play: Server does not allow or support it.", 1)
self.logMsg("Can't direct play, server doesn't allow/support it.", 1)
return False
location = result['LocationType']
# File needs to be "FileSystem"
if 'FileSystem' in location:
# Verify if path is accessible
if self.fileExists(result):
return True
else:
self.logMsg("Unable to direct play. Verify the following path is accessible by the device: %s. You might also need to add SMB credentials in the add-on settings." % result['MediaSources'][0]['Path'], 1)
if dialog:
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)
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)
if count < 2:
# Let the user know that direct play failed
utils.settings('failCount', value=str(count+1))
xbmcgui.Dialog().notification(
heading="Emby server",
message="Unable to direct play.",
icon="special://home/addons/plugin.video.emby/icon.png",
sound=False)
elif utils.settings('playFromStream') != "true":
# Permanently set direct stream as true
utils.settings('playFromStream', value="true")
xbmcgui.Dialog().notification("Emby server", "Enabled play from HTTP in add-on settings.", icon="special://home/addons/plugin.video.emby/icon.png", sound=False)
utils.settings('failCount', value="0")
xbmcgui.Dialog().notification(
heading="Emby server",
message=("Direct play failed 3 times. Enabled play "
"from HTTP in the add-on settings."),
icon="special://home/addons/plugin.video.emby/icon.png",
sound=False)
return False
def directPlay(self, result):
return True
def directPlay(self):
item = self.item
try:
playurl = result['MediaSources'][0]['Path']
except KeyError:
playurl = result['Path']
playurl = item['MediaSources'][0]['Path']
except (IndexError, KeyError):
playurl = item['Path']
if 'VideoType' in result:
if item.get('VideoType'):
# Specific format modification
if 'Dvd' in result['VideoType']:
type = item['VideoType']
if type == "Dvd":
playurl = "%s/VIDEO_TS/VIDEO_TS.IFO" % playurl
elif 'BluRay' in result['VideoType']:
elif type == "Bluray":
playurl = "%s/BDMV/index.bdmv" % playurl
# Network - SMB protocol
if "\\\\" in playurl:
smbuser = utils.settings('smbusername')
smbpass = utils.settings('smbpassword')
# Network share
if smbuser:
playurl = playurl.replace("\\\\", "smb://%s:%s@" % (smbuser, smbpass))
else:
# Assign network protocol
if playurl.startswith('\\\\'):
playurl = playurl.replace("\\\\", "smb://")
playurl = playurl.replace("\\", "/")
@ -126,107 +165,120 @@ class PlayUtils():
return playurl
def fileExists(self):
def isDirectStream(self, result):
# Requirements for Direct stream:
# FileSystem or Remote, BitRate, supported encoding
if 'Path' not in self.item:
# File has no path defined in server
return False
# Convert path to direct play
path = self.directPlay()
self.logMsg("Verifying path: %s" % path, 1)
if xbmcvfs.exists(path):
self.logMsg("Path exists.", 1)
return True
elif ":" not in path:
self.logMsg("Can't verify path, assumed linux. Still try to direct play.", 1)
return True
else:
self.logMsg("Failed to find file.")
return False
def isDirectStream(self):
item = self.item
# Avoid H265 1080p
if (utils.settings('transcodeH265') == "true" and
result['MediaSources'][0]['Name'].startswith("1080P/H265")):
# Avoid H265 1080p
self.logMsg("Option to transcode 1080P/H265 enabled.", 1)
return False
canDirectStream = result['MediaSources'][0]['SupportsDirectStream']
# Make sure it's supported by server
# Requirement: BitRate, supported encoding
canDirectStream = item['MediaSources'][0]['SupportsDirectStream']
# Make sure the server supports it
if not canDirectStream:
return False
location = result['LocationType']
# File can be FileSystem or Remote, not Virtual
if 'Virtual' in location:
self.logMsg("File location is virtual. Can't proceed.", 1)
return False
# Verify BitRate
if not self.isNetworkQualitySufficient(result):
self.logMsg("The network speed is insufficient to playback the file.", 1)
# Verify the bitrate
if not self.isNetworkSufficient():
self.logMsg("The network speed is insufficient to direct stream file.", 1)
return False
return True
def directStream(self, result, server, id, type = "Video"):
def directStream(self):
if result['Path'].endswith('.strm'):
item = self.item
server = self.server
itemid = item['Id']
type = item['Type']
if 'Path' in item and item['Path'].endswith('.strm'):
# Allow strm loading when direct streaming
playurl = self.directPlay(result)
return playurl
if "ThemeVideo" in type:
playurl = "%s/mediabrowser/Videos/%s/stream?static=true" % (server, id)
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)
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)
return playurl
def isNetworkSufficient(self):
def isTranscoding(self, result):
# Last resort, no requirements
# BitRate
canTranscode = result['MediaSources'][0]['SupportsTranscoding']
# Make sure it's supported by server
settings = self.getBitrate()*1000
try:
sourceBitrate = int(self.item['MediaSources'][0]['Bitrate'])
except (KeyError, TypeError):
self.logMsg("Bitrate value is missing.", 1)
else:
self.logMsg("The add-on settings bitrate is: %s, the video bitrate required is: %s"
% (settings, sourceBitrate), 1)
if settings < sourceBitrate:
return False
return True
def isTranscoding(self):
item = self.item
canTranscode = item['MediaSources'][0]['SupportsTranscoding']
# Make sure the server supports it
if not canTranscode:
return False
location = result['LocationType']
# File can be FileSystem or Remote, not Virtual
if 'Virtual' in location:
return False
return True
def transcoding(self, result, server, id):
def transcoding(self):
if result['Path'].endswith('.strm'):
item = self.item
if 'Path' in item and item['Path'].endswith('.strm'):
# Allow strm loading when transcoding
playurl = self.directPlay(result)
return playurl
# Play transcoding
deviceId = self.clientInfo.getMachineId()
playurl = "%s/mediabrowser/Videos/%s/master.m3u8?mediaSourceId=%s" % (server, id, id)
playurl = "%s&VideoCodec=h264&AudioCodec=ac3&MaxAudioChannels=6&deviceId=%s&VideoBitrate=%s" % (playurl, deviceId, self.getVideoBitRate()*1000)
playurl = self.audioSubsPref(playurl, result.get('MediaSources'))
self.logMsg("Playurl: %s" % playurl, 1)
return playurl
def isNetworkQualitySufficient(self, result):
# Works out if the network quality can play directly or if transcoding is needed
settingsVideoBitRate = self.getVideoBitRate()
settingsVideoBitRate = settingsVideoBitRate * 1000
try:
mediaSources = result['MediaSources']
sourceBitRate = int(mediaSources[0]['Bitrate'])
except KeyError:
self.logMsg("Bitrate value is missing.", 1)
playurl = self.directPlay()
else:
self.logMsg("The video quality selected is: %s, the video bitrate required to direct stream is: %s." % (settingsVideoBitRate, sourceBitRate), 1)
if settingsVideoBitRate < sourceBitRate:
return False
itemid = item['Id']
deviceId = self.clientInfo.getDeviceId()
playurl = (
"%s/emby/Videos/%s/master.m3u8?MediaSourceId=%s"
% (self.server, itemid, itemid)
)
playurl = (
"%s&VideoCodec=h264&AudioCodec=ac3&MaxAudioChannels=6&deviceId=%s&VideoBitrate=%s"
% (playurl, deviceId, self.getBitrate()*1000))
return True
return playurl
def getBitrate(self):
def getVideoBitRate(self):
# get the addon video quality
videoQuality = utils.settings('videoBitRate')
videoQuality = utils.settings('videoBitrate')
bitrate = {
'0': 664,
@ -253,34 +305,7 @@ class PlayUtils():
# max bit rate supported by server (max signed 32bit integer)
return bitrate.get(videoQuality, 2147483)
def fileExists(self, result):
if 'Path' not in result:
# File has no path in server
return False
# Convert Emby path to a path we can verify
path = self.directPlay(result)
try:
pathexists = xbmcvfs.exists(path)
except:
pathexists = False
# Verify the device has access to the direct path
if pathexists:
# Local or Network path
self.logMsg("Path exists.", 2)
return True
elif ":" not in path:
# Give benefit of the doubt for nfs.
self.logMsg("Can't verify path (assumed NFS). Still try direct play.", 2)
return True
else:
self.logMsg("Path is detected as follow: %s. Try direct streaming." % path, 2)
return False
def audioSubsPref(self, url, mediaSources):
def audioSubsPref(self, url):
# For transcoding only
# Present the list of audio to select from
audioStreamsList = {}
@ -292,15 +317,21 @@ class PlayUtils():
selectSubsIndex = ""
playurlprefs = "%s" % url
mediaStream = mediaSources[0].get('MediaStreams')
for stream in mediaStream:
item = self.item
try:
mediasources = item['MediaSources'][0]
mediastreams = mediasources['MediaStreams']
except (TypeError, KeyError, IndexError):
return
for stream in mediastreams:
# Since Emby returns all possible tracks together, have to sort them.
index = stream['Index']
type = stream['Type']
if 'Audio' in type:
codec = stream['Codec']
channelLayout = stream['ChannelLayout']
channelLayout = stream.get('ChannelLayout', "")
try:
track = "%s - %s - %s %s" % (index, stream['Language'], codec, channelLayout)
@ -312,6 +343,8 @@ class PlayUtils():
audioStreams.append(track)
elif 'Subtitle' in type:
if stream['IsExternal']:
continue
try:
track = "%s - %s" % (index, stream['Language'])
except:
@ -336,7 +369,7 @@ class PlayUtils():
selectAudioIndex = audioStreamsList[selected]
playurlprefs += "&AudioStreamIndex=%s" % selectAudioIndex
else: # User backed out of selection
playurlprefs += "&AudioStreamIndex=%s" % mediaSources[0]['DefaultAudioStreamIndex']
playurlprefs += "&AudioStreamIndex=%s" % mediasources['DefaultAudioStreamIndex']
else: # There's only one audiotrack.
selectAudioIndex = audioStreamsList[audioStreams[0]]
playurlprefs += "&AudioStreamIndex=%s" % selectAudioIndex
@ -352,7 +385,7 @@ class PlayUtils():
selectSubsIndex = subtitleStreamsList[selected]
playurlprefs += "&SubtitleStreamIndex=%s" % selectSubsIndex
else: # User backed out of selection
playurlprefs += "&SubtitleStreamIndex=%s" % mediaSources[0].get('DefaultSubtitleStreamIndex', "")
playurlprefs += "&SubtitleStreamIndex=%s" % mediasources.get('DefaultSubtitleStreamIndex', "")
# Get number of channels for selected audio track
audioChannels = audioStreamsChannelsList.get(selectAudioIndex, 0)

View file

@ -2,71 +2,69 @@
#################################################################################################
import datetime
import json as json
import json
import sys
import xbmc
import xbmcaddon
import xbmcplugin
import xbmcgui
import xbmcplugin
from API import API
from DownloadUtils import DownloadUtils
from PlayUtils import PlayUtils
from ClientInformation import ClientInformation
import Utils as utils
import api
import artwork
import clientinfo
import downloadutils
import playutils as putils
import playlist
import read_embyserver as embyserver
import utils
#################################################################################################
class PlaybackUtils():
clientInfo = ClientInformation()
doUtils = DownloadUtils()
api = API()
addon = xbmcaddon.Addon()
language = addon.getLocalizedString
addonName = clientInfo.getAddonName()
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()
def logMsg(self, msg, lvl=1):
className = self.__class__.__name__
utils.logMsg("%s %s" % (self.addonName, className), msg, int(lvl))
self.className = self.__class__.__name__
utils.logMsg("%s %s" % (self.addonName, self.className), msg, lvl)
def PLAY(self, result, setup = "service"):
self.logMsg("PLAY Called", 1)
def play(self, itemid, dbid=None):
self.logMsg("Play called.", 1)
api = self.api
doUtils = self.doUtils
username = utils.window('currUser')
server = utils.window('server%s' % username)
item = self.item
API = self.API
listitem = xbmcgui.ListItem()
playutils = putils.PlayUtils(item)
id = result['Id']
userdata = result['UserData']
# Get the playurl - direct play, direct stream or transcoding
playurl = PlayUtils().getPlayUrl(server, id, result)
listItem = xbmcgui.ListItem()
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)
playurl = playutils.getPlayUrl()
if not playurl:
return xbmcplugin.setResolvedUrl(int(sys.argv[1]), False, listitem)
if dbid is None:
# Item is not in Kodi database
listitem.setPath(playurl)
self.setProperties(playurl, listitem)
return xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, listitem)
############### ORGANIZE CURRENT PLAYLIST ################
@ -74,58 +72,45 @@ class PlaybackUtils():
playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
startPos = max(playlist.getposition(), 0) # Can return -1
sizePlaylist = playlist.size()
propertiesPlayback = utils.window('propertiesPlayback') == "true"
introsPlaylist = False
dummyPlaylist = False
currentPosition = startPos
self.logMsg("Playlist start position: %s" % startPos, 2)
self.logMsg("Playlist plugin position: %s" % currentPosition, 2)
self.logMsg("Playlist size: %s" % sizePlaylist, 2)
propertiesPlayback = utils.window('emby_playbackProps', windowid=10101) == "true"
introsPlaylist = False
dummyPlaylist = False
self.logMsg("Playlist start position: %s" % startPos, 1)
self.logMsg("Playlist plugin position: %s" % currentPosition, 1)
self.logMsg("Playlist size: %s" % sizePlaylist, 1)
############### RESUME POINT ################
# Resume point for widget only
timeInfo = api.getTimeInfo(result)
jumpBackSec = int(utils.settings('resumeJumpBack'))
seekTime = round(float(timeInfo.get('ResumeTime')), 6)
if seekTime > jumpBackSec:
# To avoid negative bookmark
seekTime = seekTime - jumpBackSec
# Show the additional resume dialog if launched from a widget
if homeScreen and seekTime:
# Dialog presentation
displayTime = str(datetime.timedelta(seconds=(int(seekTime))))
display_list = ["%s %s" % (self.language(30106), displayTime), self.language(30107)]
resume_result = xbmcgui.Dialog().select(self.language(30105), display_list)
if resume_result == 0:
# User selected to resume, append resume point to listitem
listItem.setProperty('StartOffset', str(seekTime))
elif resume_result > 0:
# User selected to start from beginning
seekTime = 0
else: # User cancelled the dialog
self.logMsg("User cancelled resume dialog.", 1)
return xbmcplugin.setResolvedUrl(int(sys.argv[1]), False, listItem)
userdata = API.getUserData()
seektime = API.adjustResume(userdata['Resume'])
# We need to ensure we add the intro and additional parts only once.
# Otherwise we get a loop.
if not propertiesPlayback:
utils.window('propertiesPlayback', value="true")
self.logMsg("Setting up properties in playlist.")
utils.window('emby_playbackProps', value="true", windowid=10101)
self.logMsg("Setting up properties in playlist.", 1)
if (not homeScreen and not seektime and
utils.window('emby_customPlaylist', windowid=10101) != "true"):
self.logMsg("Adding dummy file to playlist.", 2)
dummyPlaylist = True
playlist.add(playurl, listitem, index=startPos)
# Remove the original item from playlist
self.pl.removefromPlaylist(startPos+1)
# Readd the original item to playlist - via jsonrpc so we have full metadata
self.pl.insertintoPlaylist(currentPosition+1, dbid, item['Type'].lower())
currentPosition += 1
############### -- CHECK FOR INTROS ################
if utils.settings('disableCinema') == "false" and not seekTime:
if utils.settings('enableCinema') == "true" and not seektime:
# if we have any play them when the movie/show is not being resumed
url = "{server}/mediabrowser/Users/{UserId}/Items/%s/Intros?format=json&ImageTypeLimit=1&Fields=Etag" % id
url = "{server}/emby/Users/{UserId}/Items/%s/Intros?format=json" % itemid
intros = doUtils.downloadUrl(url)
if intros['TotalRecordCount'] != 0:
@ -141,17 +126,15 @@ class PlaybackUtils():
if getTrailers:
for intro in intros['Items']:
# The server randomly returns intros, process them.
introId = intro['Id']
introPlayurl = PlayUtils().getPlayUrl(server, introId, intro)
introListItem = xbmcgui.ListItem()
introPlayurl = putils.PlayUtils(intro).getPlayUrl()
self.logMsg("Adding Intro: %s" % introPlayurl, 1)
# Set listitem and properties for intros
self.setProperties(introPlayurl, intro, introListItem)
self.setListItemProps(server, introId, introListItem, intro)
pbutils = PlaybackUtils(intro)
pbutils.setProperties(introPlayurl, introListItem)
playlist.add(introPlayurl, introListItem, index=currentPosition)
self.pl.insertintoPlaylist(currentPosition, url=introPlayurl)
introsPlaylist = True
currentPosition += 1
@ -159,109 +142,126 @@ class PlaybackUtils():
############### -- ADD MAIN ITEM ONLY FOR HOMESCREEN ###############
if homeScreen and not sizePlaylist:
# Extend our current playlist with the actual item to play only if there's no playlist first
# Extend our current playlist with the actual item to play
# only if there's no playlist first
self.logMsg("Adding main item to playlist.", 1)
self.setListItemProps(server, id, listItem, result)
playlist.add(playurl, listItem, index=currentPosition)
self.pl.addtoPlaylist(dbid, item['Type'].lower())
# Ensure that additional parts are played after the main item
currentPosition += 1
############### -- CHECK FOR ADDITIONAL PARTS ################
if result.get('PartCount'):
if item.get('PartCount'):
# Only add to the playlist after intros have played
partcount = result['PartCount']
url = "{server}/mediabrowser/Videos/%s/AdditionalParts" % id
partcount = item['PartCount']
url = "{server}/emby/Videos/%s/AdditionalParts?format=json" % itemid
parts = doUtils.downloadUrl(url)
for part in parts['Items']:
partId = part['Id']
additionalPlayurl = PlayUtils().getPlayUrl(server, partId, part)
additionalListItem = xbmcgui.ListItem()
additionalPlayurl = putils.PlayUtils(part).getPlayUrl()
self.logMsg("Adding additional part: %s" % partcount, 1)
# Set listitem and properties for each additional parts
self.setProperties(additionalPlayurl, part, additionalListItem)
self.setListItemProps(server, partId, additionalListItem, part)
pbutils = PlaybackUtils(part)
pbutils.setProperties(additionalPlayurl, additionalListItem)
pbutils.setArtwork(additionalListItem)
playlist.add(additionalPlayurl, additionalListItem, index=currentPosition)
self.pl.verifyPlaylist()
currentPosition += 1
############### ADD DUMMY TO PLAYLIST #################
if (not homeScreen and introsPlaylist) or (homeScreen and sizePlaylist > 0):
# Playlist will fail on the current position. Adding dummy url
dummyPlaylist = True
self.logMsg("Adding dummy url to counter the setResolvedUrl error.", 2)
playlist.add(playurl, index=startPos)
currentPosition += 1
if dummyPlaylist:
# Added a dummy file to the playlist,
# because the first item is going to fail automatically.
self.logMsg("Processed as a playlist. First item is skipped.", 1)
return xbmcplugin.setResolvedUrl(int(sys.argv[1]), False, listitem)
# We just skipped adding properties. Reset flag for next time.
elif propertiesPlayback:
self.logMsg("Resetting properties playback flag.", 2)
utils.window('propertiesPlayback', clear=True)
utils.window('emby_playbackProps', clear=True, windowid=10101)
#self.pl.verifyPlaylist()
########## SETUP MAIN ITEM ##########
self.verifyPlaylist()
# For transcoding only, ask for audio/subs pref
if utils.window('emby_%s.playmethod' % playurl) == "Transcode":
playurl = playutils.audioSubsPref(playurl)
utils.window('emby_%s.playmethod' % playurl, value="Transcode")
listitem.setPath(playurl)
self.setProperties(playurl, listitem)
############### PLAYBACK ################
if not homeScreen and not introsPlaylist:
if homeScreen and seektime:
self.logMsg("Play as a widget item.", 1)
self.setListItem(listitem)
xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, listitem)
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)
elif ((introsPlaylist and utils.window('emby_customPlaylist', windowid=10101) == "true") or
(homeScreen and not sizePlaylist)):
# Playlist was created just now, play it.
self.logMsg("Play playlist.", 1)
xbmc.Player().play(playlist, startpos=startPos)
else:
self.logMsg("Play as a regular item.", 1)
xbmc.Player().play(playlist, startpos=startPos)
xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, listitem)
def setProperties(self, playurl, listitem):
# Set all properties necessary for plugin path playback
item = self.item
itemid = item['Id']
itemtype = item['Type']
def verifyPlaylist(self):
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)
playlistitems = '{"jsonrpc": "2.0", "method": "Playlist.GetItems", "params": { "playlistid": 1 }, "id": 1}'
items = xbmc.executeJSONRPC(playlistitems)
self.logMsg(items, 2)
if itemtype == "Episode":
utils.window('%s.refreshid' % embyitem, value=item.get('SeriesId'))
else:
utils.window('%s.refreshid' % embyitem, value=itemid)
def removeFromPlaylist(self, pos):
# 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)
playlistremove = '{"jsonrpc": "2.0", "method": "Playlist.Remove", "params": { "playlistid": 1, "position": %d }, "id": 1}' % pos
result = xbmc.executeJSONRPC(playlistremove)
self.logMsg(result, 1)
self.setArtwork(listitem)
def externalSubs(self, playurl):
def externalSubs(self, id, playurl, mediaSources):
username = utils.window('currUser')
server = utils.window('server%s' % username)
externalsubs = []
mapping = {}
mediaStream = mediaSources[0].get('MediaStreams')
item = self.item
itemid = item['Id']
try:
mediastreams = item['MediaSources'][0]['MediaStreams']
except (TypeError, KeyError, IndexError):
return
kodiindex = 0
for stream in mediaStream:
for stream in mediastreams:
index = stream['Index']
# Since Emby returns all possible tracks together, have to pull only external subtitles.
# IsTextSubtitleStream if true, is available to download from emby.
if "Subtitle" in stream['Type'] and stream['IsExternal'] and stream['IsTextSubtitleStream']:
if (stream['Type'] == "Subtitle" and
stream['IsExternal'] and stream['IsTextSubtitleStream']):
playmethod = utils.window("%splaymethod" % playurl)
if "DirectPlay" in playmethod:
# Direct play, get direct path
url = PlayUtils().directPlay(stream)
elif "DirectStream" in playmethod: # Direct stream
url = "%s/Videos/%s/%s/Subtitles/%s/Stream.srt" % (server, id, id, index)
# Direct stream
url = ("%s/Videos/%s/%s/Subtitles/%s/Stream.srt"
% (self.server, itemid, itemid, index))
# map external subtitles for mapping
mapping[kodiindex] = index
@ -269,69 +269,79 @@ class PlaybackUtils():
kodiindex += 1
mapping = json.dumps(mapping)
utils.window('%sIndexMapping' % playurl, value=mapping)
utils.window('emby_%s.indexMapping' % playurl, value=mapping)
return externalsubs
def setProperties(self, playurl, result, listItem):
# Set runtimeticks, type, refresh_id and item_id
id = result.get('Id')
type = result.get('Type', "")
utils.window("%sruntimeticks" % playurl, value=str(result.get('RunTimeTicks')))
utils.window("%stype" % playurl, value=type)
utils.window("%sitem_id" % playurl, value=id)
if type == "Episode":
utils.window("%srefresh_id" % playurl, value=result.get('SeriesId'))
else:
utils.window("%srefresh_id" % playurl, value=id)
if utils.window("%splaymethod" % playurl) != "Transcode":
# Only for direct play and direct stream
# Append external subtitles to stream
subtitleList = self.externalSubs(id, playurl, result['MediaSources'])
listItem.setSubtitles(subtitleList)
def setArt(self, list, name, path):
if name in ("thumb", "fanart_image", "small_poster", "tiny_poster", "medium_landscape", "medium_poster", "small_fanartimage", "medium_fanartimage", "fanart_noindicators"):
list.setProperty(name, path)
else:
list.setArt({name:path})
return list
def setListItemProps(self, server, id, listItem, result):
def setArtwork(self, listItem):
# Set up item and item info
api = self.api
item = self.item
artwork = self.artwork
type = result.get('Type')
people = api.getPeople(result)
studios = api.getStudios(result)
allartwork = artwork.getAllArtwork(item, parentInfo=True)
# Set artwork for listitem
arttypes = {
'poster': "Primary",
'tvshow.poster': "Primary",
'clearart': "Art",
'tvshow.clearart': "Art",
'clearlogo': "Logo",
'tvshow.clearlogo': "Logo",
'discart': "Disc",
'fanart_image': "Backdrop",
'landscape': "Thumb"
}
for arttype in arttypes:
art = arttypes[arttype]
if art == "Backdrop":
try: # Backdrop is a list, grab the first backdrop
self.setArtProp(listItem, arttype, allartwork[art][0])
except: pass
else:
self.setArtProp(listItem, arttype, allartwork[art])
def setArtProp(self, listItem, arttype, path):
if arttype in (
'thumb', 'fanart_image', 'small_poster', 'tiny_poster',
'medium_landscape', 'medium_poster', 'small_fanartimage',
'medium_fanartimage', 'fanart_noindicators'):
listItem.setProperty(arttype, path)
else:
listItem.setArt({arttype: path})
def setListItem(self, listItem):
item = self.item
type = item['Type']
API = self.API
people = API.getPeople()
studios = API.getStudios()
metadata = {
'title': result.get('Name', "Missing name"),
'year': result.get('ProductionYear'),
'plot': api.getOverview(result),
'title': item.get('Name', "Missing name"),
'year': item.get('ProductionYear'),
'plot': API.getOverview(),
'director': people.get('Director'),
'writer': people.get('Writer'),
'mpaa': api.getMpaa(result),
'genre': api.getGenre(result),
'mpaa': API.getMpaa(),
'genre': " / ".join(item['Genres']),
'studio': " / ".join(studios),
'aired': api.getPremiereDate(result),
'rating': result.get('CommunityRating'),
'votes': result.get('VoteCount')
'aired': API.getPremiereDate(),
'rating': item.get('CommunityRating'),
'votes': item.get('VoteCount')
}
if "Episode" in type:
# Only for tv shows
thumbId = result.get('SeriesId')
season = result.get('ParentIndexNumber', -1)
episode = result.get('IndexNumber', -1)
show = result.get('SeriesName', "")
thumbId = item.get('SeriesId')
season = item.get('ParentIndexNumber', -1)
episode = item.get('IndexNumber', -1)
show = item.get('SeriesName', "")
metadata['TVShowTitle'] = show
metadata['season'] = season
@ -341,122 +351,3 @@ 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,45 +2,47 @@
#################################################################################################
import json as json
import json
import xbmc
import xbmcgui
from DownloadUtils import DownloadUtils
from WebSocketClient import WebSocketThread
from ClientInformation import ClientInformation
from LibrarySync import LibrarySync
import Utils as utils
import utils
import clientinfo
import downloadutils
import kodidb_functions as kodidb
import websocket_client as wsc
#################################################################################################
class Player( xbmc.Player ):
class Player(xbmc.Player):
# Borg - multiple instances, shared state
_shared_state = {}
xbmcplayer = xbmc.Player()
doUtils = DownloadUtils()
clientInfo = ClientInformation()
ws = WebSocketThread()
librarySync = LibrarySync()
addonName = clientInfo.getAddonName()
played_information = {}
played_info = {}
playStats = {}
currentFile = None
def __init__(self, *args):
def __init__(self):
self.__dict__ = self._shared_state
self.clientInfo = clientinfo.ClientInfo()
self.addonName = self.clientInfo.getAddonName()
self.doUtils = downloadutils.DownloadUtils()
self.ws = wsc.WebSocket_Client()
self.xbmcplayer = xbmc.Player()
self.logMsg("Starting playback monitor.", 2)
def logMsg(self, msg, lvl=1):
self.className = self.__class__.__name__
utils.logMsg("%s %s" % (self.addonName, self.className), msg, int(lvl))
utils.logMsg("%s %s" % (self.addonName, self.className), msg, lvl)
def GetPlayStats(self):
return self.playStats
@ -74,31 +76,42 @@ class Player( xbmc.Player ):
self.currentFile = currentFile
# We may need to wait for info to be set in kodi monitor
itemId = utils.window("%sitem_id" % currentFile)
itemId = utils.window("emby_%s.itemid" % currentFile)
tryCount = 0
while not itemId:
xbmc.sleep(200)
itemId = utils.window("%sitem_id" % currentFile)
itemId = utils.window("emby_%s.itemid" % currentFile)
if tryCount == 20: # try 20 times or about 10 seconds
self.logMsg("Could not find itemId, cancelling playback report...", 1)
break
else: tryCount += 1
else:
self.logMsg("ONPLAYBACK_STARTED: %s ITEMID: %s" % (currentFile, itemId), 0)
self.logMsg("ONPLAYBACK_STARTED: %s itemid: %s" % (currentFile, itemId), 0)
# Only proceed if an itemId was found.
runtime = utils.window("%sruntimeticks" % currentFile)
refresh_id = utils.window("%srefresh_id" % currentFile)
playMethod = utils.window("%splaymethod" % currentFile)
itemType = utils.window("%stype" % currentFile)
embyitem = "emby_%s" % currentFile
runtime = utils.window("%s.runtime" % embyitem)
refresh_id = utils.window("%s.refreshid" % embyitem)
playMethod = utils.window("%s.playmethod" % embyitem)
itemType = utils.window("%s.type" % embyitem)
utils.window('emby_skipWatched%s' % itemId, value="true")
seekTime = xbmcplayer.getTime()
# Get playback volume
volume_query = '{"jsonrpc": "2.0", "method": "Application.GetProperties", "params": {"properties": ["volume","muted"]}, "id": 1}'
result = xbmc.executeJSONRPC(volume_query)
volume_query = {
"jsonrpc": "2.0",
"id": 1,
"method": "Application.GetProperties",
"params": {
"properties": ["volume", "muted"]
}
}
result = xbmc.executeJSONRPC(json.dumps(volume_query))
result = json.loads(result)
result = result.get('result')
@ -106,7 +119,7 @@ class Player( xbmc.Player ):
muted = result.get('muted')
# Postdata structure to send to Emby server
url = "{server}/mediabrowser/Sessions/Playing"
url = "{server}/emby/Sessions/Playing"
postdata = {
'QueueableMediaTypes': "Video",
@ -123,12 +136,22 @@ class Player( xbmc.Player ):
if playMethod == "Transcode":
# property set in PlayUtils.py
postdata['AudioStreamIndex'] = utils.window("%sAudioStreamIndex" % currentFile)
postdata['SubtitleStreamIndex'] = utils.window("%sSubtitleStreamIndex" % currentFile)
postdata['SubtitleStreamIndex'] = utils.window("%sSubtitleStreamIndex"
% currentFile)
else:
# Get the current kodi audio and subtitles and convert to Emby equivalent
track_query = '{"jsonrpc": "2.0", "method": "Player.GetProperties", "params": {"playerid": 1,"properties": ["currentsubtitle","currentaudiostream","subtitleenabled"]} , "id": 1}'
result = xbmc.executeJSONRPC(track_query)
tracks_query = {
"jsonrpc": "2.0",
"id": 1,
"method": "Player.GetProperties",
"params": {
"playerid": 1,
"properties": ["currentsubtitle","currentaudiostream","subtitleenabled"]
}
}
result = xbmc.executeJSONRPC(json.dumps(tracks_query))
result = json.loads(result)
result = result.get('result')
@ -155,9 +178,9 @@ class Player( xbmc.Player ):
# Number of audiotracks to help get Emby Index
audioTracks = len(xbmc.Player().getAvailableAudioStreams())
mapping = utils.window("%sIndexMapping" % currentFile)
mapping = utils.window("%s.indexMapping" % embyitem)
if mapping: # Set in PlaybackUtils.py
if mapping: # Set in playbackutils.py
self.logMsg("Mapping for external subtitles index: %s" % mapping, 2)
externalIndex = json.loads(mapping)
@ -167,7 +190,8 @@ class Player( xbmc.Player ):
postdata['SubtitleStreamIndex'] = externalIndex[str(indexSubs)]
else:
# Internal subtitle currently selected
postdata['SubtitleStreamIndex'] = indexSubs - len(externalIndex) + audioTracks + 1
subindex = indexSubs - len(externalIndex) + audioTracks + 1
postdata['SubtitleStreamIndex'] = subindex
else: # Direct paths enabled scenario or no external subtitles set
postdata['SubtitleStreamIndex'] = indexSubs + audioTracks + 1
@ -184,7 +208,7 @@ class Player( xbmc.Player ):
runtime = int(runtime)
except ValueError:
runtime = xbmcplayer.getTotalTime()
self.logMsg("Runtime is missing, grabbing runtime from Kodi player: %s" % runtime, 1)
self.logMsg("Runtime is missing, Kodi runtime: %s" % runtime, 1)
# Save data map for updates and position calls
data = {
@ -200,8 +224,8 @@ class Player( xbmc.Player ):
'currentPosition': int(seekTime)
}
self.played_information[currentFile] = data
self.logMsg("ADDING_FILE: %s" % self.played_information, 1)
self.played_info[currentFile] = data
self.logMsg("ADDING_FILE: %s" % self.played_info, 1)
# log some playback stats
'''if(itemType != None):
@ -225,7 +249,7 @@ class Player( xbmc.Player ):
# Get current file
currentFile = self.currentFile
data = self.played_information.get(currentFile)
data = self.played_info.get(currentFile)
# only report playback if emby has initiated the playback (item_id has value)
if data:
@ -239,15 +263,23 @@ class Player( xbmc.Player ):
# Get playback volume
volume_query = '{"jsonrpc": "2.0", "method": "Application.GetProperties", "params": {"properties": ["volume","muted"]}, "id": 1}'
result = xbmc.executeJSONRPC(volume_query)
volume_query = {
"jsonrpc": "2.0",
"id": 1,
"method": "Application.GetProperties",
"params": {
"properties": ["volume", "muted"]
}
}
result = xbmc.executeJSONRPC(json.dumps(volume_query))
result = json.loads(result)
result = result.get('result')
volume = result.get('volume')
muted = result.get('muted')
# Postdata for the websocketclient report
postdata = {
@ -269,8 +301,18 @@ class Player( xbmc.Player ):
else:
# Get current audio and subtitles track
track_query = '{"jsonrpc": "2.0", "method": "Player.GetProperties", "params": {"playerid":1,"properties": ["currentsubtitle","currentaudiostream","subtitleenabled"]} , "id": 1}'
result = xbmc.executeJSONRPC(track_query)
tracks_query = {
"jsonrpc": "2.0",
"id": 1,
"method": "Player.GetProperties",
"params": {
"playerid": 1,
"properties": ["currentsubtitle","currentaudiostream","subtitleenabled"]
}
}
result = xbmc.executeJSONRPC(json.dumps(tracks_query))
result = json.loads(result)
result = result.get('result')
@ -297,7 +339,7 @@ class Player( xbmc.Player ):
# Number of audiotracks to help get Emby Index
audioTracks = len(xbmc.Player().getAvailableAudioStreams())
mapping = utils.window("%sIndexMapping" % currentFile)
mapping = utils.window("emby_%s.indexMapping" % currentFile)
if mapping: # Set in PlaybackUtils.py
@ -306,13 +348,16 @@ class Player( xbmc.Player ):
if externalIndex.get(str(indexSubs)):
# If the current subtitle is in the mapping
data['SubtitleStreamIndex'], postdata['SubtitleStreamIndex'] = [externalIndex[str(indexSubs)]] * 2
subindex = [externalIndex[str(indexSubs)]] * 2
data['SubtitleStreamIndex'], postdata['SubtitleStreamIndex'] = subindex
else:
# Internal subtitle currently selected
data['SubtitleStreamIndex'], postdata['SubtitleStreamIndex'] = [indexSubs - len(externalIndex) + audioTracks + 1] * 2
subindex = [indexSubs - len(externalIndex) + audioTracks + 1] * 2
data['SubtitleStreamIndex'], postdata['SubtitleStreamIndex'] = subindex
else: # Direct paths enabled scenario or no external subtitles set
data['SubtitleStreamIndex'], postdata['SubtitleStreamIndex'] = [indexSubs + audioTracks + 1] * 2
subindex = [indexSubs + audioTracks + 1] * 2
data['SubtitleStreamIndex'], postdata['SubtitleStreamIndex'] = subindex
else:
data['SubtitleStreamIndex'], postdata['SubtitleStreamIndex'] = [""] * 2
@ -326,8 +371,8 @@ class Player( xbmc.Player ):
currentFile = self.currentFile
self.logMsg("PLAYBACK_PAUSED: %s" % currentFile, 2)
if self.played_information.get(currentFile):
self.played_information[currentFile]['paused'] = True
if self.played_info.get(currentFile):
self.played_info[currentFile]['paused'] = True
self.reportPlayback()
@ -336,8 +381,8 @@ class Player( xbmc.Player ):
currentFile = self.currentFile
self.logMsg("PLAYBACK_RESUMED: %s" % currentFile, 2)
if self.played_information.get(currentFile):
self.played_information[currentFile]['paused'] = False
if self.played_info.get(currentFile):
self.played_info[currentFile]['paused'] = False
self.reportPlayback()
@ -346,15 +391,17 @@ class Player( xbmc.Player ):
currentFile = self.currentFile
self.logMsg("PLAYBACK_SEEK: %s" % currentFile, 2)
if self.played_information.get(currentFile):
if self.played_info.get(currentFile):
position = self.xbmcplayer.getTime()
self.played_information[currentFile]['currentPosition'] = position
self.played_info[currentFile]['currentPosition'] = position
self.reportPlayback()
def onPlayBackStopped( self ):
# Will be called when user stops xbmc playing a file
self.logMsg("ONPLAYBACK_STOPPED", 2)
xbmcgui.Window(10101).clearProperties()
self.logMsg("Clear playlist properties.")
self.stopAll()
def onPlayBackEnded( self ):
@ -364,14 +411,16 @@ class Player( xbmc.Player ):
def stopAll(self):
if not self.played_information:
doUtils = self.doUtils
if not self.played_info:
return
self.logMsg("Played_information: %s" % self.played_information, 1)
self.logMsg("Played_information: %s" % self.played_info, 1)
# Process each items
for item in self.played_information:
for item in self.played_info:
data = self.played_information.get(item)
data = self.played_info.get(item)
if data:
self.logMsg("Item path: %s" % item, 2)
@ -379,47 +428,61 @@ class Player( xbmc.Player ):
runtime = data['runtime']
currentPosition = data['currentPosition']
itemId = data['item_id']
itemid = data['item_id']
refresh_id = data['refresh_id']
currentFile = data['currentfile']
type = data['Type']
playMethod = data['playmethod']
if currentPosition and runtime:
try:
percentComplete = (currentPosition * 10000000) / int(runtime)
markPlayedAt = float(utils.settings('markPlayed')) / 100
except ZeroDivisionError:
# Runtime is 0.
percentComplete = 0
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")
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.stopPlayback(data)
offerDelete = utils.settings('offerDelete') == "true"
offerTypeDelete = False
if type == "Episode" and utils.settings('offerDeleteTV') == "true":
offerTypeDelete = True
elif type == "Movie" and utils.settings('offerDeleteMovies') == "true":
offerTypeDelete = True
if percentComplete >= markPlayedAt and offerDelete and offerTypeDelete:
# Make the bigger setting be able to disable option easily.
self.logMsg("Offering deletion for: %s." % itemId, 1)
return_value = xbmcgui.Dialog().yesno("Offer Delete", "Delete %s" % currentFile.split("/")[-1], "on Emby Server?")
if return_value:
# Delete Kodi entry before Emby
listItem = [itemId]
LibrarySync().removefromDB(listItem, True)
# Stop transcoding
if playMethod == "Transcode":
self.logMsg("Transcoding for %s terminated." % itemId, 1)
deviceId = self.clientInfo.getMachineId()
url = "{server}/mediabrowser/Videos/ActiveEncodings?DeviceId=%s" % deviceId
self.doUtils.downloadUrl(url, type="DELETE")
self.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.played_information.clear()
# 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()
def stopPlayback(self, data):
@ -429,12 +492,11 @@ class Player( xbmc.Player ):
currentPosition = data['currentPosition']
positionTicks = int(currentPosition * 10000000)
url = "{server}/mediabrowser/Sessions/Playing/Stopped"
url = "{server}/emby/Sessions/Playing/Stopped"
postdata = {
'ItemId': itemId,
'MediaSourceId': itemId,
'PositionTicks': positionTicks
}
self.doUtils.downloadUrl(url, postBody=postdata, type="POST")

View file

@ -1,22 +1,21 @@
#################################################################################################
# UserClient thread
#################################################################################################
# -*- coding: utf-8 -*-
##################################################################################################
import hashlib
import threading
import xbmc
import xbmcgui
import xbmcaddon
import xbmcvfs
import threading
import hashlib
import json as json
import artwork
import utils
import clientinfo
import downloadutils
import KodiMonitor
import Utils as utils
from ClientInformation import ClientInformation
from DownloadUtils import DownloadUtils
from Player import Player
from API import API
##################################################################################################
class UserClient(threading.Thread):
@ -24,16 +23,7 @@ class UserClient(threading.Thread):
# Borg - multiple instances, shared state
_shared_state = {}
clientInfo = ClientInformation()
doUtils = DownloadUtils()
KodiMonitor = KodiMonitor.Kodi_Monitor()
addonName = clientInfo.getAddonName()
addon = xbmcaddon.Addon()
WINDOW = xbmcgui.Window(10000)
stopClient = False
logLevel = int(addon.getSetting('logLevel'))
auth = True
retry = 0
@ -44,25 +34,25 @@ class UserClient(threading.Thread):
HasAccess = True
AdditionalUser = []
def __init__(self, *args):
userSettings = None
def __init__(self):
self.__dict__ = self._shared_state
threading.Thread.__init__(self, *args)
self.addon = xbmcaddon.Addon()
self.addonName = clientinfo.ClientInfo().getAddonName()
self.doUtils = downloadutils.DownloadUtils()
self.logLevel = int(utils.settings('logLevel'))
threading.Thread.__init__(self)
def logMsg(self, msg, lvl=1):
className = self.__class__.__name__
utils.logMsg("%s %s" % (self.addonName, className), str(msg), int(lvl))
utils.logMsg("%s %s" % (self.addonName, className), msg, lvl)
def getUsername(self):
username = utils.settings('username')
if (username == ""):
self.logMsg("No username saved.", 2)
return ""
return username
def getAdditionalUsers(self):
@ -71,11 +61,21 @@ class UserClient(threading.Thread):
if additionalUsers:
self.AdditionalUser = additionalUsers.split(',')
def getUsername(self):
username = utils.settings('username')
if not username:
self.logMsg("No username saved.", 2)
return ""
return username
def getLogLevel(self):
try:
logLevel = int(utils.settings('logLevel'))
except:
except ValueError:
logLevel = 0
return logLevel
@ -83,71 +83,84 @@ class UserClient(threading.Thread):
def getUserId(self):
username = self.getUsername()
w_userId = self.WINDOW.getProperty('userId%s' % username)
w_userId = utils.window('emby_userId%s' % username)
s_userId = utils.settings('userId%s' % username)
# Verify the window property
if (w_userId != ""):
self.logMsg("Returning userId from WINDOW for username: %s UserId: %s" % (username, w_userId), 2)
if w_userId:
if not s_userId:
# Save access token if it's missing from settings
utils.settings('userId%s' % username, value=w_userId)
self.logMsg(
"Returning userId from WINDOW for username: %s UserId: %s"
% (username, w_userId), 2)
return w_userId
# Verify the settings
elif (s_userId != ""):
self.logMsg("Returning userId from SETTINGS for username: %s userId: %s" % (username, s_userId), 2)
elif s_userId:
self.logMsg(
"Returning userId from SETTINGS for username: %s userId: %s"
% (username, s_userId), 2)
return s_userId
# No userId found
else:
self.logMsg("No userId saved for username: %s." % username)
return
self.logMsg("No userId saved for username: %s." % username, 1)
def getServer(self, prefix=True):
alternate = utils.settings('altip') == "true"
# For https support
HTTPS = utils.settings('https')
host = utils.settings('ipaddress')
port = utils.settings('port')
# Alternate host
if alternate:
HTTPS = utils.settings('secondhttps')
# Alternate host
HTTPS = utils.settings('secondhttps') == "true"
host = utils.settings('secondipaddress')
port = utils.settings('secondport')
else:
# Original host
HTTPS = utils.settings('https') == "true"
host = utils.settings('ipaddress')
port = utils.settings('port')
server = host + ":" + port
if host == "":
if not host:
self.logMsg("No server information saved.", 2)
return ""
return False
# If https is true
if prefix and (HTTPS == "true"):
if prefix and HTTPS:
server = "https://%s" % server
return server
# If https is false
elif prefix and (HTTPS == "false"):
elif prefix and not HTTPS:
server = "http://%s" % server
return server
# If only the host:port is required
elif (prefix == False):
elif not prefix:
return server
def getToken(self):
username = self.getUsername()
w_token = self.WINDOW.getProperty('accessToken%s' % username)
w_token = utils.window('emby_accessToken%s' % username)
s_token = utils.settings('accessToken')
# Verify the window property
if (w_token != ""):
self.logMsg("Returning accessToken from WINDOW for username: %s accessToken: %s" % (username, w_token), 2)
if w_token:
if not s_token:
# Save access token if it's missing from settings
utils.settings('accessToken', value=w_token)
self.logMsg(
"Returning accessToken from WINDOW for username: %s accessToken: %s"
% (username, w_token), 2)
return w_token
# Verify the settings
elif (s_token != ""):
self.logMsg("Returning accessToken from SETTINGS for username: %s accessToken: %s" % (username, s_token), 2)
self.WINDOW.setProperty('accessToken%s' % username, s_token)
elif s_token:
self.logMsg(
"Returning accessToken from SETTINGS for username: %s accessToken: %s"
% (username, s_token), 2)
utils.window('emby_accessToken%s' % username, value=s_token)
return s_token
else:
self.logMsg("No token found.")
self.logMsg("No token found.", 1)
return ""
def getSSLverify(self):
@ -174,71 +187,63 @@ class UserClient(threading.Thread):
def setUserPref(self):
player = Player()
server = self.getServer()
userId = self.getUserId()
url = "{server}/mediabrowser/Users/{UserId}?format=json"
result = self.doUtils.downloadUrl(url)
doUtils = self.doUtils
url = "{server}/emby/Users/{UserId}?format=json"
result = doUtils.downloadUrl(url)
self.userSettings = result
# Set user image for skin display
self.WINDOW.setProperty("EmbyUserImage",API().getUserArtwork(result,"Primary"))
if result.get('PrimaryImageTag'):
utils.window('EmbyUserImage', value=artwork.Artwork().getUserArtwork(result, 'Primary'))
# Load the resume point from Emby and set as setting
url = "{server}/mediabrowser/System/Configuration?format=json"
result = self.doUtils.downloadUrl(url)
# Set resume point max
url = "{server}/emby/System/Configuration?format=json"
result = doUtils.downloadUrl(url)
utils.settings('markPlayed', value=str(result['MaxResumePct']))
return True
def getPublicUsers(self):
server = self.getServer()
# Get public Users
url = "%s/mediabrowser/Users/Public?format=json" % server
url = "%s/emby/Users/Public?format=json" % server
result = self.doUtils.downloadUrl(url, authenticate=False)
users = []
if (result != ""):
users = result
if result != "":
return result
else:
# Server connection failed
return False
return users
def hasAccess(self):
url = "{server}/mediabrowser/Users"
# hasAccess is verified in service.py
url = "{server}/emby/Users?format=json"
result = self.doUtils.downloadUrl(url)
if result is False:
# Access is restricted
self.logMsg("Access is restricted.")
if result == False:
# Access is restricted, set in downloadutils.py via exception
self.logMsg("Access is restricted.", 1)
self.HasAccess = False
return
elif self.WINDOW.getProperty('Server_online') != "true":
# Server connection failed
return
if self.WINDOW.getProperty("Server_status") == "restricted":
self.logMsg("Access is granted.")
elif utils.window('emby_online') != "true":
# Server connection failed
pass
elif utils.window('emby_serverStatus') == "restricted":
self.logMsg("Access is granted.", 1)
self.HasAccess = True
self.WINDOW.setProperty("Server_status", "")
utils.window('emby_serverStatus', clear=True)
xbmcgui.Dialog().notification("Emby server", "Access is enabled.")
return
def loadCurrUser(self, authenticated=False):
WINDOW = self.WINDOW
doUtils = self.doUtils
username = self.getUsername()
userId = self.getUserId()
# Only to be used if token exists
self.currUserId = self.getUserId()
self.currUserId = userId
self.currServer = self.getServer()
self.currToken = self.getToken()
self.ssl = self.getSSLverify()
@ -246,21 +251,21 @@ class UserClient(threading.Thread):
# Test the validity of current token
if authenticated == False:
url = "%s/mediabrowser/Users/%s" % (self.currServer, self.currUserId)
WINDOW.setProperty("currUser", username)
WINDOW.setProperty("accessToken%s" % username, self.currToken)
url = "%s/emby/Users/%s?format=json" % (self.currServer, userId)
utils.window('emby_currUser', value=userId)
utils.window('emby_accessToken%s' % userId, value=self.currToken)
result = doUtils.downloadUrl(url)
if result == 401:
# Token is no longer valid
self.resetClient()
return False
# Set to windows property
WINDOW.setProperty("currUser", username)
WINDOW.setProperty("accessToken%s" % username, self.currToken)
WINDOW.setProperty("server%s" % username, self.currServer)
WINDOW.setProperty("server_%s" % username, self.getServer(prefix=False))
WINDOW.setProperty("userId%s" % username, self.currUserId)
utils.window('emby_currUser', value=userId)
utils.window('emby_accessToken%s' % userId, value=self.currToken)
utils.window('emby_server%s' % userId, value=self.currServer)
utils.window('emby_server_%s' % userId, value=self.getServer(prefix=False))
# Set DownloadUtils values
doUtils.setUsername(username)
@ -273,188 +278,194 @@ class UserClient(threading.Thread):
# Start DownloadUtils session
doUtils.startSession()
self.getAdditionalUsers()
self.currUser = username
# Set user preferences in settings
self.currUser = username
self.setUserPref()
def authenticate(self):
WINDOW = self.WINDOW
addon = self.addon
username = self.getUsername()
server = self.getServer()
# Get /profile/addon_data
addondir = xbmc.translatePath(self.addon.getAddonInfo('profile')).decode('utf-8')
hasSettings = xbmcvfs.exists("%ssettings.xml" % addondir)
username = self.getUsername()
server = self.getServer()
# If there's no settings.xml
if (hasSettings == 0):
self.logMsg("No settings.xml found.")
if not hasSettings:
self.logMsg("No settings.xml found.", 1)
self.auth = False
return
# If no user information
if (server == "") or (username == ""):
self.logMsg("Missing server information.")
elif not server or not username:
self.logMsg("Missing server information.", 1)
self.auth = False
return
# If there's a token
if (self.getToken() != ""):
# If there's a token, load the user
elif self.getToken():
result = self.loadCurrUser()
if result == False:
pass
else:
self.logMsg("Current user: %s" % self.currUser, 0)
self.logMsg("Current userId: %s" % self.currUserId, 0)
self.logMsg("Current accessToken: %s" % self.currToken, 0)
self.logMsg("Current user: %s" % self.currUser, 1)
self.logMsg("Current userId: %s" % self.currUserId, 1)
self.logMsg("Current accessToken: %s" % self.currToken, 2)
return
##### AUTHENTICATE USER #####
users = self.getPublicUsers()
password = ""
# Find user in list
for user in users:
name = user[u'Name']
userHasPassword = False
name = user['Name']
if (unicode(username, 'utf-8') in name):
# Verify if user has a password
if (user.get("HasPassword") == True):
userHasPassword = True
if username.decode('utf-8') in name:
# If user has password
if (userHasPassword):
password = xbmcgui.Dialog().input("Enter password for user: %s" % username, option=xbmcgui.ALPHANUM_HIDE_INPUT)
if user['HasPassword'] == True:
password = xbmcgui.Dialog().input(
heading="Enter password for user: %s" % username,
option=xbmcgui.ALPHANUM_HIDE_INPUT)
# If password dialog is cancelled
if (password == ""):
if not password:
self.logMsg("No password entered.", 0)
self.WINDOW.setProperty("Server_status", "Stop")
utils.window('emby_serverStatus', value="Stop")
self.auth = False
return
break
else:
# Manual login, user is hidden
password = xbmcgui.Dialog().input("Enter password for user: %s" % username, option=xbmcgui.ALPHANUM_HIDE_INPUT)
password = xbmcgui.Dialog().input(
heading="Enter password for user: %s" % username,
option=xbmcgui.ALPHANUM_HIDE_INPUT)
sha1 = hashlib.sha1(password)
sha1 = sha1.hexdigest()
# Authenticate username and password
url = "%s/mediabrowser/Users/AuthenticateByName?format=json" % server
url = "%s/emby/Users/AuthenticateByName?format=json" % server
data = {'username': username, 'password': sha1}
self.logMsg(data, 2)
result = self.doUtils.downloadUrl(url, postBody=data, type="POST", authenticate=False)
accessToken = None
try:
self.logMsg("Auth_Reponse: %s" % result, 1)
accessToken = result[u'AccessToken']
except:
pass
self.logMsg("Auth response: %s" % result, 1)
accessToken = result['AccessToken']
if (result != None and accessToken != None):
except (KeyError, TypeError):
self.logMsg("Failed to retrieve the api key.", 1)
accessToken = None
if accessToken is not None:
self.currUser = username
xbmcgui.Dialog().notification("Emby server", "Welcome %s!" % self.currUser)
userId = result[u'User'][u'Id']
utils.settings("accessToken", accessToken)
utils.settings("userId%s" % username, userId)
self.logMsg("User Authenticated: %s" % accessToken)
userId = result['User']['Id']
utils.settings('accessToken', value=accessToken)
utils.settings('userId%s' % username, value=userId)
self.logMsg("User Authenticated: %s" % accessToken, 1)
self.loadCurrUser(authenticated=True)
self.WINDOW.setProperty("Server_status", "")
utils.window('emby_serverStatus', clear=True)
self.retry = 0
return
else:
self.logMsg("User authentication failed.")
utils.settings("accessToken", "")
utils.settings("userId%s" % username, "")
self.logMsg("User authentication failed.", 1)
utils.settings('accessToken', value="")
utils.settings('userId%s' % username, value="")
xbmcgui.Dialog().ok("Error connecting", "Invalid username or password.")
# Give two attempts at entering password
self.retry += 1
if self.retry == 2:
self.logMsg("Too many retries. You can retry by selecting the option in the addon settings.")
self.WINDOW.setProperty("Server_status", "Stop")
xbmcgui.Dialog().ok("Error connecting", "Failed to authenticate too many times. You can retry by selecting the option in the addon settings.")
self.logMsg(
"""Too many retries. You can retry by resetting
attempts in the addon settings.""", 1)
utils.window('emby_serverStatus', value="Stop")
xbmcgui.Dialog().ok(
heading="Error connecting",
line1="Failed to authenticate too many times.",
line2="You can retry by resetting attempts in the addon settings.")
self.retry += 1
self.auth = False
return
def resetClient(self):
username = self.getUsername()
self.logMsg("Reset UserClient authentication.", 1)
if (self.currToken != None):
username = self.getUsername()
if self.currToken is not None:
# In case of 401, removed saved token
utils.settings("accessToken", "")
self.WINDOW.setProperty("accessToken%s" % username, "")
utils.settings('accessToken', value="")
utils.window('emby_accessToken%s' % username, clear=True)
self.currToken = None
self.logMsg("User token has been removed.", 1)
self.auth = True
self.currUser = None
return
def run(self):
self.logMsg("|---- Starting UserClient ----|", 0)
monitor = xbmc.Monitor()
self.logMsg("----===## Starting UserClient ##===----", 0)
while not self.KodiMonitor.abortRequested():
while not monitor.abortRequested():
# Verify the log level
currLogLevel = self.getLogLevel()
if self.logLevel != currLogLevel:
# Set new log level
self.logLevel = currLogLevel
utils.window('emby_logLevel', value=str(currLogLevel))
self.logMsg("New Log Level: %s" % currLogLevel, 0)
self.WINDOW.setProperty('getLogLevel', str(currLogLevel))
if (self.WINDOW.getProperty("Server_status") != ""):
status = self.WINDOW.getProperty("Server_status")
status = utils.window('emby_serverStatus')
if status:
# Verify the connection status to server
if status == "restricted":
# Parental control is restricting access
self.HasAccess = False
elif status == "401":
self.WINDOW.setProperty("Server_status", "Auth")
# Revoked token
# Unauthorized access, revoke token
utils.window('emby_serverStatus', value="Auth")
self.resetClient()
if self.auth and (self.currUser == None):
status = self.WINDOW.getProperty("Server_status")
if (status == "") or (status == "Auth"):
if self.auth and (self.currUser is None):
# Try to authenticate user
status = utils.window('emby_serverStatus')
if not status or status == "Auth":
# Set auth flag because we no longer need
# to authenticate the user
self.auth = False
self.authenticate()
if (self.auth == False) and (self.currUser == None):
# Only if there's information found to login
if not self.auth and (self.currUser is None):
# If authenticate failed.
server = self.getServer()
username = self.getUsername()
status = self.WINDOW.getProperty("Server_status")
status = utils.window('emby_serverStatus')
# 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)
# The status Stop is for when user cancelled password dialog.
if server and username and status != "Stop":
# Only if there's information found to login
self.logMsg("Server found: %s" % server, 2)
self.logMsg("Username found: %s" % username, 2)
self.auth = True
# If stopping the client didn't work
if self.stopClient == True:
# If stopping the client didn't work
break
if self.KodiMonitor.waitForAbort(1):
if monitor.waitForAbort(1):
# Abort was requested while waiting. We should exit
break
self.doUtils.stopSession()
self.logMsg("|---- UserClient Stopped ----|", 0)
self.logMsg("##===---- UserClient Stopped ----===##", 0)
def stopClient(self):
# As last resort
# When emby for kodi terminates
self.stopClient = True

View file

@ -1,59 +1,76 @@
#################################################################################################
# utils
# -*- coding: utf-8 -*-
#################################################################################################
import xbmc
import xbmcgui
import xbmcaddon
import xbmcvfs
import json
import os
import cProfile
import pstats
import time
import inspect
import pstats
import sqlite3
import string
import time
import unicodedata
import xml.etree.ElementTree as etree
from API import API
from PlayUtils import PlayUtils
from DownloadUtils import DownloadUtils
import xbmc
import xbmcaddon
import xbmcgui
import xbmcvfs
downloadUtils = DownloadUtils()
addon = xbmcaddon.Addon()
language = addon.getLocalizedString
#################################################################################################
def logMsg(title, msg, level = 1):
def logMsg(title, msg, level=1):
WINDOW = xbmcgui.Window(10000)
# Get the logLevel set in UserClient
logLevel = int(WINDOW.getProperty('getLogLevel'))
if(logLevel >= level):
if(logLevel == 2): # inspect.stack() is expensive
try:
xbmc.log(title + " -> " + inspect.stack()[1][3] + " : " + str(msg))
logLevel = int(window('emby_logLevel'))
except ValueError:
logLevel = 0
if logLevel >= level:
if logLevel == 2: # inspect.stack() is expensive
try:
xbmc.log("%s -> %s : %s" % (title, inspect.stack()[1][3], msg))
except UnicodeEncodeError:
xbmc.log(title + " -> " + inspect.stack()[1][3] + " : " + str(msg.encode('utf-8')))
xbmc.log("%s -> %s : %s" % (title, inspect.stack()[1][3], msg.encode('utf-8')))
else:
try:
xbmc.log(title + " -> " + str(msg))
xbmc.log("%s -> %s" % (title, msg))
except UnicodeEncodeError:
xbmc.log(title + " -> " + str(msg.encode('utf-8')))
xbmc.log("%s -> %s" % (title, msg.encode('utf-8')))
def convertEncoding(data):
#nasty hack to make sure we have a unicode string
try:
return data.decode('utf-8')
except:
return data
def window(property, value=None, clear=False, windowid=10000):
# Get or set window property
WINDOW = xbmcgui.Window(windowid)
def KodiSQL(type="video"):
if clear:
WINDOW.clearProperty(property)
elif value is not None:
WINDOW.setProperty(property, value)
else:
return WINDOW.getProperty(property)
if type == "music":
def settings(setting, value=None):
# Get or add addon setting
addon = xbmcaddon.Addon(id='plugin.video.emby')
if value is not None:
addon.setSetting(setting, value)
else:
return addon.getSetting(setting)
def language(stringid):
# Central string retrieval
addon = xbmcaddon.Addon(id='plugin.video.emby')
string = addon.getLocalizedString(stringid)
return string
def kodiSQL(type="video"):
if type == "emby":
dbPath = xbmc.translatePath("special://database/emby.db").decode('utf-8')
elif type == "music":
dbPath = getKodiMusicDBPath()
elif type == "texture":
dbPath = xbmc.translatePath("special://database/Textures13.db").decode('utf-8')
@ -95,218 +112,139 @@ def getKodiMusicDBPath():
% dbVersion.get(kodibuild, "")).decode('utf-8')
return dbPath
def prettifyXml(elem):
rough_string = etree.tostring(elem, "utf-8")
reparsed = minidom.parseString(rough_string)
return reparsed.toprettyxml(indent="\t")
def 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 startProfiling():
pr = cProfile.Profile()
pr.enable()
return pr
def stopProfiling(pr, profileName):
pr.disable()
ps = pstats.Stats(pr)
addondir = xbmc.translatePath(xbmcaddon.Addon(id='plugin.video.emby').getAddonInfo('profile'))
profiles = xbmc.translatePath("%sprofiles/"
% xbmcaddon.Addon().getAddonInfo('profile')).decode('utf-8')
fileTimeStamp = time.strftime("%Y-%m-%d %H-%M-%S")
tabFileNamepath = os.path.join(addondir, "profiles")
tabFileName = os.path.join(addondir, "profiles" , profileName + "_profile_(" + fileTimeStamp + ").tab")
if not xbmcvfs.exists(profiles):
# Create the profiles folder
xbmcvfs.mkdir(profiles)
if not xbmcvfs.exists(tabFileNamepath):
xbmcvfs.mkdir(tabFileNamepath)
timestamp = time.strftime("%Y-%m-%d %H-%M-%S")
profile = "%s%s_profile_(%s).tab" % (profiles, profileName, timestamp)
f = open(tabFileName, 'wb')
f = open(profile, 'wb')
f.write("NumbCalls\tTotalTime\tCumulativeTime\tFunctionName\tFileName\r\n")
for (key, value) in ps.stats.items():
(filename, count, func_name) = key
(ccalls, ncalls, total_time, cumulative_time, callers) = value
try:
f.write(str(ncalls) + "\t" + "{:10.4f}".format(total_time) + "\t" + "{:10.4f}".format(cumulative_time) + "\t" + func_name + "\t" + filename + "\r\n")
f.write(
"%s\t%s\t%s\t%s\t%s\r\n"
% (ncalls, "{:10.4f}".format(total_time),
"{:10.4f}".format(cumulative_time), func_name, filename))
except ValueError:
f.write(str(ncalls) + "\t" + "{0}".format(total_time) + "\t" + "{0}".format(cumulative_time) + "\t" + func_name + "\t" + filename + "\r\n")
f.write(
"%s\t%s\t%s\t%s\t%s\r\n"
% (ncalls, "{0}".format(total_time),
"{0}".format(cumulative_time), func_name, filename))
f.close()
def indent(elem, level=0):
# Prettify xml trees
i = "\n" + level*" "
if len(elem):
if not elem.text or not elem.text.strip():
elem.text = i + " "
if not elem.tail or not elem.tail.strip():
elem.tail = i
for elem in elem:
indent(elem, level+1)
if not elem.tail or not elem.tail.strip():
elem.tail = i
else:
if level and (not elem.tail or not elem.tail.strip()):
elem.tail = i
def createSources():
# To make Master lock compatible
path = xbmc.translatePath("special://profile/").decode("utf-8")
xmlpath = "%ssources.xml" % path
if xbmcvfs.exists(xmlpath):
# Modify the existing file
try:
xmlparse = etree.parse(xmlpath)
except:
root = etree.Element('sources')
else:
root = xmlparse.getroot()
video = root.find('video')
if video is None:
video = etree.SubElement(root, 'video')
else:
# We need to create the file
root = etree.Element('sources')
video = etree.SubElement(root, 'video')
# Add elements
etree.SubElement(video, 'default', attrib={'pathversion': "1"})
# First dummy source
source_one = etree.SubElement(video, 'source')
etree.SubElement(source_one, 'name').text = "Emby"
etree.SubElement(source_one, 'path', attrib={'pathversion': "1"}).text = (
"smb://embydummy/dummypath1/"
)
etree.SubElement(source_one, 'allowsharing').text = "true"
# Second dummy source
source_two = etree.SubElement(video, 'source')
etree.SubElement(source_two, 'name').text = "Emby"
etree.SubElement(source_two, 'path', attrib={'pathversion': "1"}).text = (
"smb://embydummy/dummypath2/"
)
etree.SubElement(source_two, 'allowsharing').text = "true"
try:
indent(root)
except:pass
etree.ElementTree(root).write(xmlpath)
def pathsubstitution(add=True):
path = xbmc.translatePath('special://userdata').decode('utf-8')
xmlpath = "%sadvancedsettings.xml" % path
xmlpathexists = xbmcvfs.exists(xmlpath)
# original address
originalServer = settings('ipaddress')
originalPort = settings('port')
originalHttp = settings('https') == "true"
if originalHttp:
originalHttp = "https"
else:
originalHttp = "http"
# Process add or deletion
if add:
# second address
secondServer = settings('secondipaddress')
secondPort = settings('secondport')
secondHttp = settings('secondhttps') == "true"
if secondHttp:
secondHttp = "https"
else:
secondHttp = "http"
logMsg("EMBY", "Original address: %s://%s:%s, alternate is: %s://%s:%s" % (originalHttp, originalServer, originalPort, secondHttp, secondServer, secondPort), 1)
if xmlpathexists:
# we need to modify the file.
try:
xmlparse = etree.parse(xmlpath)
except: # Document is blank
root = etree.Element('advancedsettings')
else:
root = xmlparse.getroot()
pathsubs = root.find('pathsubstitution')
if pathsubs is None:
pathsubs = etree.SubElement(root, 'pathsubstitution')
else:
# we need to create the file.
root = etree.Element('advancedsettings')
pathsubs = etree.SubElement(root, 'pathsubstitution')
substitute = etree.SubElement(pathsubs, 'substitute')
# From original address
etree.SubElement(substitute, 'from').text = "%s://%s:%s" % (originalHttp, originalServer, originalPort)
# To secondary address
etree.SubElement(substitute, 'to').text = "%s://%s:%s" % (secondHttp, secondServer, secondPort)
etree.ElementTree(root).write(xmlpath)
settings('pathsub', "true")
else: # delete the path substitution, we don't need it anymore.
logMsg("EMBY", "Alternate address is disabled, removing path substitution for: %s://%s:%s" % (originalHttp, originalServer, originalPort), 1)
xmlparse = etree.parse(xmlpath)
root = xmlparse.getroot()
iterator = root.getiterator("pathsubstitution")
for substitutes in iterator:
for substitute in substitutes:
frominsert = substitute.find(".//from").text == "%s://%s:%s" % (originalHttp, originalServer, originalPort)
if frominsert:
# Found a match, in case there's more than one substitution.
substitutes.remove(substitute)
etree.ElementTree(root).write(xmlpath)
settings('pathsub', "false")
def settings(setting, value = None):
# Get or add addon setting
addon = xbmcaddon.Addon()
if value:
addon.setSetting(setting, value)
else:
return addon.getSetting(setting)
def window(property, value = None, clear = False):
# Get or set window property
WINDOW = xbmcgui.Window(10000)
if clear:
WINDOW.clearProperty(property)
elif value:
WINDOW.setProperty(property, value)
else:
return WINDOW.getProperty(property)
def normalize_string(text):
# For theme media, do not modify unless
# modified in TV Tunes
text = text.replace(":", "")
text = text.replace("/", "-")
text = text.replace("\\", "-")
text = text.replace("<", "")
text = text.replace(">", "")
text = text.replace("*", "")
text = text.replace("?", "")
text = text.replace('|', "")
text = text.strip()
# Remove dots from the last character as windows can not have directories
# with dots at the end
text = text.rstrip('.')
text = unicodedata.normalize('NFKD', unicode(text, 'utf-8')).encode('ascii', 'ignore')
return text
def normalize_nodes(text):
# For video nodes
text = text.replace(":", "")
@ -327,99 +265,223 @@ def normalize_nodes(text):
return text
def reloadProfile():
# Useful to reload the add-on without restarting Kodi.
profile = xbmc.getInfoLabel('System.ProfileName')
xbmc.executebuiltin("LoadProfile(%s)" % profile)
def normalize_string(text):
# For theme media, do not modify unless
# modified in TV Tunes
text = text.replace(":", "")
text = text.replace("/", "-")
text = text.replace("\\", "-")
text = text.replace("<", "")
text = text.replace(">", "")
text = text.replace("*", "")
text = text.replace("?", "")
text = text.replace('|', "")
text = text.strip()
# Remove dots from the last character as windows can not have directories
# with dots at the end
text = text.rstrip('.')
text = unicodedata.normalize('NFKD', unicode(text, 'utf-8')).encode('ascii', 'ignore')
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 reset():
video = root.find('video')
if video is None:
video = etree.SubElement(root, 'video')
etree.SubElement(video, 'default', attrib={'pathversion': "1"})
WINDOW = xbmcgui.Window( 10000 )
return_value = xbmcgui.Dialog().yesno("Warning", "Are you sure you want to reset your local Kodi database?")
# Add elements
for i in range(1, 3):
if return_value == 0:
return
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 = (
# 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)")
"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)
#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)
def passwordsXML():
settings('SyncInstallRunDone', "false")
# To add network credentials
path = xbmc.translatePath("special://userdata/").decode('utf-8')
xmlpath = "%spasswords.xml" % path
# 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")
try:
xmlparse = etree.parse(xmlpath)
except: # Document is blank or missing
root = etree.Element('passwords')
else:
root = xmlparse.getroot()
dialog = xbmcgui.Dialog()
# Reload would work instead of restart since the add-on is a service.
#dialog.ok('Emby Reset', 'Database reset has completed, Kodi will now restart to apply the changes.')
#WINDOW.clearProperty("SyncDatabaseShouldStop")
#reloadProfile()
dialog.ok('Emby Reset', 'Database reset has completed, Kodi will now restart to apply the changes.')
xbmc.executebuiltin("RestartApp")
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)

View file

@ -1,466 +1,344 @@
#################################################################################################
# VideoNodes - utils to create video nodes listings in kodi for the emby addon
# -*- coding: utf-8 -*-
#################################################################################################
import xbmc
import xbmcgui
import xbmcaddon
import xbmcvfs
import json
import os
import shutil
#import common elementree because cElementree has issues with kodi
import xml.etree.ElementTree as etree
import Utils as utils
import xbmc
import xbmcaddon
import xbmcvfs
from ReadEmbyDB import ReadEmbyDB
WINDOW = xbmcgui.Window(10000)
import clientinfo
import utils
addonSettings = xbmcaddon.Addon()
language = addonSettings.getLocalizedString
class VideoNodes():
#################################################################################################
def buildVideoNodeForView(self, tagname, type, windowPropId):
#this method will build a video node for a particular Emby view (= tag in kodi)
#we set some window props here to for easy future reference and to be used in skins (for easy access only)
tagname_normalized = utils.normalize_nodes(tagname.encode('utf-8'))
class VideoNodes(object):
libraryPath = xbmc.translatePath("special://profile/library/video/Emby - %s/" %tagname_normalized)
kodiVersion = 14
if xbmc.getInfoLabel("System.BuildVersion").startswith("15") or xbmc.getInfoLabel("System.BuildVersion").startswith("16"):
kodiVersion = 15
#create tag node - index
xbmcvfs.mkdir(libraryPath)
nodefile = os.path.join(libraryPath, "index.xml")
root = etree.Element("node", {"order":"0"})
etree.SubElement(root, "label").text = tagname
etree.SubElement(root, "icon").text = "special://home/addons/plugin.video.emby/icon.png"
path = "library://video/Emby - %s/" %tagname_normalized
WINDOW.setProperty("Emby.nodes.%s.index" %str(windowPropId),path)
try:
etree.ElementTree(root).write(nodefile, xml_declaration=True)
except:
etree.ElementTree(root).write(nodefile)
def __init__(self):
#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)
clientInfo = clientinfo.ClientInfo()
self.addonName = clientInfo.getAddonName()
#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)
self.kodiversion = int(xbmc.getInfoLabel("System.BuildVersion")[:2])
def logMsg(self, msg, lvl=1):
className = self.__class__.__name__
utils.logMsg("%s %s" % (self.addonName, className), msg, lvl)
def commonRoot(self, order, label, tagname, roottype=1):
if roottype == 0:
# Index
root = etree.Element('node', attrib={'order': "%s" % order})
elif roottype == 1:
# Filter
root = etree.Element('node', attrib={'order': "%s" % order, 'type': "filter"})
etree.SubElement(root, 'match').text = "all"
# Add tag rule
rule = etree.SubElement(root, 'rule', attrib={'field': "tag", 'operator': "is"})
etree.SubElement(rule, 'value').text = tagname
else:
label = language(30174)
etree.SubElement(root, "label").text = label
etree.SubElement(root, "match").text = "all"
etree.SubElement(root, "content").text = type
etree.SubElement(root, "icon").text = "special://home/addons/plugin.video.emby/icon.png"
Rule = etree.SubElement(root, "rule", {"field":"tag","operator":"is"})
etree.SubElement(Rule, "value").text = tagname
etree.SubElement(root, "order", {"direction":"descending"}).text = "dateadded"
#set limit to 25 --> currently hardcoded --> TODO: add a setting for this ?
etree.SubElement(root, "limit").text = "25"
#exclude watched items --> currently hardcoded --> TODO: add a setting for this ?
Rule2 = etree.SubElement(root, "rule", {"field":"playcount","operator":"is"})
etree.SubElement(Rule2, "value").text = "0"
WINDOW.setProperty("Emby.nodes.%s.recent.title" %str(windowPropId),label)
path = "library://video/Emby - %s/%s_recent.xml"%(tagname_normalized,tagname_normalized)
WINDOW.setProperty("Emby.nodes.%s.recent.path" %str(windowPropId),"ActivateWindow(Video,%s,return)"%path)
WINDOW.setProperty("Emby.nodes.%s.recent.content" %str(windowPropId),path)
try:
etree.ElementTree(root).write(nodefile, xml_declaration=True)
except:
etree.ElementTree(root).write(nodefile)
# Folder
root = etree.Element('node', attrib={'order': "%s" % order, 'type': "folder"})
#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)
etree.SubElement(root, 'label').text = label
etree.SubElement(root, 'icon').text = "special://home/addons/plugin.video.emby/icon.png"
return root
def viewNode(self, indexnumber, tagname, mediatype, viewtype, delete=False):
kodiversion = self.kodiversion
if mediatype == "homevideos":
# Treat homevideos as movies
mediatype = "movies"
tagname = tagname.encode('utf-8')
cleantagname = utils.normalize_nodes(tagname)
if viewtype == "mixed":
dirname = "%s - %s" % (cleantagname, mediatype)
else:
label = language(30177)
etree.SubElement(root, "label").text = label
etree.SubElement(root, "match").text = "all"
etree.SubElement(root, "content").text = type
etree.SubElement(root, "icon").text = "special://home/addons/plugin.video.emby/icon.png"
Rule = etree.SubElement(root, "rule", {"field":"tag","operator":"is"})
etree.SubElement(Rule, "value").text = tagname
#set limit to 25 --> currently hardcoded --> TODO: add a setting for this ?
etree.SubElement(root, "limit").text = "25"
Rule2 = etree.SubElement(root, "rule", {"field":"inprogress","operator":"true"})
WINDOW.setProperty("Emby.nodes.%s.inprogress.title" %str(windowPropId),label)
path = "library://video/Emby - %s/%s_progress.xml"%(tagname_normalized,tagname_normalized)
WINDOW.setProperty("Emby.nodes.%s.inprogress.path" %str(windowPropId),"ActivateWindow(Video,%s,return)"%path)
WINDOW.setProperty("Emby.nodes.%s.inprogress.content" %str(windowPropId),path)
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)
try:
etree.ElementTree(root).write(nodefile, xml_declaration=True)
except:
etree.ElementTree(root).write(nodefile)
utils.indent(root)
except: pass
etree.ElementTree(root).write(nodeXML)
#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)
nodetypes = {
#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)
'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},
#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)
'tvshows': {
'1': tagname,
'2': 30170,
'3': 30175,
'4': 30171,
'5': 30178,
'7': 30179,
'9': 135,
'10': 30229,
'11': 30230},
}
nodes = mediatypes[mediatype]
for node in nodes:
nodetype = nodetypes[node]
nodeXML = "%s%s_%s.xml" % (nodepath, cleantagname, nodetype)
# Get label
stringid = nodes[node]
if node != '1':
label = utils.language(stringid)
if not label:
label = xbmc.getLocalizedString(stringid)
else:
label = stringid
# Set window properties
if nodetype == "nextupepisodes":
# Custom query
path = "plugin://plugin.video.emby/?id=%s&mode=nextup&limit=25" % tagname
elif kodiversion == 14 and nodetype == "recentepisodes":
# Custom query
path = "plugin://plugin.video.emby/?id=%s&mode=recentepisodes&limit=25" % tagname
elif kodiversion == 14 and nodetype == "inprogressepisodes":
# Custom query
path = "plugin://plugin.video.emby/?id=%s&mode=inprogressepisodes&limit=25"% tagname
else:
path = "library://video/Emby - %s/%s_%s.xml" % (dirname, cleantagname, nodetype)
windowpath = "ActivateWindow(Video, %s, return)" % path
if nodetype == "all":
if viewtype == "mixed":
templabel = dirname
else:
templabel = label
embynode = "Emby.nodes.%s" % indexnumber
utils.window('%s.title' % embynode, value=templabel)
utils.window('%s.path' % embynode, value=windowpath)
utils.window('%s.content' % embynode, value=path)
utils.window('%s.type' % embynode, value=mediatype)
else:
embynode = "Emby.nodes.%s.%s" % (indexnumber, nodetype)
utils.window('%s.title' % embynode, value=label)
utils.window('%s.path' % embynode, value=windowpath)
utils.window('%s.content' % embynode, value=path)
if xbmcvfs.exists(nodeXML):
# Don't recreate xml if already exists
continue
# Create the root
if nodetype == "nextupepisodes" or (kodiversion == 14 and
nodetype in ('recentepisodes', 'inprogressepisodes')):
# Folder type with plugin path
root = self.commonRoot(order=node, label=label, tagname=tagname, roottype=2)
etree.SubElement(root, 'path').text = path
etree.SubElement(root, 'content').text = "episodes"
else:
root = self.commonRoot(order=node, label=label, tagname=tagname)
if nodetype in ('recentepisodes', 'inprogressepisodes'):
etree.SubElement(root, 'content').text = "episodes"
else:
etree.SubElement(root, 'content').text = mediatype
limit = "25"
# Elements per nodetype
if nodetype == "all":
etree.SubElement(root, 'order', {'direction': "ascending"}).text = "sorttitle"
elif nodetype == "recent":
etree.SubElement(root, 'order', {'direction': "descending"}).text = "dateadded"
etree.SubElement(root, 'limit').text = limit
rule = etree.SubElement(root, 'rule', {'field': "playcount", 'operator': "is"})
etree.SubElement(rule, 'value').text = "0"
elif nodetype == "inprogress":
etree.SubElement(root, 'rule', {'field': "inprogress", 'operator': "true"})
etree.SubElement(root, 'limit').text = limit
elif nodetype == "genres":
etree.SubElement(root, 'order', {'direction': "ascending"}).text = "sorttitle"
etree.SubElement(root, 'group').text = "genres"
elif nodetype == "unwatched":
etree.SubElement(root, 'order', {'direction': "ascending"}).text = "sorttitle"
rule = etree.SubElement(root, "rule", {'field': "playcount", 'operator': "is"})
etree.SubElement(rule, 'value').text = "0"
elif nodetype == "sets":
etree.SubElement(root, 'order', {'direction': "ascending"}).text = "sorttitle"
etree.SubElement(root, 'group').text = "sets"
elif nodetype == "random":
etree.SubElement(root, 'order', {'direction': "ascending"}).text = "random"
etree.SubElement(root, 'limit').text = limit
elif nodetype == "recommended":
etree.SubElement(root, 'order', {'direction': "descending"}).text = "rating"
etree.SubElement(root, 'limit').text = limit
rule = etree.SubElement(root, 'rule', {'field': "playcount", 'operator': "is"})
etree.SubElement(rule, 'value').text = "0"
rule2 = etree.SubElement(root, 'rule',
attrib={'field': "rating", 'operator': "greaterthan"})
etree.SubElement(rule2, 'value').text = "7"
elif nodetype == "recentepisodes":
# Kodi Isengard, Jarvis
etree.SubElement(root, 'order', {'direction': "descending"}).text = "dateadded"
etree.SubElement(root, 'limit').text = limit
rule = etree.SubElement(root, 'rule', {'field': "playcount", 'operator': "is"})
etree.SubElement(rule, 'value').text = "0"
elif nodetype == "inprogressepisodes":
# Kodi Isengard, Jarvis
etree.SubElement(root, 'limit').text = "25"
rule = etree.SubElement(root, 'rule',
attrib={'field': "inprogress", 'operator':"true"})
try:
etree.ElementTree(root).write(nodefile, xml_declaration=True)
except:
etree.ElementTree(root).write(nodefile)
utils.indent(root)
except: pass
etree.ElementTree(root).write(nodeXML)
#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)
def singleNode(self, indexnumber, tagname, mediatype, itemtype):
#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)
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
#### TAGS ONLY FOR TV SHOWS COLLECTIONS ####
if type == "tvshows":
# 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)
#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)
labels = {
#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)
'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 kodiVersion == 14:
#create tag node - recent episodes
nodefile = os.path.join(libraryPath, tagname_normalized + "_recent_episodes.xml")
root = etree.Element("node", {"order":"4", "type":"folder"})
label = language(30175)
etree.SubElement(root, "label").text = label
etree.SubElement(root, "content").text = "episodes"
etree.SubElement(root, "icon").text = "special://home/addons/plugin.video.emby/icon.png"
path = "plugin://plugin.video.emby/?id=%s&mode=recentepisodes&limit=25" %tagname
etree.SubElement(root, "path").text = path
WINDOW.setProperty("Emby.nodes.%s.recentepisodes.title" %str(windowPropId),label)
path = "library://video/Emby - %s/%s_recent_episodes.xml"%(tagname_normalized,tagname_normalized)
WINDOW.setProperty("Emby.nodes.%s.recentepisodes.path" %str(windowPropId),"ActivateWindow(Video,%s,return)"%path)
WINDOW.setProperty("Emby.nodes.%s.recentepisodes.content" %str(windowPropId),path)
try:
etree.ElementTree(root).write(nodefile, xml_declaration=True)
except:
etree.ElementTree(root).write(nodefile)
if xbmcvfs.exists(nodeXML):
# Don't recreate xml if already exists
return
#create tag node - inprogress items
nodefile = os.path.join(libraryPath, tagname_normalized + "_progress_episodes.xml")
root = etree.Element("node", {"order":"5", "type":"folder"})
label = language(30178)
etree.SubElement(root, "label").text = label
etree.SubElement(root, "content").text = "episodes"
etree.SubElement(root, "icon").text = "special://home/addons/plugin.video.emby/icon.png"
path = "plugin://plugin.video.emby/?id=%s&mode=inprogressepisodes&limit=25" %tagname
etree.SubElement(root, "path").text = path
WINDOW.setProperty("Emby.nodes.%s.inprogressepisodes.title" %str(windowPropId),label)
path = "library://video/Emby - %s/%s_progress_episodes.xml"%(tagname_normalized,tagname_normalized)
WINDOW.setProperty("Emby.nodes.%s.inprogressepisodes.path" %str(windowPropId),"ActivateWindow(Video,%s,return)"%path)
WINDOW.setProperty("Emby.nodes.%s.inprogressepisodes.content" %str(windowPropId),path)
try:
etree.ElementTree(root).write(nodefile, xml_declaration=True)
except:
etree.ElementTree(root).write(nodefile)
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"
#create tag node - nextup items
#for nextup we always use the dynamic content approach with the plugin's entrypoint because it involves a custom query
nodefile = os.path.join(libraryPath, tagname_normalized + "_nextup_episodes.xml")
root = etree.Element("node", {"order":"6", "type":"folder"})
label = language(30179)
etree.SubElement(root, "label").text = label
etree.SubElement(root, "content").text = "episodes"
path = "plugin://plugin.video.emby/?id=%s&mode=nextup&limit=25" %tagname
etree.SubElement(root, "path").text = path
etree.SubElement(root, "icon").text = "special://home/addons/plugin.video.emby/icon.png"
WINDOW.setProperty("Emby.nodes.%s.nextepisodes.title" %str(windowPropId),label)
path = "library://video/Emby - %s/%s_nextup_episodes.xml"%(tagname_normalized,tagname_normalized)
WINDOW.setProperty("Emby.nodes.%s.nextepisodes.path" %str(windowPropId),"ActivateWindow(Video,%s,return)"%path)
WINDOW.setProperty("Emby.nodes.%s.nextepisodes.content" %str(windowPropId),path)
try:
etree.ElementTree(root).write(nodefile, xml_declaration=True)
except:
etree.ElementTree(root).write(nodefile)
def buildVideoNodesListing(self):
etree.SubElement(root, 'content').text = mediatype
try:
utils.indent(root)
except: pass
etree.ElementTree(root).write(nodeXML)
# 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"))
def clearProperties(self):
#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)
self.logMsg("Clearing nodes properties.", 1)
embyprops = utils.window('Emby.nodes.total')
propnames = [
#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
"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"
]
#build the listing for all views
views_movies = ReadEmbyDB().getCollections("movies")
if views_movies:
for view in views_movies:
title = view.get('title')
content = view.get('content')
if content == "mixed":
title = "%s - Movies" % title
self.buildVideoNodeForView(title, "movies", totalNodesCount)
totalNodesCount +=1
views_shows = ReadEmbyDB().getCollections("tvshows")
if views_shows:
for view in views_shows:
title = view.get('title')
content = view.get('content')
if content == "mixed":
title = "%s - TV Shows" % title
self.buildVideoNodeForView(title, "tvshows", totalNodesCount)
totalNodesCount +=1
#create tag node for emby channels
nodefile = os.path.join(xbmc.translatePath("special://profile/library/video"), "emby_channels.xml")
root = etree.Element("node", {"order":"1", "type":"folder"})
label = language(30173)
etree.SubElement(root, "label").text = label
etree.SubElement(root, "content").text = "movies"
etree.SubElement(root, "path").text = "plugin://plugin.video.emby/?id=0&mode=channels"
etree.SubElement(root, "icon").text = "special://home/addons/plugin.video.emby/icon.png"
WINDOW.setProperty("Emby.nodes.%s.title" %str(totalNodesCount),label)
WINDOW.setProperty("Emby.nodes.%s.type" %str(totalNodesCount),"channels")
path = "library://video/emby_channels.xml"
WINDOW.setProperty("Emby.nodes.%s.path" %str(totalNodesCount),"ActivateWindow(Video,%s,return)"%path)
WINDOW.setProperty("Emby.nodes.%s.content" %str(totalNodesCount),path)
totalNodesCount +=1
try:
etree.ElementTree(root).write(nodefile, xml_declaration=True)
except:
etree.ElementTree(root).write(nodefile)
#create tag node - favorite shows
nodefile = os.path.join(xbmc.translatePath("special://profile/library/video"),"emby_favorite_shows.xml")
root = etree.Element("node", {"order":"1", "type":"filter"})
label = language(30181)
etree.SubElement(root, "label").text = label
etree.SubElement(root, "match").text = "all"
etree.SubElement(root, "content").text = "tvshows"
etree.SubElement(root, "icon").text = "special://home/addons/plugin.video.emby/icon.png"
etree.SubElement(root, "order", {"direction":"ascending"}).text = "sorttitle"
Rule = etree.SubElement(root, "rule", {"field":"tag","operator":"is"})
etree.SubElement(Rule, "value").text = "Favorite tvshows" #do not localize the tagname itself
WINDOW.setProperty("Emby.nodes.%s.title" %str(totalNodesCount),label)
WINDOW.setProperty("Emby.nodes.%s.type" %str(totalNodesCount),"favourites")
path = "library://video/emby_favorite_shows.xml"
WINDOW.setProperty("Emby.nodes.%s.path" %str(totalNodesCount),"ActivateWindow(Video,%s,return)"%path)
WINDOW.setProperty("Emby.nodes.%s.content" %str(totalNodesCount),path)
totalNodesCount +=1
try:
etree.ElementTree(root).write(nodefile, xml_declaration=True)
except:
etree.ElementTree(root).write(nodefile)
#create tag node - favorite movies
nodefile = os.path.join(xbmc.translatePath("special://profile/library/video"),"emby_favorite_movies.xml")
root = etree.Element("node", {"order":"1", "type":"filter"})
label = language(30180)
etree.SubElement(root, "label").text = label
etree.SubElement(root, "match").text = "all"
etree.SubElement(root, "content").text = "movies"
etree.SubElement(root, "icon").text = "special://home/addons/plugin.video.emby/icon.png"
etree.SubElement(root, "order", {"direction":"ascending"}).text = "sorttitle"
Rule = etree.SubElement(root, "rule", {"field":"tag","operator":"is"})
etree.SubElement(Rule, "value").text = "Favorite movies" #do not localize the tagname itself
WINDOW.setProperty("Emby.nodes.%s.title" %str(totalNodesCount),label)
WINDOW.setProperty("Emby.nodes.%s.type" %str(totalNodesCount),"favourites")
path = "library://video/emby_favorite_movies.xml"
WINDOW.setProperty("Emby.nodes.%s.path" %str(totalNodesCount),"ActivateWindow(Video,%s,return)"%path)
WINDOW.setProperty("Emby.nodes.%s.content" %str(totalNodesCount),path)
totalNodesCount +=1
try:
etree.ElementTree(root).write(nodefile, xml_declaration=True)
except:
etree.ElementTree(root).write(nodefile)
WINDOW.setProperty("Emby.nodes.total", str(totalNodesCount))
except Exception as e:
utils.logMsg("Emby addon","Error while creating videonodes listings, restart required ?")
print e
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)