Remove to readd

This commit is contained in:
angelblue05 2015-12-24 14:06:31 -06:00
parent 08f9add813
commit 9855ac4c94
11 changed files with 0 additions and 5377 deletions

View file

@ -1,378 +0,0 @@
# -*- coding: utf-8 -*-
##################################################################################################
import clientinfo
import utils
##################################################################################################
class API():
def __init__(self, item):
self.item = item
self.clientinfo = clientinfo.ClientInfo()
self.addonName = self.clientinfo.getAddonName()
def logMsg(self, msg, lvl=1):
className = self.__class__.__name__
utils.logMsg("%s %s" % (self.addonName, className), msg, lvl)
def getUserData(self):
# Default
favorite = False
playcount = None
played = False
lastPlayedDate = None
resume = 0
rating = 0
try:
userdata = self.item['UserData']
except KeyError: # No userdata found.
pass
else:
favorite = userdata['IsFavorite']
likes = userdata.get('Likes')
# Rating for album and songs
if favorite:
rating = 5
elif likes:
rating = 3
elif likes == False:
rating = 1
else:
rating = 0
lastPlayedDate = userdata.get('LastPlayedDate')
if lastPlayedDate:
lastPlayedDate = lastPlayedDate.split('.')[0].replace('T', " ")
if userdata['Played']:
# Playcount is tied to the watch status
played = True
playcount = userdata['PlayCount']
if playcount == 0:
playcount = 1
if lastPlayedDate is None:
lastPlayedDate = self.getDateCreated()
playbackPosition = userdata.get('PlaybackPositionTicks')
if playbackPosition:
resume = playbackPosition / 10000000.0
return {
'Favorite': favorite,
'PlayCount': playcount,
'Played': played,
'LastPlayedDate': lastPlayedDate,
'Resume': resume,
'Rating': rating
}
def getPeople(self):
# Process People
director = []
writer = []
cast = []
try:
people = self.item['People']
except KeyError:
pass
else:
for person in people:
type = person['Type']
name = person['Name']
if "Director" in type:
director.append(name)
elif "Actor" in type:
cast.append(name)
elif type in ("Writing", "Writer"):
writer.append(name)
return {
'Director': director,
'Writer': writer,
'Cast': cast
}
def getMediaStreams(self):
item = self.item
videotracks = []
audiotracks = []
subtitlelanguages = []
try:
media_streams = item['MediaSources'][0]['MediaStreams']
except KeyError:
media_streams = item['MediaStreams']
for media_stream in media_streams:
# Sort through Video, Audio, Subtitle
stream_type = media_stream['Type']
codec = media_stream.get('Codec', "").lower()
profile = media_stream.get('Profile', "").lower()
if stream_type == "Video":
# Height, Width, Codec, AspectRatio, AspectFloat, 3D
track = {
'videocodec': codec,
'height': media_stream.get('Height'),
'width': media_stream.get('Width'),
'video3DFormat': item.get('Video3DFormat'),
'aspectratio': 1.85
}
try:
container = item['MediaSources'][0]['Container'].lower()
except:
container = ""
# Sort codec vs container/profile
if "msmpeg4" in codec:
track['videocodec'] = "divx"
elif "mpeg4" in codec:
if "simple profile" in profile or not profile:
track['videocodec'] = "xvid"
elif "h264" in codec:
if container in ("mp4", "mov", "m4v"):
track['videocodec'] = "avc1"
# Aspect ratio
if item.get('AspectRatio'):
# Metadata AR
aspectratio = item['AspectRatio']
else: # File AR
aspectratio = media_stream.get('AspectRatio', "0")
try:
aspectwidth, aspectheight = aspectratio.split(':')
track['aspectratio'] = round(float(aspectwidth) / float(aspectheight), 6)
except ValueError:
width = track['width']
height = track['height']
if width and height:
track['aspectratio'] = round(float(width / height), 6)
videotracks.append(track)
elif stream_type == "Audio":
# Codec, Channels, language
track = {
'audiocodec': codec,
'channels': media_stream.get('Channels'),
'audiolanguage': media_stream.get('Language')
}
if "dca" in codec and "dts-hd ma" in profile:
track['audiocodec'] = "dtshd_ma"
audiotracks.append(track)
elif stream_type == "Subtitle":
# Language
subtitlelanguages.append(media_stream.get('Language', "Unknown"))
return {
'video': videotracks,
'audio': audiotracks,
'subtitle': subtitlelanguages
}
def getRuntime(self):
item = self.item
try:
runtime = item['RunTimeTicks'] / 10000000.0
except KeyError:
runtime = item.get('CumulativeRunTimeTicks', 0) / 10000000.0
return runtime
def adjustResume(self, resume_seconds):
resume = 0
if resume_seconds:
resume = round(float(resume_seconds), 6)
jumpback = int(utils.settings('resumeJumpBack'))
if resume > jumpback:
# To avoid negative bookmark
resume = resume - jumpback
return resume
def getStudios(self):
# Process Studios
item = self.item
studios = []
try:
studio = item['SeriesStudio']
studios.append(self.verifyStudio(studio))
except KeyError:
studioList = item['Studios']
for studio in studioList:
name = studio['Name']
studios.append(self.verifyStudio(name))
return studios
def verifyStudio(self, studioName):
# Convert studio for Kodi to properly detect them
studios = {
'abc (us)': "ABC",
'fox (us)': "FOX",
'mtv (us)': "MTV",
'showcase (ca)': "Showcase",
'wgn america': "WGN"
}
return studios.get(studioName.lower(), studioName)
def getChecksum(self):
# Use the etags checksum and userdata
item = self.item
userdata = item['UserData']
checksum = "%s%s%s%s%s%s" % (
item['Etag'],
userdata['Played'],
userdata['IsFavorite'],
userdata['PlaybackPositionTicks'],
userdata.get('UnplayedItemCount', ""),
userdata.get('LastPlayedDate', "")
)
return checksum
def getGenres(self):
item = self.item
all_genres = ""
genres = item.get('Genres', item.get('SeriesGenres'))
if genres:
all_genres = " / ".join(genres)
return all_genres
def getDateCreated(self):
try:
dateadded = self.item['DateCreated']
dateadded = dateadded.split('.')[0].replace('T', " ")
except KeyError:
dateadded = None
return dateadded
def getPremiereDate(self):
try:
premiere = self.item['PremiereDate']
premiere = premiere.split('.')[0].replace('T', " ")
except KeyError:
premiere = None
return premiere
def getOverview(self):
try:
overview = self.item['Overview']
overview = overview.replace("\"", "\'")
overview = overview.replace("\n", " ")
overview = overview.replace("\r", " ")
except KeyError:
overview = ""
return overview
def getTagline(self):
try:
tagline = self.item['Taglines'][0]
except IndexError:
tagline = None
return tagline
def getProvider(self, providername):
try:
provider = self.item['ProviderIds'][providername]
except KeyError:
provider = None
return provider
def getMpaa(self):
# Convert more complex cases
mpaa = self.item.get('OfficialRating', "")
if mpaa in ("NR", "UR"):
# Kodi seems to not like NR, but will accept Not Rated
mpaa = "Not Rated"
return mpaa
def getCountry(self):
try:
country = self.item['ProductionLocations'][0]
except IndexError:
country = None
return country
def getFilePath(self):
item = self.item
try:
filepath = item['Path']
except KeyError:
filepath = ""
else:
if "\\\\" in filepath:
# append smb protocol
filepath = filepath.replace("\\\\", "smb://")
filepath = filepath.replace("\\", "/")
if item.get('VideoType'):
videotype = item['VideoType']
# Specific format modification
if 'Dvd'in videotype:
filepath = "%s/VIDEO_TS/VIDEO_TS.IFO" % filepath
elif 'Bluray' in videotype:
filepath = "%s/BDMV/index.bdmv" % filepath
if "\\" in filepath:
# Local path scenario, with special videotype
filepath = filepath.replace("/", "\\")
return filepath

View file

@ -1,347 +0,0 @@
import xbmc
import xbmcgui
import xbmcaddon
import requests
import json
import logging
import Utils as utils
from ClientInformation import ClientInformation
from requests.packages.urllib3.exceptions import InsecureRequestWarning
# Disable requests logging
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
#logging.getLogger("requests").setLevel(logging.WARNING)
class DownloadUtils():
# Borg - multiple instances, shared state
_shared_state = {}
clientInfo = ClientInformation()
addonName = clientInfo.getAddonName()
addon = xbmcaddon.Addon()
WINDOW = xbmcgui.Window(10000)
# Requests session
s = None
timeout = 60
def __init__(self):
self.__dict__ = self._shared_state
def logMsg(self, msg, lvl=1):
self.className = self.__class__.__name__
utils.logMsg("%s %s" % (self.addonName, self.className), msg, int(lvl))
def setUsername(self, username):
# Reserved for UserClient only
self.username = username
self.logMsg("Set username: %s" % username, 2)
def setUserId(self, userId):
# Reserved for UserClient only
self.userId = userId
self.logMsg("Set userId: %s" % userId, 2)
def setServer(self, server):
# Reserved for UserClient only
self.server = server
self.logMsg("Set server: %s" % server, 2)
def setToken(self, token):
# Reserved for UserClient only
self.token = token
self.logMsg("Set token: %s" % token, 2)
def setSSL(self, ssl, sslclient):
# Reserved for UserClient only
self.sslverify = ssl
self.sslclient = sslclient
self.logMsg("Verify SSL host certificate: %s" % ssl, 2)
self.logMsg("SSL client side certificate: %s" % sslclient, 2)
def postCapabilities(self, deviceId):
# Post settings to session
url = "{server}/mediabrowser/Sessions/Capabilities/Full"
data = {
'PlayableMediaTypes': "Audio,Video",
'SupportsMediaControl': True,
'SupportedCommands': (
"MoveUp,MoveDown,MoveLeft,MoveRight,Select,"
"Back,ToggleContextMenu,ToggleFullscreen,ToggleOsdMenu,"
"GoHome,PageUp,NextLetter,GoToSearch,"
"GoToSettings,PageDown,PreviousLetter,TakeScreenshot,"
"VolumeUp,VolumeDown,ToggleMute,SendString,DisplayMessage,"
"SetAudioStreamIndex,SetSubtitleStreamIndex,"
"Mute,Unmute,SetVolume,"
"Play,Playstate,PlayNext"
)
}
self.logMsg("Capabilities URL: %s" % url, 2)
self.logMsg("PostData: %s" % data, 2)
try:
self.downloadUrl(url, postBody=data, type="POST")
self.logMsg("Posted capabilities to %s" % self.server, 1)
except:
self.logMsg("Posted capabilities failed.")
# Attempt at getting sessionId
url = "{server}/mediabrowser/Sessions?DeviceId=%s&format=json" % deviceId
try:
result = self.downloadUrl(url)
self.logMsg("Session: %s" % result, 2)
sessionId = result[0][u'Id']
self.logMsg("SessionId: %s" % sessionId)
self.WINDOW.setProperty("sessionId%s" % self.username, sessionId)
except:
self.logMsg("Failed to retrieve sessionId.", 1)
else:
# Post any permanent additional users
additionalUsers = utils.settings('additionalUsers').split(',')
self.logMsg("List of permanent users that should be added to the session: %s" % str(additionalUsers), 1)
# Get the user list from server to get the userId
url = "{server}/mediabrowser/Users?format=json"
result = self.downloadUrl(url)
if result:
for user in result:
username = user['Name'].lower()
userId = user['Id']
for additional in additionalUsers:
addUser = additional.decode('utf-8').lower()
if username in addUser:
url = "{server}/mediabrowser/Sessions/%s/Users/%s" % (sessionId, userId)
postdata = {}
self.downloadUrl(url, postBody=postdata, type="POST")
#xbmcgui.Dialog().notification("Success!", "%s added to viewing session" % username, time=1000)
def startSession(self):
self.deviceId = self.clientInfo.getMachineId()
# User is identified from this point
# Attach authenticated header to the session
verify = None
cert = None
header = self.getHeader()
# If user enabled host certificate verification
try:
verify = self.sslverify
cert = self.sslclient
except:
self.logMsg("Could not load SSL settings.", 1)
# Start session
self.s = requests.Session()
self.s.headers = header
self.s.verify = verify
self.s.cert = cert
# Retry connections to the server
self.s.mount("http://", requests.adapters.HTTPAdapter(max_retries=1))
self.s.mount("https://", requests.adapters.HTTPAdapter(max_retries=1))
self.logMsg("Requests session started on: %s" % self.server)
def stopSession(self):
try:
self.s.close()
except:
self.logMsg("Requests session could not be terminated.", 1)
def getHeader(self, authenticate=True):
clientInfo = self.clientInfo
deviceName = clientInfo.getDeviceName()
deviceId = clientInfo.getMachineId()
version = clientInfo.getVersion()
if not authenticate:
# If user is not authenticated
auth = 'MediaBrowser Client="Kodi", Device="%s", DeviceId="%s", Version="%s"' % (deviceName, deviceId, version)
header = {'Content-type': 'application/json', 'Accept-encoding': 'gzip', 'Accept-Charset': 'UTF-8,*', 'Authorization': auth}
self.logMsg("Header: %s" % header, 2)
return header
else:
userId = self.userId
token = self.token
# Attached to the requests session
auth = 'MediaBrowser UserId="%s", Client="Kodi", Device="%s", DeviceId="%s", Version="%s"' % (userId, deviceName, deviceId, version)
header = {'Content-type': 'application/json', 'Accept-encoding': 'gzip', 'Accept-Charset': 'UTF-8,*', 'Authorization': auth, 'X-MediaBrowser-Token': token}
self.logMsg("Header: %s" % header, 2)
return header
def downloadUrl(self, url, postBody=None, type="GET", authenticate=True):
self.logMsg("=== ENTER downloadUrl ===", 2)
WINDOW = self.WINDOW
timeout = self.timeout
default_link = ""
try:
# If user is authenticated
if (authenticate):
# Get requests session
try:
s = self.s
# Replace for the real values and append api_key
url = url.replace("{server}", self.server, 1)
url = url.replace("{UserId}", self.userId, 1)
self.logMsg("URL: %s" % url, 2)
# Prepare request
if type == "GET":
r = s.get(url, json=postBody, timeout=timeout)
elif type == "POST":
r = s.post(url, json=postBody, timeout=timeout)
elif type == "DELETE":
r = s.delete(url, json=postBody, timeout=timeout)
except AttributeError:
# Get user information
self.username = WINDOW.getProperty('currUser')
self.userId = WINDOW.getProperty('userId%s' % self.username)
self.server = WINDOW.getProperty('server%s' % self.username)
self.token = WINDOW.getProperty('accessToken%s' % self.username)
header = self.getHeader()
verifyssl = False
cert = None
# IF user enables ssl verification
try:
if utils.settings('sslverify') == "true":
verifyssl = True
if utils.settings('sslcert') != "None":
cert = utils.settings('sslcert')
except:
self.logMsg("Could not load SSL settings.", 1)
pass
# Replace for the real values and append api_key
url = url.replace("{server}", self.server, 1)
url = url.replace("{UserId}", self.userId, 1)
self.logMsg("URL: %s" % url, 2)
# Prepare request
if type == "GET":
r = requests.get(url, json=postBody, headers=header, timeout=timeout, cert=cert, verify=verifyssl)
elif type == "POST":
r = requests.post(url, json=postBody, headers=header, timeout=timeout, cert=cert, verify=verifyssl)
elif type == "DELETE":
r = requests.delete(url, json=postBody, headers=header, timeout=timeout, cert=cert, verify=verifyssl)
# If user is not authenticated
elif not authenticate:
self.logMsg("URL: %s" % url, 2)
header = self.getHeader(authenticate=False)
verifyssl = False
# If user enables ssl verification
try:
verifyssl = self.sslverify
except AttributeError:
pass
# Prepare request
if type == "GET":
r = requests.get(url, json=postBody, headers=header, timeout=timeout, verify=verifyssl)
elif type == "POST":
r = requests.post(url, json=postBody, headers=header, timeout=timeout, verify=verifyssl)
# Process the response
if r.status_code == 204:
# No body in the response
self.logMsg("====== 204 Success ======", 2)
return default_link
elif r.status_code == requests.codes.ok:
try:
# UTF-8 - JSON object
r = r.json()
self.logMsg("====== 200 Success ======", 2)
self.logMsg("Response: %s" % r, 2)
return r
except:
if r.headers.get('content-type') == "text/html":
pass
else:
self.logMsg("Unable to convert the response for: %s" % url, 1)
else:
r.raise_for_status()
return default_link
# TO REVIEW EXCEPTIONS
except requests.exceptions.ConnectionError as e:
# Make the addon aware of status
if WINDOW.getProperty("Server_online") != "false":
self.logMsg("Server unreachable at: %s" % url, 0)
self.logMsg(e, 2)
WINDOW.setProperty("Server_online", "false")
pass
except requests.exceptions.ConnectTimeout as e:
self.logMsg("Server timeout at: %s" % url, 0)
self.logMsg(e, 1)
except requests.exceptions.HTTPError as e:
if r.status_code == 401:
# Unauthorized
status = WINDOW.getProperty("Server_status")
if 'x-application-error-code' in r.headers:
if r.headers['X-Application-Error-Code'] == "ParentalControl":
# Parental control - access restricted
WINDOW.setProperty("Server_status", "restricted")
xbmcgui.Dialog().notification("Emby server", "Access restricted.", xbmcgui.NOTIFICATION_ERROR, time=5000)
return False
elif r.headers['X-Application-Error-Code'] == "UnauthorizedAccessException":
# User tried to do something his emby account doesn't allow - admin restricted in some way
pass
elif (status == "401") or (status == "Auth"):
pass
else:
# Tell UserClient token has been revoked.
WINDOW.setProperty("Server_status", "401")
self.logMsg("HTTP Error: %s" % e, 0)
xbmcgui.Dialog().notification("Error connecting", "Unauthorized.", xbmcgui.NOTIFICATION_ERROR)
return 401
elif (r.status_code == 301) or (r.status_code == 302):
# Redirects
pass
elif r.status_code == 400:
# Bad requests
pass
except requests.exceptions.SSLError as e:
self.logMsg("Invalid SSL certificate for: %s" % url, 0)
self.logMsg(e, 1)
except requests.exceptions.RequestException as e:
self.logMsg("Unknown error connecting to: %s" % url, 0)
self.logMsg(e, 1)
return default_link

View file

@ -1,694 +0,0 @@
import xbmcaddon
import xbmcplugin
import xbmc
import xbmcgui
import xbmcvfs
import os, sys
import threading
import json
import urllib
import time
WINDOW = xbmcgui.Window(10000)
import Utils as utils
from ClientInformation import ClientInformation
from PlaybackUtils import PlaybackUtils
from PlayUtils import PlayUtils
from DownloadUtils import DownloadUtils
from ReadEmbyDB import ReadEmbyDB
from API import API
from UserPreferences import UserPreferences
##### Play items via plugin://plugin.video.emby/ #####
def doPlayback(id):
url = "{server}/mediabrowser/Users/{UserId}/Items/%s?format=json&ImageTypeLimit=1" % id
result = DownloadUtils().downloadUrl(url)
item = PlaybackUtils().PLAY(result, setup="default")
#### DO RESET AUTH #####
def resetAuth():
# User tried login and failed too many times
resp = xbmcgui.Dialog().yesno("Warning", "Emby might lock your account if you fail to log in too many times. Proceed anyway?")
if resp == 1:
xbmc.log("Reset login attempts.")
WINDOW.setProperty("Server_status", "Auth")
else:
xbmc.executebuiltin('Addon.OpenSettings(plugin.video.emby)')
### ADD ADDITIONAL USERS ###
def addUser():
doUtils = DownloadUtils()
clientInfo = ClientInformation()
currUser = WINDOW.getProperty("currUser")
deviceId = clientInfo.getMachineId()
deviceName = clientInfo.getDeviceName()
# Get session
url = "{server}/mediabrowser/Sessions?DeviceId=%s" % deviceId
result = doUtils.downloadUrl(url)
try:
sessionId = result[0][u'Id']
additionalUsers = result[0][u'AdditionalUsers']
# Add user to session
userlist = {}
users = []
url = "{server}/mediabrowser/Users?IsDisabled=false&IsHidden=false"
result = doUtils.downloadUrl(url)
# pull the list of users
for user in result:
name = user[u'Name']
userId = user[u'Id']
if currUser not in name:
userlist[name] = userId
users.append(name)
# Display dialog if there's additional users
if additionalUsers:
option = xbmcgui.Dialog().select("Add/Remove user from the session", ["Add user", "Remove user"])
# Users currently in the session
additionalUserlist = {}
additionalUsername = []
# Users currently in the session
for user in additionalUsers:
name = user[u'UserName']
userId = user[u'UserId']
additionalUserlist[name] = userId
additionalUsername.append(name)
if option == 1:
# User selected Remove user
resp = xbmcgui.Dialog().select("Remove user from the session", additionalUsername)
if resp > -1:
selected = additionalUsername[resp]
selected_userId = additionalUserlist[selected]
url = "{server}/mediabrowser/Sessions/%s/Users/%s" % (sessionId, selected_userId)
postdata = {}
doUtils.downloadUrl(url, postBody=postdata, type="DELETE")
xbmcgui.Dialog().notification("Success!", "%s removed from viewing session" % selected, time=1000)
# clear picture
position = WINDOW.getProperty('EmbyAdditionalUserPosition.' + selected_userId)
WINDOW.clearProperty('EmbyAdditionalUserImage.' + str(position))
return
else:
return
elif option == 0:
# User selected Add user
for adduser in additionalUsername:
try: # Remove from selected already added users. It is possible they are hidden.
users.remove(adduser)
except: pass
elif option < 0:
# User cancelled
return
# Subtract any additional users
xbmc.log("Displaying list of users: %s" % users)
resp = xbmcgui.Dialog().select("Add user to the session", users)
# post additional user
if resp > -1:
selected = users[resp]
selected_userId = userlist[selected]
url = "{server}/mediabrowser/Sessions/%s/Users/%s" % (sessionId, selected_userId)
postdata = {}
doUtils.downloadUrl(url, postBody=postdata, type="POST")
xbmcgui.Dialog().notification("Success!", "%s added to viewing session" % selected, time=1000)
except:
xbmc.log("Failed to add user to session.")
xbmcgui.Dialog().notification("Error", "Unable to add/remove user from the session.", xbmcgui.NOTIFICATION_ERROR)
try:
# Add additional user images
#always clear the individual items first
totalNodes = 10
for i in range(totalNodes):
if not WINDOW.getProperty('EmbyAdditionalUserImage.' + str(i)):
break
WINDOW.clearProperty('EmbyAdditionalUserImage.' + str(i))
url = "{server}/mediabrowser/Sessions?DeviceId=%s" % deviceId
result = doUtils.downloadUrl(url)
additionalUsers = result[0][u'AdditionalUsers']
count = 0
for additionaluser in additionalUsers:
url = "{server}/mediabrowser/Users/%s?format=json" % (additionaluser[u'UserId'])
result = doUtils.downloadUrl(url)
WINDOW.setProperty("EmbyAdditionalUserImage." + str(count),API().getUserArtwork(result,"Primary"))
WINDOW.setProperty("EmbyAdditionalUserPosition." + str(additionaluser[u'UserId']),str(count))
count +=1
except:
pass
# THEME MUSIC/VIDEOS
def getThemeMedia():
doUtils = DownloadUtils()
playUtils = PlayUtils()
currUser = WINDOW.getProperty('currUser')
server = WINDOW.getProperty('server%s' % currUser)
playback = None
library = xbmc.translatePath("special://profile/addon_data/plugin.video.emby/library/").decode('utf-8')
# Choose playback method
resp = xbmcgui.Dialog().select("Choose playback method for your themes", ["Direct Play", "Direct Stream"])
if resp == 0:
# Direct Play
playback = "DirectPlay"
elif resp == 1:
# Direct Stream
playback = "DirectStream"
else:return
# Set custom path for user
tvtunes_path = xbmc.translatePath("special://profile/addon_data/script.tvtunes/").decode('utf-8')
if xbmcvfs.exists(tvtunes_path):
tvtunes = xbmcaddon.Addon(id="script.tvtunes")
tvtunes.setSetting('custom_path_enable', "true")
tvtunes.setSetting('custom_path', library)
xbmc.log("TV Tunes custom path is enabled and set.")
else:
# if it does not exist this will not work so warn user, often they need to edit the settings first for it to be created.
dialog = xbmcgui.Dialog()
dialog.ok('Warning', 'The settings file does not exist in tvtunes. Go to the tvtunes addon and change a setting, then come back and re-run')
return
# Create library directory
if not xbmcvfs.exists(library):
xbmcvfs.mkdir(library)
# Get every user view Id
userViews = []
url = "{server}/mediabrowser/Users/{UserId}/Items?format=json"
result = doUtils.downloadUrl(url)
for view in result[u'Items']:
userviewId = view[u'Id']
userViews.append(userviewId)
# Get Ids with Theme Videos
itemIds = {}
for view in userViews:
url = "{server}/mediabrowser/Users/{UserId}/Items?HasThemeVideo=True&ParentId=%s&format=json" % view
result = doUtils.downloadUrl(url)
if result[u'TotalRecordCount'] != 0:
for item in result[u'Items']:
itemId = item[u'Id']
folderName = item[u'Name']
folderName = utils.normalize_string(folderName.encode('utf-8'))
itemIds[itemId] = folderName
# Get paths for theme videos
for itemId in itemIds:
nfo_path = xbmc.translatePath("special://profile/addon_data/plugin.video.emby/library/%s/" % itemIds[itemId])
# Create folders for each content
if not xbmcvfs.exists(nfo_path):
xbmcvfs.mkdir(nfo_path)
# Where to put the nfos
nfo_path = "%s%s" % (nfo_path, "tvtunes.nfo")
url = "{server}/mediabrowser/Items/%s/ThemeVideos?format=json" % itemId
result = doUtils.downloadUrl(url)
# Create nfo and write themes to it
nfo_file = open(nfo_path, 'w')
pathstowrite = ""
# May be more than one theme
for theme in result[u'Items']:
if playback == "DirectPlay":
playurl = playUtils.directPlay(theme)
else:
playurl = playUtils.directStream(result, server, theme[u'Id'], "ThemeVideo")
pathstowrite += ('<file>%s</file>' % playurl.encode('utf-8'))
# Check if the item has theme songs and add them
url = "{server}/mediabrowser/Items/%s/ThemeSongs?format=json" % itemId
result = doUtils.downloadUrl(url)
# May be more than one theme
for theme in result[u'Items']:
if playback == "DirectPlay":
playurl = playUtils.directPlay(theme)
else:
playurl = playUtils.directStream(result, server, theme[u'Id'], "Audio")
pathstowrite += ('<file>%s</file>' % playurl.encode('utf-8'))
nfo_file.write(
'<tvtunes>%s</tvtunes>' % pathstowrite
)
# Close nfo file
nfo_file.close()
# Get Ids with Theme songs
musicitemIds = {}
for view in userViews:
url = "{server}/mediabrowser/Users/{UserId}/Items?HasThemeSong=True&ParentId=%s&format=json" % view
result = doUtils.downloadUrl(url)
if result[u'TotalRecordCount'] != 0:
for item in result[u'Items']:
itemId = item[u'Id']
folderName = item[u'Name']
folderName = utils.normalize_string(folderName.encode('utf-8'))
musicitemIds[itemId] = folderName
# Get paths
for itemId in musicitemIds:
# if the item was already processed with video themes back out
if itemId in itemIds:
continue
nfo_path = xbmc.translatePath("special://profile/addon_data/plugin.video.emby/library/%s/" % musicitemIds[itemId])
# Create folders for each content
if not xbmcvfs.exists(nfo_path):
xbmcvfs.mkdir(nfo_path)
# Where to put the nfos
nfo_path = "%s%s" % (nfo_path, "tvtunes.nfo")
url = "{server}/mediabrowser/Items/%s/ThemeSongs?format=json" % itemId
result = doUtils.downloadUrl(url)
# Create nfo and write themes to it
nfo_file = open(nfo_path, 'w')
pathstowrite = ""
# May be more than one theme
for theme in result[u'Items']:
if playback == "DirectPlay":
playurl = playUtils.directPlay(theme)
else:
playurl = playUtils.directStream(result, server, theme[u'Id'], "Audio")
pathstowrite += ('<file>%s</file>' % playurl.encode('utf-8'))
nfo_file.write(
'<tvtunes>%s</tvtunes>' % pathstowrite
)
# Close nfo file
nfo_file.close()
def userPreferences():
doUtils = DownloadUtils()
addonSettings = xbmcaddon.Addon(id='plugin.video.emby')
userPreferencesPage = UserPreferences("script-emby-kodi-UserPreferences.xml", addonSettings.getAddonInfo('path'), "default", "1080i")
url = "{server}/mediabrowser/Users/{UserId}"
result = doUtils.downloadUrl(url)
configuration = result[u'Configuration']
userPreferencesPage.setConfiguration(configuration)
userPreferencesPage.setName(result[u'Name'])
userPreferencesPage.setImage(API().getUserArtwork(result,"Primary"))
userPreferencesPage.doModal()
if userPreferencesPage.isSave():
url = "{server}/mediabrowser/Users/{UserId}/Configuration"
postdata = userPreferencesPage.getConfiguration()
doUtils.downloadUrl(url, postBody=postdata, type="POST")
##### BROWSE EMBY CHANNELS #####
def BrowseChannels(id, folderid=None):
_addon_id = int(sys.argv[1])
_addon_url = sys.argv[0]
xbmcplugin.setContent(int(sys.argv[1]), 'files')
if folderid:
url = "{server}/mediabrowser/Channels/" + id + "/Items?userid={UserId}&folderid=" + folderid + "&format=json"
else:
if id == "0": # id 0 is the root channels folder
url = "{server}/mediabrowser/Channels?{UserId}&format=json"
else:
url = "{server}/mediabrowser/Channels/" + id + "/Items?userid={UserId}&format=json"
results = DownloadUtils().downloadUrl(url)
if results:
result = results.get("Items")
if(result == None):
result = []
item_count = len(result)
current_item = 1;
for item in result:
id=str(item.get("Id")).encode('utf-8')
type=item.get("Type").encode('utf-8')
if(item.get("Name") != None):
tempTitle = item.get("Name")
tempTitle=tempTitle.encode('utf-8')
else:
tempTitle = "Missing Title"
if type=="ChannelFolderItem":
isFolder = True
else:
isFolder = False
item_type = str(type).encode('utf-8')
if(item.get("ChannelId") != None):
channelId = str(item.get("ChannelId")).encode('utf-8')
channelName = ''
if(item.get("ChannelName") != None):
channelName = item.get("ChannelName").encode('utf-8')
if(item.get("PremiereDate") != None):
premieredatelist = (item.get("PremiereDate")).split("T")
premieredate = premieredatelist[0]
else:
premieredate = ""
#mediaStreams=API().getMediaStreams(item, True)
#people = API().getPeople(item)
# Process Genres
genre = API().getGenre(item)
# Process UserData
userData = item.get("UserData")
PlaybackPositionTicks = '100'
overlay = "0"
favorite = "False"
seekTime = 0
if(userData != None):
if userData.get("Played") != True:
overlay = "7"
watched = "true"
else:
overlay = "6"
watched = "false"
if userData.get("IsFavorite") == True:
overlay = "5"
favorite = "True"
else:
favorite = "False"
if userData.get("PlaybackPositionTicks") != None:
PlaybackPositionTicks = str(userData.get("PlaybackPositionTicks"))
reasonableTicks = int(userData.get("PlaybackPositionTicks")) / 1000
seekTime = reasonableTicks / 10000
playCount = 0
if(userData != None and userData.get("Played") == True):
playCount = 1
# Populate the details list
details={'title' : tempTitle,
'channelname' : channelName,
'plot' : item.get("Overview"),
'Overlay' : overlay,
'playcount' : str(playCount)}
if item.get("Type") == "ChannelVideoItem":
xbmcplugin.setContent(_addon_id, 'movies')
elif item.get("Type") == "ChannelAudioItem":
xbmcplugin.setContent(_addon_id, 'songs')
# Populate the extraData list
extraData={'thumb' : API().getArtwork(item, "Primary") ,
'fanart_image' : API().getArtwork(item, "Backdrop") ,
'poster' : API().getArtwork(item, "poster") ,
'tvshow.poster': API().getArtwork(item, "tvshow.poster") ,
'banner' : API().getArtwork(item, "Banner") ,
'clearlogo' : API().getArtwork(item, "Logo") ,
'discart' : API().getArtwork(item, "Disc") ,
'clearart' : API().getArtwork(item, "Art") ,
'landscape' : API().getArtwork(item, "Thumb") ,
'id' : id ,
'rating' : item.get("CommunityRating"),
'year' : item.get("ProductionYear"),
'premieredate' : premieredate,
'genre' : genre,
'playcount' : str(playCount),
'itemtype' : item_type}
if extraData['thumb'] == '':
extraData['thumb'] = extraData['fanart_image']
liz = xbmcgui.ListItem(tempTitle)
artTypes=['poster', 'tvshow.poster', 'fanart_image', 'clearlogo', 'discart', 'banner', 'clearart', 'landscape', 'small_poster', 'tiny_poster', 'medium_poster','small_fanartimage', 'medium_fanartimage', 'medium_landscape', 'fanart_noindicators']
for artType in artTypes:
imagePath=str(extraData.get(artType,''))
liz=PlaybackUtils().setArt(liz,artType, imagePath)
liz.setThumbnailImage(API().getArtwork(item, "Primary"))
liz.setIconImage('DefaultTVShows.png')
#liz.setInfo( type="Video", infoLabels={ "Rating": item.get("CommunityRating") })
#liz.setInfo( type="Video", infoLabels={ "Plot": item.get("Overview") })
if type=="Channel":
file = _addon_url + "?id=%s&mode=channels"%id
xbmcplugin.addDirectoryItem(handle=_addon_id, url=file, listitem=liz, isFolder=True)
elif isFolder == True:
file = _addon_url + "?id=%s&mode=channelsfolder&folderid=%s" %(channelId, id)
xbmcplugin.addDirectoryItem(handle=_addon_id, url=file, listitem=liz, isFolder=True)
else:
file = _addon_url + "?id=%s&mode=play"%id
liz.setProperty('IsPlayable', 'true')
xbmcplugin.addDirectoryItem(handle=_addon_id, url=file, listitem=liz)
xbmcplugin.endOfDirectory(handle=int(sys.argv[1]))
##### GET NEXTUP EPISODES FOR TAGNAME #####
def getNextUpEpisodes(tagname,limit):
count=0
#if the addon is called with nextup parameter, we return the nextepisodes list of the given tagname
xbmcplugin.setContent(int(sys.argv[1]), 'episodes')
# First we get a list of all the in-progress TV shows - filtered by tag
json_query_string = xbmc.executeJSONRPC('{"jsonrpc": "2.0", "method": "VideoLibrary.GetTVShows", "params": { "sort": { "order": "descending", "method": "lastplayed" }, "filter": {"and": [{"operator":"true", "field":"inprogress", "value":""}, {"operator": "is", "field": "tag", "value": "%s"}]}, "properties": [ "title", "studio", "mpaa", "file", "art" ] }, "id": "libTvShows"}' %tagname)
json_result = json.loads(json_query_string)
# If we found any, find the oldest unwatched show for each one.
if json_result.has_key('result') and json_result['result'].has_key('tvshows'):
for item in json_result['result']['tvshows']:
# If Ignore Specials is true only choose episodes from seasons greater than 0.
if utils.settings("ignoreSpecialsNextEpisodes")=="true":
json_query2 = xbmc.executeJSONRPC('{"jsonrpc": "2.0", "method": "VideoLibrary.GetEpisodes", "params": { "tvshowid": %d, "sort": {"method":"episode"}, "filter": {"and": [ {"field": "playcount", "operator": "lessthan", "value":"1"}, {"field": "season", "operator": "greaterthan", "value": "0"} ]}, "properties": [ "title", "playcount", "season", "episode", "showtitle", "plot", "file", "rating", "resume", "tvshowid", "art", "streamdetails", "firstaired", "runtime", "writer", "dateadded", "lastplayed" ], "limits":{"end":1}}, "id": "1"}' %item['tvshowid'])
else:
json_query2 = xbmc.executeJSONRPC('{"jsonrpc": "2.0", "method": "VideoLibrary.GetEpisodes", "params": { "tvshowid": %d, "sort": {"method":"episode"}, "filter": {"field": "playcount", "operator": "lessthan", "value":"1"}, "properties": [ "title", "playcount", "season", "episode", "showtitle", "plot", "file", "rating", "resume", "tvshowid", "art", "streamdetails", "firstaired", "runtime", "writer", "dateadded", "lastplayed" ], "limits":{"end":1}}, "id": "1"}' %item['tvshowid'])
if json_query2:
json_query2 = json.loads(json_query2)
if json_query2.has_key('result') and json_query2['result'].has_key('episodes'):
for item in json_query2['result']['episodes']:
liz = createListItem(item)
xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=item['file'], listitem=liz)
count +=1
if count == limit:
break
xbmcplugin.endOfDirectory(handle=int(sys.argv[1]))
def getInProgressEpisodes(tagname,limit):
count = 0
#if the addon is called with inprogressepisodes parameter, we return the inprogressepisodes list of the given tagname
xbmcplugin.setContent(int(sys.argv[1]), 'episodes')
# First we get a list of all the in-progress TV shows - filtered by tag
json_query_string = xbmc.executeJSONRPC('{"jsonrpc": "2.0", "method": "VideoLibrary.GetTVShows", "params": { "sort": { "order": "descending", "method": "lastplayed" }, "filter": {"and": [{"operator":"true", "field":"inprogress", "value":""}, {"operator": "contains", "field": "tag", "value": "%s"}]}, "properties": [ "title", "studio", "mpaa", "file", "art" ] }, "id": "libTvShows"}' %tagname)
json_result = json.loads(json_query_string)
# If we found any, find all in progress episodes for each one.
if json_result.has_key('result') and json_result['result'].has_key('tvshows'):
for item in json_result['result']['tvshows']:
json_query2 = xbmc.executeJSONRPC('{"jsonrpc": "2.0", "method": "VideoLibrary.GetEpisodes", "params": { "tvshowid": %d, "sort": {"method":"episode"}, "filter": {"field": "inprogress", "operator": "true", "value":""}, "properties": [ "title", "playcount", "season", "episode", "showtitle", "plot", "file", "rating", "resume", "tvshowid", "art", "cast", "streamdetails", "firstaired", "runtime", "writer", "dateadded", "lastplayed" ]}, "id": "1"}' %item['tvshowid'])
if json_query2:
json_query2 = json.loads(json_query2)
if json_query2.has_key('result') and json_query2['result'].has_key('episodes'):
for item in json_query2['result']['episodes']:
liz = createListItem(item)
xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=item['file'], listitem=liz)
count +=1
if count == limit:
break
xbmcplugin.endOfDirectory(handle=int(sys.argv[1]))
def getRecentEpisodes(tagname,limit):
#if the addon is called with recentepisodes parameter, we return the recentepisodes list of the given tagname
xbmcplugin.setContent(int(sys.argv[1]), 'episodes')
# First we get a list of all the TV shows - filtered by tag
json_query_string = xbmc.executeJSONRPC('{"jsonrpc": "2.0", "method": "VideoLibrary.GetTVShows", "params": { "sort": { "order": "descending", "method": "dateadded" }, "properties": [ "title","sorttitle" ], "filter": {"operator": "contains", "field": "tag", "value": "%s"} }, "id": "libTvShows"}' %tagname)
json_result = json.loads(json_query_string)
# If we found any, put all tv show id's in a list
if json_result.has_key('result') and json_result['result'].has_key('tvshows'):
alltvshowIds = list()
for tvshow in json_result['result']['tvshows']:
alltvshowIds.append(tvshow["tvshowid"])
alltvshowIds = set(alltvshowIds)
#get all recently added episodes
json_query2 = xbmc.executeJSONRPC('{"jsonrpc": "2.0", "method": "VideoLibrary.GetEpisodes", "params": { "sort": {"order": "descending", "method": "dateadded"}, "filter": {"field": "playcount", "operator": "lessthan", "value":"1"}, "properties": [ "title", "playcount", "season", "episode", "showtitle", "plot", "file", "rating", "resume", "tvshowid", "art", "streamdetails", "firstaired", "runtime", "cast", "writer", "dateadded", "lastplayed" ]}, "limits":{"end":%d}, "id": "1"}' %limit)
count = 0
if json_query2:
json_query2 = json.loads(json_query2)
if json_query2.has_key('result') and json_query2['result'].has_key('episodes'):
for item in json_query2['result']['episodes']:
if item["tvshowid"] in alltvshowIds:
liz = createListItem(item)
xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=item['file'], listitem=liz)
count += 1
if count == limit:
break
xbmcplugin.endOfDirectory(handle=int(sys.argv[1]))
def createListItem(item):
liz = xbmcgui.ListItem(item['title'])
liz.setInfo( type="Video", infoLabels={ "Title": item['title'] })
liz.setProperty('IsPlayable', 'true')
liz.setInfo( type="Video", infoLabels={ "duration": str(item['runtime']/60) })
if "episode" in item:
episode = "%.2d" % float(item['episode'])
liz.setInfo( type="Video", infoLabels={ "Episode": item['episode'] })
if "season" in item:
season = "%.2d" % float(item['season'])
liz.setInfo( type="Video", infoLabels={ "Season": item['season'] })
if season and episode:
episodeno = "s%se%s" %(season,episode)
liz.setProperty("episodeno", episodeno)
if "firstaired" in item:
liz.setInfo( type="Video", infoLabels={ "Premiered": item['firstaired'] })
plot = item['plot']
liz.setInfo( type="Video", infoLabels={ "Plot": plot })
if "showtitle" in item:
liz.setInfo( type="Video", infoLabels={ "TVshowTitle": item['showtitle'] })
if "rating" in item:
liz.setInfo( type="Video", infoLabels={ "Rating": str(round(float(item['rating']),1)) })
liz.setInfo( type="Video", infoLabels={ "Playcount": item['playcount'] })
if "director" in item:
liz.setInfo( type="Video", infoLabels={ "Director": " / ".join(item['director']) })
if "writer" in item:
liz.setInfo( type="Video", infoLabels={ "Writer": " / ".join(item['writer']) })
if "cast" in item:
listCast = []
listCastAndRole = []
for castmember in item["cast"]:
listCast.append( castmember["name"] )
listCastAndRole.append( (castmember["name"], castmember["role"]) )
cast = [listCast, listCastAndRole]
liz.setInfo( type="Video", infoLabels={ "Cast": cast[0] })
liz.setInfo( type="Video", infoLabels={ "CastAndRole": cast[1] })
liz.setProperty("resumetime", str(item['resume']['position']))
liz.setProperty("totaltime", str(item['resume']['total']))
liz.setArt(item['art'])
liz.setThumbnailImage(item['art'].get('thumb',''))
liz.setIconImage('DefaultTVShows.png')
liz.setProperty("dbid", str(item['episodeid']))
liz.setProperty("fanart_image", item['art'].get('tvshow.fanart',''))
for key, value in item['streamdetails'].iteritems():
for stream in value:
liz.addStreamInfo( key, stream )
return liz
##### GET EXTRAFANART FOR LISTITEM #####
def getExtraFanArt():
itemPath = ""
embyId = ""
#get extrafanart for listitem - this will only be used for skins that actually call the listitem's path + fanart dir...
try:
#only do this if the listitem has actually changed
itemPath = xbmc.getInfoLabel("ListItem.FileNameAndPath")
if not itemPath:
itemPath = xbmc.getInfoLabel("ListItem.Path")
if ("/tvshows/" in itemPath or "/musicvideos/" in itemPath or "/movies/" in itemPath):
embyId = itemPath.split("/")[-2]
utils.logMsg("%s %s" % ("Emby addon", "getExtraFanArt"), "requesting extraFanArt for Id: " + embyId, 1)
#we need to store the images locally for this to work because of the caching system in xbmc
fanartDir = xbmc.translatePath("special://thumbnails/emby/" + embyId + "/")
if not xbmcvfs.exists(fanartDir):
#download the images to the cache directory
xbmcvfs.mkdir(fanartDir)
item = ReadEmbyDB().getFullItem(embyId)
if item != None:
if item.has_key("BackdropImageTags"):
if(len(item["BackdropImageTags"]) > 0):
WINDOW = xbmcgui.Window(10000)
username = WINDOW.getProperty('currUser')
server = WINDOW.getProperty('server%s' % username)
totalbackdrops = len(item["BackdropImageTags"])
count = 0
for backdrop in item["BackdropImageTags"]:
backgroundUrl = "%s/mediabrowser/Items/%s/Images/Backdrop/%s/?MaxWidth=10000&MaxHeight=10000&Format=original&Tag=%s&EnableImageEnhancers=false" % (server, embyId, str(count), backdrop)
count += 1
fanartFile = os.path.join(fanartDir,"fanart" + backdrop + ".jpg")
li = xbmcgui.ListItem(backdrop, path=fanartFile)
xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=fanartFile, listitem=li)
xbmcvfs.copy(backgroundUrl,fanartFile)
else:
#use existing cached images
dirs, files = xbmcvfs.listdir(fanartDir)
count = 1
for file in files:
count +=1
li = xbmcgui.ListItem(file, path=os.path.join(fanartDir,file))
xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=os.path.join(fanartDir,file), listitem=li)
except Exception as e:
utils.logMsg("%s %s" % ("Emby addon", "Error in getExtraFanArt"), str(e), 1)
pass
#always do endofdirectory to prevent errors in the logs
xbmcplugin.endOfDirectory(int(sys.argv[1]))
def addDirectoryItem(label, path, folder=True):
li = xbmcgui.ListItem(label, path=path)
li.setThumbnailImage("special://home/addons/plugin.video.emby/icon.png")
li.setArt({"fanart":"special://home/addons/plugin.video.emby/fanart.jpg"})
li.setArt({"landscape":"special://home/addons/plugin.video.emby/fanart.jpg"})
xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=path, listitem=li, isFolder=folder)
# if the addon is called without parameters we show the listing...
def doMainListing():
xbmcplugin.setContent(int(sys.argv[1]), 'files')
#get emby nodes from the window props
embyProperty = WINDOW.getProperty("Emby.nodes.total")
if embyProperty:
totalNodes = int(embyProperty)
for i in range(totalNodes):
path = WINDOW.getProperty("Emby.nodes.%s.index" %str(i))
if not path:
path = WINDOW.getProperty("Emby.nodes.%s.content" %str(i))
label = WINDOW.getProperty("Emby.nodes.%s.title" %str(i))
if path:
addDirectoryItem(label, path)
# some extra entries for settings and stuff. TODO --> localize the labels
addDirectoryItem("Settings", "plugin://plugin.video.emby/?mode=settings")
addDirectoryItem("Perform manual sync", "plugin://plugin.video.emby/?mode=manualsync")
addDirectoryItem("Add user to session", "plugin://plugin.video.emby/?mode=adduser")
addDirectoryItem("Configure user preferences", "plugin://plugin.video.emby/?mode=userprefs")
addDirectoryItem("Perform local database reset (full resync)", "plugin://plugin.video.emby/?mode=reset")
addDirectoryItem("Cache all images to Kodi texture cache (advanced)", "plugin://plugin.video.emby/?mode=texturecache")
addDirectoryItem("Sync Emby Theme Media to Kodi", "plugin://plugin.video.emby/?mode=thememedia")
xbmcplugin.endOfDirectory(int(sys.argv[1]))

View file

@ -1,150 +0,0 @@
#################################################################################################
# Kodi Monitor
# Watched events that occur in Kodi, like setting media watched
#################################################################################################
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
class Kodi_Monitor( xbmc.Monitor ):
WINDOW = xbmcgui.Window(10000)
def __init__(self, *args, **kwargs):
xbmc.Monitor.__init__(self)
def logMsg(self, msg, lvl = 1):
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)
def onScanFinished(self, library):
utils.window('kodiScan', clear=True)
self.logMsg("Kodi library scan finished.", 2)
#this library monitor is used to detect a watchedstate change by the user through the library
#as well as detect when a library item has been deleted to pass the delete to the Emby server
def onNotification (self, sender, method, data):
WINDOW = self.WINDOW
downloadUtils = DownloadUtils()
#player started playing an item -
if ("Playlist.OnAdd" in method or "Player.OnPlay" in method):
jsondata = json.loads(data)
if jsondata:
if jsondata.has_key("item"):
if jsondata.get("item").has_key("id") and jsondata.get("item").has_key("type"):
id = jsondata.get("item").get("id")
type = jsondata.get("item").get("type")
if (utils.settings('useDirectPaths')=='true' and not type == "song") or (type == "song" and utils.settings('enableMusicSync') == "true"):
if type == "song":
connection = utils.KodiSQL('music')
cursor = connection.cursor()
embyid = ReadKodiDB().getEmbyIdByKodiId(id, type, connection, cursor)
cursor.close()
else:
embyid = ReadKodiDB().getEmbyIdByKodiId(id,type)
if embyid:
url = "{server}/mediabrowser/Users/{UserId}/Items/%s?format=json" % embyid
result = downloadUtils.downloadUrl(url)
self.logMsg("Result: %s" % result, 2)
playurl = None
count = 0
while not playurl and count < 2:
try:
playurl = xbmc.Player().getPlayingFile()
except RuntimeError:
xbmc.sleep(200)
else:
listItem = xbmcgui.ListItem()
PlaybackUtils().setProperties(playurl, result, listItem)
if type == "song" and utils.settings('directstreammusic') == "true":
utils.window('%splaymethod' % playurl, value="DirectStream")
else:
utils.window('%splaymethod' % playurl, value="DirectPlay")
count += 1
if method == "VideoLibrary.OnUpdate":
# Triggers 4 times, the following is only for manually marking as watched/unwatched
jsondata = json.loads(data)
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)
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)
self.clearProperty(type, item)
if method == "System.OnWake":
xbmc.sleep(10000) #Allow network to wake up
WINDOW.setProperty("OnWakeSync", "true")
if method == "VideoLibrary.OnRemove":
xbmc.log('Intercepted remove from sender: ' + sender + ' method: ' + method + ' data: ' + data)
jsondata = json.loads(data)
id = ReadKodiDB().getEmbyIdByKodiId(jsondata.get("id"), jsondata.get("type"))
if id == None:
return
xbmc.log("Deleting Emby ID: " + id + " from database")
connection = utils.KodiSQL()
cursor = connection.cursor()
cursor.execute("DELETE FROM emby WHERE emby_id = ?", (id,))
connection.commit()
cursor.close
if jsondata:
if jsondata.get("type") == "episode" or "movie":
url='{server}/mediabrowser/Items?Ids=' + id + '&format=json'
#This is a check to see if the item exists on the server, if it doesn't it may have already been deleted by another client
result = DownloadUtils().downloadUrl(url)
item = result.get("Items")[0]
if data:
return_value = xbmcgui.Dialog().yesno("Confirm Delete", "Delete file on Emby Server?")
if return_value:
url='{server}/mediabrowser/Items/' + id
xbmc.log('Deleting via URL: ' + url)
DownloadUtils().downloadUrl(url, type="DELETE")
elif method == "Playlist.OnClear":
self.logMsg("Clear playback properties.", 2)
utils.window('propertiesPlayback', clear=True)
def clearProperty(self, type, id):
# The sleep is necessary since VideoLibrary.OnUpdate
# triggers 4 times in a row.
xbmc.sleep(100)
utils.window('Played%s%s' % (type,id), clear=True)
# Clear the widget cache
utils.window('clearwidgetcache', value="clear")

File diff suppressed because it is too large Load diff

View file

@ -1,364 +0,0 @@
# -*- coding: utf-8 -*-
#################################################################################################
import xbmc
import xbmcgui
import xbmcvfs
from ClientInformation import ClientInformation
import Utils as utils
#################################################################################################
class PlayUtils():
clientInfo = ClientInformation()
addonName = clientInfo.getAddonName()
def logMsg(self, msg, lvl=1):
className = self.__class__.__name__
utils.logMsg("%s %s" % (self.addonName, className), msg, int(lvl))
def getPlayUrl(self, server, id, result):
if self.isDirectPlay(result,True):
# Try direct play
playurl = self.directPlay(result)
if playurl:
self.logMsg("File is direct playing.", 1)
utils.window("%splaymethod" % playurl.encode('utf-8'), value="DirectPlay")
elif self.isDirectStream(result):
# Try direct stream
playurl = self.directStream(result, server, id)
if playurl:
self.logMsg("File is direct streaming.", 1)
utils.window("%splaymethod" % playurl, value="DirectStream")
elif self.isTranscoding(result):
# Try transcoding
playurl = self.transcoding(result, server, id)
if playurl:
self.logMsg("File is transcoding.", 1)
utils.window("%splaymethod" % playurl, value="Transcode")
else: # Error
utils.window("playurlFalse", value="true")
return
return playurl.encode('utf-8')
def isDirectPlay(self, result, dialog = False):
# Requirements for Direct play:
# FileSystem, Accessible path
if utils.settings('playFromStream') == "true":
# User forcing to play via HTTP instead of SMB
self.logMsg("Can't direct play: Play from HTTP is enabled.", 1)
return False
# Avoid H265 1080p
if (utils.settings('transcodeH265') == "true" and
result['MediaSources'][0]['Name'].startswith("1080P/H265")):
self.logMsg("Option to transcode 1080P/H265 enabled.", 1)
return False
canDirectPlay = result['MediaSources'][0]['SupportsDirectPlay']
# Make sure it's supported by server
if not canDirectPlay:
self.logMsg("Can't direct play: Server does not allow or support it.", 1)
return False
location = result['LocationType']
# File needs to be "FileSystem"
if 'FileSystem' in location:
# Verify if path is accessible
if self.fileExists(result):
return True
else:
self.logMsg("Unable to direct play. Verify the following path is accessible by the device: %s. You might also need to add SMB credentials in the add-on settings." % result['MediaSources'][0]['Path'], 1)
if dialog:
failCount = int(utils.settings('directSteamFailedCount'))
self.logMsg("Direct Play failCount: %s." % failCount, 1)
if failCount < 2:
# Let user know that direct play failed
utils.settings('directSteamFailedCount', value=str(failCount + 1))
xbmcgui.Dialog().notification("Emby server", "Unable to direct play. Verify your log for more information.", icon="special://home/addons/plugin.video.emby/icon.png", sound=False)
elif utils.settings('playFromStream') != "true":
# Permanently set direct stream as true
utils.settings('playFromStream', value="true")
xbmcgui.Dialog().notification("Emby server", "Enabled play from HTTP in add-on settings.", icon="special://home/addons/plugin.video.emby/icon.png", sound=False)
return False
def directPlay(self, result):
try:
playurl = result['MediaSources'][0]['Path']
except KeyError:
playurl = result['Path']
if 'VideoType' in result:
# Specific format modification
if 'Dvd' in result['VideoType']:
playurl = "%s/VIDEO_TS/VIDEO_TS.IFO" % playurl
elif 'BluRay' in result['VideoType']:
playurl = "%s/BDMV/index.bdmv" % playurl
# Network - SMB protocol
if "\\\\" in playurl:
smbuser = utils.settings('smbusername')
smbpass = utils.settings('smbpassword')
# Network share
if smbuser:
playurl = playurl.replace("\\\\", "smb://%s:%s@" % (smbuser, smbpass))
else:
playurl = playurl.replace("\\\\", "smb://")
playurl = playurl.replace("\\", "/")
if "apple.com" in playurl:
USER_AGENT = "QuickTime/7.7.4"
playurl += "?|User-Agent=%s" % USER_AGENT
return playurl
def isDirectStream(self, result):
# Requirements for Direct stream:
# FileSystem or Remote, BitRate, supported encoding
# Avoid H265 1080p
if (utils.settings('transcodeH265') == "true" and
result['MediaSources'][0]['Name'].startswith("1080P/H265")):
self.logMsg("Option to transcode 1080P/H265 enabled.", 1)
return False
canDirectStream = result['MediaSources'][0]['SupportsDirectStream']
# Make sure it's supported by server
if not canDirectStream:
return False
location = result['LocationType']
# File can be FileSystem or Remote, not Virtual
if 'Virtual' in location:
self.logMsg("File location is virtual. Can't proceed.", 1)
return False
# Verify BitRate
if not self.isNetworkQualitySufficient(result):
self.logMsg("The network speed is insufficient to playback the file.", 1)
return False
return True
def directStream(self, result, server, id, type = "Video"):
if result['Path'].endswith('.strm'):
# Allow strm loading when direct streaming
playurl = self.directPlay(result)
return playurl
if "ThemeVideo" in type:
playurl = "%s/mediabrowser/Videos/%s/stream?static=true" % (server, id)
elif "Video" in type:
playurl = "%s/mediabrowser/Videos/%s/stream?static=true" % (server, id)
elif "Audio" in type:
playurl = "%s/mediabrowser/Audio/%s/stream.mp3" % (server, id)
return playurl
def isTranscoding(self, result):
# Last resort, no requirements
# BitRate
canTranscode = result['MediaSources'][0]['SupportsTranscoding']
# Make sure it's supported by server
if not canTranscode:
return False
location = result['LocationType']
# File can be FileSystem or Remote, not Virtual
if 'Virtual' in location:
return False
return True
def transcoding(self, result, server, id):
if result['Path'].endswith('.strm'):
# Allow strm loading when transcoding
playurl = self.directPlay(result)
return playurl
# Play transcoding
deviceId = self.clientInfo.getMachineId()
playurl = "%s/mediabrowser/Videos/%s/master.m3u8?mediaSourceId=%s" % (server, id, id)
playurl = "%s&VideoCodec=h264&AudioCodec=ac3&MaxAudioChannels=6&deviceId=%s&VideoBitrate=%s" % (playurl, deviceId, self.getVideoBitRate()*1000)
playurl = self.audioSubsPref(playurl, result.get('MediaSources'))
self.logMsg("Playurl: %s" % playurl, 1)
return playurl
def isNetworkQualitySufficient(self, result):
# Works out if the network quality can play directly or if transcoding is needed
settingsVideoBitRate = self.getVideoBitRate()
settingsVideoBitRate = settingsVideoBitRate * 1000
try:
mediaSources = result['MediaSources']
sourceBitRate = int(mediaSources[0]['Bitrate'])
except KeyError:
self.logMsg("Bitrate value is missing.", 1)
else:
self.logMsg("The video quality selected is: %s, the video bitrate required to direct stream is: %s." % (settingsVideoBitRate, sourceBitRate), 1)
if settingsVideoBitRate < sourceBitRate:
return False
return True
def getVideoBitRate(self):
# get the addon video quality
videoQuality = utils.settings('videoBitRate')
bitrate = {
'0': 664,
'1': 996,
'2': 1320,
'3': 2000,
'4': 3200,
'5': 4700,
'6': 6200,
'7': 7700,
'8': 9200,
'9': 10700,
'10': 12200,
'11': 13700,
'12': 15200,
'13': 16700,
'14': 18200,
'15': 20000,
'16': 40000,
'17': 100000,
'18': 1000000
}
# max bit rate supported by server (max signed 32bit integer)
return bitrate.get(videoQuality, 2147483)
def fileExists(self, result):
if 'Path' not in result:
# File has no path in server
return False
# Convert Emby path to a path we can verify
path = self.directPlay(result)
try:
pathexists = xbmcvfs.exists(path)
except:
pathexists = False
# Verify the device has access to the direct path
if pathexists:
# Local or Network path
self.logMsg("Path exists.", 2)
return True
elif ":" not in path:
# Give benefit of the doubt for nfs.
self.logMsg("Can't verify path (assumed NFS). Still try direct play.", 2)
return True
else:
self.logMsg("Path is detected as follow: %s. Try direct streaming." % path, 2)
return False
def audioSubsPref(self, url, mediaSources):
# For transcoding only
# Present the list of audio to select from
audioStreamsList = {}
audioStreams = []
audioStreamsChannelsList = {}
subtitleStreamsList = {}
subtitleStreams = ['No subtitles']
selectAudioIndex = ""
selectSubsIndex = ""
playurlprefs = "%s" % url
mediaStream = mediaSources[0].get('MediaStreams')
for stream in mediaStream:
# Since Emby returns all possible tracks together, have to sort them.
index = stream['Index']
type = stream['Type']
if 'Audio' in type:
codec = stream['Codec']
channelLayout = stream['ChannelLayout']
try:
track = "%s - %s - %s %s" % (index, stream['Language'], codec, channelLayout)
except:
track = "%s - %s %s" % (index, codec, channelLayout)
audioStreamsChannelsList[index] = stream['Channels']
audioStreamsList[track] = index
audioStreams.append(track)
elif 'Subtitle' in type:
try:
track = "%s - %s" % (index, stream['Language'])
except:
track = "%s - %s" % (index, stream['Codec'])
default = stream['IsDefault']
forced = stream['IsForced']
if default:
track = "%s - Default" % track
if forced:
track = "%s - Forced" % track
subtitleStreamsList[track] = index
subtitleStreams.append(track)
if len(audioStreams) > 1:
resp = xbmcgui.Dialog().select("Choose the audio stream", audioStreams)
if resp > -1:
# User selected audio
selected = audioStreams[resp]
selectAudioIndex = audioStreamsList[selected]
playurlprefs += "&AudioStreamIndex=%s" % selectAudioIndex
else: # User backed out of selection
playurlprefs += "&AudioStreamIndex=%s" % mediaSources[0]['DefaultAudioStreamIndex']
else: # There's only one audiotrack.
selectAudioIndex = audioStreamsList[audioStreams[0]]
playurlprefs += "&AudioStreamIndex=%s" % selectAudioIndex
if len(subtitleStreams) > 1:
resp = xbmcgui.Dialog().select("Choose the subtitle stream", subtitleStreams)
if resp == 0:
# User selected no subtitles
pass
elif resp > -1:
# User selected subtitles
selected = subtitleStreams[resp]
selectSubsIndex = subtitleStreamsList[selected]
playurlprefs += "&SubtitleStreamIndex=%s" % selectSubsIndex
else: # User backed out of selection
playurlprefs += "&SubtitleStreamIndex=%s" % mediaSources[0].get('DefaultSubtitleStreamIndex', "")
# Get number of channels for selected audio track
audioChannels = audioStreamsChannelsList.get(selectAudioIndex, 0)
if audioChannels > 2:
playurlprefs += "&AudioBitrate=384000"
else:
playurlprefs += "&AudioBitrate=192000"
return playurlprefs

View file

@ -1,462 +0,0 @@
# -*- coding: utf-8 -*-
#################################################################################################
import datetime
import json as json
import sys
import xbmc
import xbmcaddon
import xbmcplugin
import xbmcgui
from API import API
from DownloadUtils import DownloadUtils
from PlayUtils import PlayUtils
from ClientInformation import ClientInformation
import Utils as utils
#################################################################################################
class PlaybackUtils():
clientInfo = ClientInformation()
doUtils = DownloadUtils()
api = API()
addon = xbmcaddon.Addon()
language = addon.getLocalizedString
addonName = clientInfo.getAddonName()
def logMsg(self, msg, lvl=1):
className = self.__class__.__name__
utils.logMsg("%s %s" % (self.addonName, className), msg, int(lvl))
def PLAY(self, result, setup = "service"):
self.logMsg("PLAY Called", 1)
api = self.api
doUtils = self.doUtils
username = utils.window('currUser')
server = utils.window('server%s' % username)
id = result['Id']
userdata = result['UserData']
# Get the playurl - direct play, direct stream or transcoding
playurl = PlayUtils().getPlayUrl(server, id, result)
listItem = xbmcgui.ListItem()
if utils.window('playurlFalse') == "true":
# Playurl failed - set in PlayUtils.py
utils.window('playurlFalse', clear=True)
self.logMsg("Failed to retrieve the playback path/url or dialog was cancelled.", 1)
return xbmcplugin.setResolvedUrl(int(sys.argv[1]), False, listItem)
############### -- SETUP MAIN ITEM ################
# Set listitem and properties for main item
self.logMsg("Returned playurl: %s" % playurl, 1)
listItem.setPath(playurl)
self.setProperties(playurl, result, listItem)
mainArt = API().getArtwork(result, "Primary")
listItem.setThumbnailImage(mainArt)
listItem.setIconImage(mainArt)
############### ORGANIZE CURRENT PLAYLIST ################
homeScreen = xbmc.getCondVisibility('Window.IsActive(home)')
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)
############### 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)
# 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.")
############### -- CHECK FOR INTROS ################
if utils.settings('disableCinema') == "false" and not seekTime:
# if we have any play them when the movie/show is not being resumed
url = "{server}/mediabrowser/Users/{UserId}/Items/%s/Intros?format=json&ImageTypeLimit=1&Fields=Etag" % id
intros = doUtils.downloadUrl(url)
if intros['TotalRecordCount'] != 0:
getTrailers = True
if utils.settings('askCinema') == "true":
resp = xbmcgui.Dialog().yesno("Emby Cinema Mode", "Play trailers?")
if not resp:
# User selected to not play trailers
getTrailers = False
self.logMsg("Skip trailers.", 1)
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()
self.logMsg("Adding Intro: %s" % introPlayurl, 1)
# Set listitem and properties for intros
self.setProperties(introPlayurl, intro, introListItem)
self.setListItemProps(server, introId, introListItem, intro)
playlist.add(introPlayurl, introListItem, index=currentPosition)
introsPlaylist = True
currentPosition += 1
############### -- 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
self.logMsg("Adding main item to playlist.", 1)
self.setListItemProps(server, id, listItem, result)
playlist.add(playurl, listItem, index=currentPosition)
# Ensure that additional parts are played after the main item
currentPosition += 1
############### -- CHECK FOR ADDITIONAL PARTS ################
if result.get('PartCount'):
# Only add to the playlist after intros have played
partcount = result['PartCount']
url = "{server}/mediabrowser/Videos/%s/AdditionalParts" % id
parts = doUtils.downloadUrl(url)
for part in parts['Items']:
partId = part['Id']
additionalPlayurl = PlayUtils().getPlayUrl(server, partId, part)
additionalListItem = xbmcgui.ListItem()
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)
playlist.add(additionalPlayurl, additionalListItem, index=currentPosition)
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
# We just skipped adding properties. Reset flag for next time.
elif propertiesPlayback:
self.logMsg("Resetting properties playback flag.", 2)
utils.window('propertiesPlayback', clear=True)
self.verifyPlaylist()
############### PLAYBACK ################
if not homeScreen and not introsPlaylist:
self.logMsg("Processed as a single item.", 1)
xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, listItem)
elif dummyPlaylist:
# Added a dummy file to the playlist because the first item is going to fail automatically.
self.logMsg("Processed as a playlist. First item is skipped.", 1)
xbmcplugin.setResolvedUrl(int(sys.argv[1]), False, listItem)
else:
self.logMsg("Play as a regular item.", 1)
xbmc.Player().play(playlist, startpos=startPos)
def verifyPlaylist(self):
playlistitems = '{"jsonrpc": "2.0", "method": "Playlist.GetItems", "params": { "playlistid": 1 }, "id": 1}'
items = xbmc.executeJSONRPC(playlistitems)
self.logMsg(items, 2)
def removeFromPlaylist(self, pos):
playlistremove = '{"jsonrpc": "2.0", "method": "Playlist.Remove", "params": { "playlistid": 1, "position": %d }, "id": 1}' % pos
result = xbmc.executeJSONRPC(playlistremove)
self.logMsg(result, 1)
def externalSubs(self, id, playurl, mediaSources):
username = utils.window('currUser')
server = utils.window('server%s' % username)
externalsubs = []
mapping = {}
mediaStream = mediaSources[0].get('MediaStreams')
kodiindex = 0
for stream in mediaStream:
index = stream['Index']
# Since Emby returns all possible tracks together, have to pull only external subtitles.
# IsTextSubtitleStream if true, is available to download from emby.
if "Subtitle" in stream['Type'] and stream['IsExternal'] and stream['IsTextSubtitleStream']:
playmethod = utils.window("%splaymethod" % playurl)
if "DirectPlay" in playmethod:
# Direct play, get direct path
url = PlayUtils().directPlay(stream)
elif "DirectStream" in playmethod: # Direct stream
url = "%s/Videos/%s/%s/Subtitles/%s/Stream.srt" % (server, id, id, index)
# map external subtitles for mapping
mapping[kodiindex] = index
externalsubs.append(url)
kodiindex += 1
mapping = json.dumps(mapping)
utils.window('%sIndexMapping' % playurl, value=mapping)
return externalsubs
def setProperties(self, playurl, result, listItem):
# Set runtimeticks, type, refresh_id and item_id
id = result.get('Id')
type = result.get('Type', "")
utils.window("%sruntimeticks" % playurl, value=str(result.get('RunTimeTicks')))
utils.window("%stype" % playurl, value=type)
utils.window("%sitem_id" % playurl, value=id)
if type == "Episode":
utils.window("%srefresh_id" % playurl, value=result.get('SeriesId'))
else:
utils.window("%srefresh_id" % playurl, value=id)
if utils.window("%splaymethod" % playurl) != "Transcode":
# Only for direct play and direct stream
# Append external subtitles to stream
subtitleList = self.externalSubs(id, playurl, result['MediaSources'])
listItem.setSubtitles(subtitleList)
def setArt(self, list, name, path):
if name in ("thumb", "fanart_image", "small_poster", "tiny_poster", "medium_landscape", "medium_poster", "small_fanartimage", "medium_fanartimage", "fanart_noindicators"):
list.setProperty(name, path)
else:
list.setArt({name:path})
return list
def setListItemProps(self, server, id, listItem, result):
# Set up item and item info
api = self.api
type = result.get('Type')
people = api.getPeople(result)
studios = api.getStudios(result)
metadata = {
'title': result.get('Name', "Missing name"),
'year': result.get('ProductionYear'),
'plot': api.getOverview(result),
'director': people.get('Director'),
'writer': people.get('Writer'),
'mpaa': api.getMpaa(result),
'genre': api.getGenre(result),
'studio': " / ".join(studios),
'aired': api.getPremiereDate(result),
'rating': result.get('CommunityRating'),
'votes': result.get('VoteCount')
}
if "Episode" in type:
# Only for tv shows
thumbId = result.get('SeriesId')
season = result.get('ParentIndexNumber', -1)
episode = result.get('IndexNumber', -1)
show = result.get('SeriesName', "")
metadata['TVShowTitle'] = show
metadata['season'] = season
metadata['episode'] = episode
listItem.setProperty('IsPlayable', 'true')
listItem.setProperty('IsFolder', 'false')
listItem.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

@ -1,440 +0,0 @@
# -*- coding: utf-8 -*-
#################################################################################################
import json as 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
#################################################################################################
class Player( xbmc.Player ):
# Borg - multiple instances, shared state
_shared_state = {}
xbmcplayer = xbmc.Player()
doUtils = DownloadUtils()
clientInfo = ClientInformation()
ws = WebSocketThread()
librarySync = LibrarySync()
addonName = clientInfo.getAddonName()
played_information = {}
playStats = {}
currentFile = None
def __init__(self, *args):
self.__dict__ = self._shared_state
self.logMsg("Starting playback monitor.", 2)
def logMsg(self, msg, lvl=1):
self.className = self.__class__.__name__
utils.logMsg("%s %s" % (self.addonName, self.className), msg, int(lvl))
def GetPlayStats(self):
return self.playStats
def onPlayBackStarted( self ):
# Will be called when xbmc starts playing a file
xbmcplayer = self.xbmcplayer
self.stopAll()
# Get current file
try:
currentFile = xbmcplayer.getPlayingFile()
xbmc.sleep(300)
except:
currentFile = ""
count = 0
while not currentFile:
xbmc.sleep(100)
try:
currentFile = xbmcplayer.getPlayingFile()
except: pass
if count == 5: # try 5 times
self.logMsg("Cancelling playback report...", 1)
break
else: count += 1
if currentFile:
self.currentFile = currentFile
# We may need to wait for info to be set in kodi monitor
itemId = utils.window("%sitem_id" % currentFile)
tryCount = 0
while not itemId:
xbmc.sleep(200)
itemId = utils.window("%sitem_id" % currentFile)
if tryCount == 20: # try 20 times or about 10 seconds
self.logMsg("Could not find itemId, cancelling playback report...", 1)
break
else: tryCount += 1
else:
self.logMsg("ONPLAYBACK_STARTED: %s ITEMID: %s" % (currentFile, itemId), 0)
# Only proceed if an itemId was found.
runtime = utils.window("%sruntimeticks" % currentFile)
refresh_id = utils.window("%srefresh_id" % currentFile)
playMethod = utils.window("%splaymethod" % currentFile)
itemType = utils.window("%stype" % currentFile)
seekTime = xbmcplayer.getTime()
# Get playback volume
volume_query = '{"jsonrpc": "2.0", "method": "Application.GetProperties", "params": {"properties": ["volume","muted"]}, "id": 1}'
result = xbmc.executeJSONRPC(volume_query)
result = json.loads(result)
result = result.get('result')
volume = result.get('volume')
muted = result.get('muted')
# Postdata structure to send to Emby server
url = "{server}/mediabrowser/Sessions/Playing"
postdata = {
'QueueableMediaTypes': "Video",
'CanSeek': True,
'ItemId': itemId,
'MediaSourceId': itemId,
'PlayMethod': playMethod,
'VolumeLevel': volume,
'PositionTicks': int(seekTime * 10000000),
'IsMuted': muted
}
# Get the current audio track and subtitles
if playMethod == "Transcode":
# property set in PlayUtils.py
postdata['AudioStreamIndex'] = utils.window("%sAudioStreamIndex" % currentFile)
postdata['SubtitleStreamIndex'] = utils.window("%sSubtitleStreamIndex" % currentFile)
else:
# Get the current kodi audio and subtitles and convert to Emby equivalent
track_query = '{"jsonrpc": "2.0", "method": "Player.GetProperties", "params": {"playerid": 1,"properties": ["currentsubtitle","currentaudiostream","subtitleenabled"]} , "id": 1}'
result = xbmc.executeJSONRPC(track_query)
result = json.loads(result)
result = result.get('result')
try: # Audio tracks
indexAudio = result['currentaudiostream']['index']
except (KeyError, TypeError):
indexAudio = 0
try: # Subtitles tracks
indexSubs = result['currentsubtitle']['index']
except (KeyError, TypeError):
indexSubs = 0
try: # If subtitles are enabled
subsEnabled = result['subtitleenabled']
except (KeyError, TypeError):
subsEnabled = ""
# Postdata for the audio
postdata['AudioStreamIndex'] = indexAudio + 1
# Postdata for the subtitles
if subsEnabled and len(xbmc.Player().getAvailableSubtitleStreams()) > 0:
# Number of audiotracks to help get Emby Index
audioTracks = len(xbmc.Player().getAvailableAudioStreams())
mapping = utils.window("%sIndexMapping" % currentFile)
if mapping: # Set in PlaybackUtils.py
self.logMsg("Mapping for external subtitles index: %s" % mapping, 2)
externalIndex = json.loads(mapping)
if externalIndex.get(str(indexSubs)):
# If the current subtitle is in the mapping
postdata['SubtitleStreamIndex'] = externalIndex[str(indexSubs)]
else:
# Internal subtitle currently selected
postdata['SubtitleStreamIndex'] = indexSubs - len(externalIndex) + audioTracks + 1
else: # Direct paths enabled scenario or no external subtitles set
postdata['SubtitleStreamIndex'] = indexSubs + audioTracks + 1
else:
postdata['SubtitleStreamIndex'] = ""
# Post playback to server
self.logMsg("Sending POST play started: %s." % postdata, 2)
self.doUtils.downloadUrl(url, postBody=postdata, type="POST")
# Ensure we do have a runtime
try:
runtime = int(runtime)
except ValueError:
runtime = xbmcplayer.getTotalTime()
self.logMsg("Runtime is missing, grabbing runtime from Kodi player: %s" % runtime, 1)
# Save data map for updates and position calls
data = {
'runtime': runtime,
'item_id': itemId,
'refresh_id': refresh_id,
'currentfile': currentFile,
'AudioStreamIndex': postdata['AudioStreamIndex'],
'SubtitleStreamIndex': postdata['SubtitleStreamIndex'],
'playmethod': playMethod,
'Type': itemType,
'currentPosition': int(seekTime)
}
self.played_information[currentFile] = data
self.logMsg("ADDING_FILE: %s" % self.played_information, 1)
# log some playback stats
'''if(itemType != None):
if(self.playStats.get(itemType) != None):
count = self.playStats.get(itemType) + 1
self.playStats[itemType] = count
else:
self.playStats[itemType] = 1
if(playMethod != None):
if(self.playStats.get(playMethod) != None):
count = self.playStats.get(playMethod) + 1
self.playStats[playMethod] = count
else:
self.playStats[playMethod] = 1'''
def reportPlayback(self):
self.logMsg("reportPlayback Called", 2)
xbmcplayer = self.xbmcplayer
# Get current file
currentFile = self.currentFile
data = self.played_information.get(currentFile)
# only report playback if emby has initiated the playback (item_id has value)
if data:
# Get playback information
itemId = data['item_id']
audioindex = data['AudioStreamIndex']
subtitleindex = data['SubtitleStreamIndex']
playTime = data['currentPosition']
playMethod = data['playmethod']
paused = data.get('paused', False)
# Get playback volume
volume_query = '{"jsonrpc": "2.0", "method": "Application.GetProperties", "params": {"properties": ["volume","muted"]}, "id": 1}'
result = xbmc.executeJSONRPC(volume_query)
result = json.loads(result)
result = result.get('result')
volume = result.get('volume')
muted = result.get('muted')
# Postdata for the websocketclient report
postdata = {
'QueueableMediaTypes': "Video",
'CanSeek': True,
'ItemId': itemId,
'MediaSourceId': itemId,
'PlayMethod': playMethod,
'PositionTicks': int(playTime * 10000000),
'IsPaused': paused,
'VolumeLevel': volume,
'IsMuted': muted
}
if playMethod == "Transcode":
# Track can't be changed, keep reporting the same index
postdata['AudioStreamIndex'] = audioindex
postdata['AudioStreamIndex'] = subtitleindex
else:
# Get current audio and subtitles track
track_query = '{"jsonrpc": "2.0", "method": "Player.GetProperties", "params": {"playerid":1,"properties": ["currentsubtitle","currentaudiostream","subtitleenabled"]} , "id": 1}'
result = xbmc.executeJSONRPC(track_query)
result = json.loads(result)
result = result.get('result')
try: # Audio tracks
indexAudio = result['currentaudiostream']['index']
except (KeyError, TypeError):
indexAudio = 0
try: # Subtitles tracks
indexSubs = result['currentsubtitle']['index']
except (KeyError, TypeError):
indexSubs = 0
try: # If subtitles are enabled
subsEnabled = result['subtitleenabled']
except (KeyError, TypeError):
subsEnabled = ""
# Postdata for the audio
data['AudioStreamIndex'], postdata['AudioStreamIndex'] = [indexAudio + 1] * 2
# Postdata for the subtitles
if subsEnabled and len(xbmc.Player().getAvailableSubtitleStreams()) > 0:
# Number of audiotracks to help get Emby Index
audioTracks = len(xbmc.Player().getAvailableAudioStreams())
mapping = utils.window("%sIndexMapping" % currentFile)
if mapping: # Set in PlaybackUtils.py
self.logMsg("Mapping for external subtitles index: %s" % mapping, 2)
externalIndex = json.loads(mapping)
if externalIndex.get(str(indexSubs)):
# If the current subtitle is in the mapping
data['SubtitleStreamIndex'], postdata['SubtitleStreamIndex'] = [externalIndex[str(indexSubs)]] * 2
else:
# Internal subtitle currently selected
data['SubtitleStreamIndex'], postdata['SubtitleStreamIndex'] = [indexSubs - len(externalIndex) + audioTracks + 1] * 2
else: # Direct paths enabled scenario or no external subtitles set
data['SubtitleStreamIndex'], postdata['SubtitleStreamIndex'] = [indexSubs + audioTracks + 1] * 2
else:
data['SubtitleStreamIndex'], postdata['SubtitleStreamIndex'] = [""] * 2
# Report progress via websocketclient
postdata = json.dumps(postdata)
self.logMsg("Report: %s" % postdata, 2)
self.ws.sendProgressUpdate(postdata)
def onPlayBackPaused( self ):
currentFile = self.currentFile
self.logMsg("PLAYBACK_PAUSED: %s" % currentFile, 2)
if self.played_information.get(currentFile):
self.played_information[currentFile]['paused'] = True
self.reportPlayback()
def onPlayBackResumed( self ):
currentFile = self.currentFile
self.logMsg("PLAYBACK_RESUMED: %s" % currentFile, 2)
if self.played_information.get(currentFile):
self.played_information[currentFile]['paused'] = False
self.reportPlayback()
def onPlayBackSeek( self, time, seekOffset ):
# Make position when seeking a bit more accurate
currentFile = self.currentFile
self.logMsg("PLAYBACK_SEEK: %s" % currentFile, 2)
if self.played_information.get(currentFile):
position = self.xbmcplayer.getTime()
self.played_information[currentFile]['currentPosition'] = position
self.reportPlayback()
def onPlayBackStopped( self ):
# Will be called when user stops xbmc playing a file
self.logMsg("ONPLAYBACK_STOPPED", 2)
self.stopAll()
def onPlayBackEnded( self ):
# Will be called when xbmc stops playing a file
self.logMsg("ONPLAYBACK_ENDED", 2)
self.stopAll()
def stopAll(self):
if not self.played_information:
return
self.logMsg("Played_information: %s" % self.played_information, 1)
# Process each items
for item in self.played_information:
data = self.played_information.get(item)
if data:
self.logMsg("Item path: %s" % item, 2)
self.logMsg("Item data: %s" % data, 2)
runtime = data['runtime']
currentPosition = data['currentPosition']
itemId = data['item_id']
refresh_id = data['refresh_id']
currentFile = data['currentfile']
type = data['Type']
playMethod = data['playmethod']
if currentPosition and runtime:
percentComplete = (currentPosition * 10000000) / int(runtime)
markPlayedAt = float(utils.settings('markPlayed')) / 100
self.logMsg("Percent complete: %s Mark played at: %s" % (percentComplete, markPlayedAt), 1)
# Prevent manually mark as watched in Kodi monitor > WriteKodiVideoDB().UpdatePlaycountFromKodi()
utils.window('SkipWatched%s' % itemId, "true")
self.stopPlayback(data)
offerDelete = utils.settings('offerDelete') == "true"
offerTypeDelete = False
if type == "Episode" and utils.settings('offerDeleteTV') == "true":
offerTypeDelete = True
elif type == "Movie" and utils.settings('offerDeleteMovies') == "true":
offerTypeDelete = True
if percentComplete >= markPlayedAt and offerDelete and offerTypeDelete:
# Make the bigger setting be able to disable option easily.
self.logMsg("Offering deletion for: %s." % itemId, 1)
return_value = xbmcgui.Dialog().yesno("Offer Delete", "Delete %s" % currentFile.split("/")[-1], "on Emby Server?")
if return_value:
# Delete Kodi entry before Emby
listItem = [itemId]
LibrarySync().removefromDB(listItem, True)
# Stop transcoding
if playMethod == "Transcode":
self.logMsg("Transcoding for %s terminated." % itemId, 1)
deviceId = self.clientInfo.getMachineId()
url = "{server}/mediabrowser/Videos/ActiveEncodings?DeviceId=%s" % deviceId
self.doUtils.downloadUrl(url, type="DELETE")
self.played_information.clear()
def stopPlayback(self, data):
self.logMsg("stopPlayback called", 2)
itemId = data['item_id']
currentPosition = data['currentPosition']
positionTicks = int(currentPosition * 10000000)
url = "{server}/mediabrowser/Sessions/Playing/Stopped"
postdata = {
'ItemId': itemId,
'MediaSourceId': itemId,
'PositionTicks': positionTicks
}
self.doUtils.downloadUrl(url, postBody=postdata, type="POST")

View file

@ -1,460 +0,0 @@
#################################################################################################
# UserClient thread
#################################################################################################
import xbmc
import xbmcgui
import xbmcaddon
import xbmcvfs
import threading
import hashlib
import json as json
import KodiMonitor
import Utils as utils
from ClientInformation import ClientInformation
from DownloadUtils import DownloadUtils
from Player import Player
from API import API
class UserClient(threading.Thread):
# Borg - multiple instances, shared state
_shared_state = {}
clientInfo = ClientInformation()
doUtils = DownloadUtils()
KodiMonitor = KodiMonitor.Kodi_Monitor()
addonName = clientInfo.getAddonName()
addon = xbmcaddon.Addon()
WINDOW = xbmcgui.Window(10000)
stopClient = False
logLevel = int(addon.getSetting('logLevel'))
auth = True
retry = 0
currUser = None
currUserId = None
currServer = None
currToken = None
HasAccess = True
AdditionalUser = []
def __init__(self, *args):
self.__dict__ = self._shared_state
threading.Thread.__init__(self, *args)
def logMsg(self, msg, lvl=1):
className = self.__class__.__name__
utils.logMsg("%s %s" % (self.addonName, className), str(msg), int(lvl))
def getUsername(self):
username = utils.settings('username')
if (username == ""):
self.logMsg("No username saved.", 2)
return ""
return username
def getAdditionalUsers(self):
additionalUsers = utils.settings('additionalUsers')
if additionalUsers:
self.AdditionalUser = additionalUsers.split(',')
def getLogLevel(self):
try:
logLevel = int(utils.settings('logLevel'))
except:
logLevel = 0
return logLevel
def getUserId(self):
username = self.getUsername()
w_userId = self.WINDOW.getProperty('userId%s' % username)
s_userId = utils.settings('userId%s' % username)
# Verify the window property
if (w_userId != ""):
self.logMsg("Returning userId from WINDOW for username: %s UserId: %s" % (username, w_userId), 2)
return w_userId
# Verify the settings
elif (s_userId != ""):
self.logMsg("Returning userId from SETTINGS for username: %s userId: %s" % (username, s_userId), 2)
return s_userId
# No userId found
else:
self.logMsg("No userId saved for username: %s." % username)
return
def getServer(self, prefix=True):
alternate = utils.settings('altip') == "true"
# For https support
HTTPS = utils.settings('https')
host = utils.settings('ipaddress')
port = utils.settings('port')
# Alternate host
if alternate:
HTTPS = utils.settings('secondhttps')
host = utils.settings('secondipaddress')
port = utils.settings('secondport')
server = host + ":" + port
if host == "":
self.logMsg("No server information saved.", 2)
return ""
# If https is true
if prefix and (HTTPS == "true"):
server = "https://%s" % server
return server
# If https is false
elif prefix and (HTTPS == "false"):
server = "http://%s" % server
return server
# If only the host:port is required
elif (prefix == False):
return server
def getToken(self):
username = self.getUsername()
w_token = self.WINDOW.getProperty('accessToken%s' % username)
s_token = utils.settings('accessToken')
# Verify the window property
if (w_token != ""):
self.logMsg("Returning accessToken from WINDOW for username: %s accessToken: %s" % (username, w_token), 2)
return w_token
# Verify the settings
elif (s_token != ""):
self.logMsg("Returning accessToken from SETTINGS for username: %s accessToken: %s" % (username, s_token), 2)
self.WINDOW.setProperty('accessToken%s' % username, s_token)
return s_token
else:
self.logMsg("No token found.")
return ""
def getSSLverify(self):
# Verify host certificate
s_sslverify = utils.settings('sslverify')
if utils.settings('altip') == "true":
s_sslverify = utils.settings('secondsslverify')
if s_sslverify == "true":
return True
else:
return False
def getSSL(self):
# Client side certificate
s_cert = utils.settings('sslcert')
if utils.settings('altip') == "true":
s_cert = utils.settings('secondsslcert')
if s_cert == "None":
return None
else:
return s_cert
def setUserPref(self):
player = Player()
server = self.getServer()
userId = self.getUserId()
url = "{server}/mediabrowser/Users/{UserId}?format=json"
result = self.doUtils.downloadUrl(url)
# Set user image for skin display
self.WINDOW.setProperty("EmbyUserImage",API().getUserArtwork(result,"Primary"))
# Load the resume point from Emby and set as setting
url = "{server}/mediabrowser/System/Configuration?format=json"
result = self.doUtils.downloadUrl(url)
utils.settings('markPlayed', value=str(result['MaxResumePct']))
return True
def getPublicUsers(self):
server = self.getServer()
# Get public Users
url = "%s/mediabrowser/Users/Public?format=json" % server
result = self.doUtils.downloadUrl(url, authenticate=False)
users = []
if (result != ""):
users = result
else:
# Server connection failed
return False
return users
def hasAccess(self):
url = "{server}/mediabrowser/Users"
result = self.doUtils.downloadUrl(url)
if result is False:
# Access is restricted
self.logMsg("Access is restricted.")
self.HasAccess = False
return
elif self.WINDOW.getProperty('Server_online') != "true":
# Server connection failed
return
if self.WINDOW.getProperty("Server_status") == "restricted":
self.logMsg("Access is granted.")
self.HasAccess = True
self.WINDOW.setProperty("Server_status", "")
xbmcgui.Dialog().notification("Emby server", "Access is enabled.")
return
def loadCurrUser(self, authenticated=False):
WINDOW = self.WINDOW
doUtils = self.doUtils
username = self.getUsername()
# Only to be used if token exists
self.currUserId = self.getUserId()
self.currServer = self.getServer()
self.currToken = self.getToken()
self.ssl = self.getSSLverify()
self.sslcert = self.getSSL()
# Test the validity of current token
if authenticated == False:
url = "%s/mediabrowser/Users/%s" % (self.currServer, self.currUserId)
WINDOW.setProperty("currUser", username)
WINDOW.setProperty("accessToken%s" % username, self.currToken)
result = doUtils.downloadUrl(url)
if result == 401:
# Token is no longer valid
self.resetClient()
return False
# Set to windows property
WINDOW.setProperty("currUser", username)
WINDOW.setProperty("accessToken%s" % username, self.currToken)
WINDOW.setProperty("server%s" % username, self.currServer)
WINDOW.setProperty("server_%s" % username, self.getServer(prefix=False))
WINDOW.setProperty("userId%s" % username, self.currUserId)
# Set DownloadUtils values
doUtils.setUsername(username)
doUtils.setUserId(self.currUserId)
doUtils.setServer(self.currServer)
doUtils.setToken(self.currToken)
doUtils.setSSL(self.ssl, self.sslcert)
# parental control - let's verify if access is restricted
self.hasAccess()
# Start DownloadUtils session
doUtils.startSession()
self.getAdditionalUsers()
self.currUser = username
# Set user preferences in settings
self.setUserPref()
def authenticate(self):
WINDOW = self.WINDOW
addon = self.addon
username = self.getUsername()
server = self.getServer()
addondir = xbmc.translatePath(self.addon.getAddonInfo('profile')).decode('utf-8')
hasSettings = xbmcvfs.exists("%ssettings.xml" % addondir)
# If there's no settings.xml
if (hasSettings == 0):
self.logMsg("No settings.xml found.")
self.auth = False
return
# If no user information
if (server == "") or (username == ""):
self.logMsg("Missing server information.")
self.auth = False
return
# If there's a token
if (self.getToken() != ""):
result = self.loadCurrUser()
if result == False:
pass
else:
self.logMsg("Current user: %s" % self.currUser, 0)
self.logMsg("Current userId: %s" % self.currUserId, 0)
self.logMsg("Current accessToken: %s" % self.currToken, 0)
return
users = self.getPublicUsers()
password = ""
# Find user in list
for user in users:
name = user[u'Name']
userHasPassword = False
if (unicode(username, 'utf-8') in name):
# Verify if user has a password
if (user.get("HasPassword") == True):
userHasPassword = True
# If user has password
if (userHasPassword):
password = xbmcgui.Dialog().input("Enter password for user: %s" % username, option=xbmcgui.ALPHANUM_HIDE_INPUT)
# If password dialog is cancelled
if (password == ""):
self.logMsg("No password entered.", 0)
self.WINDOW.setProperty("Server_status", "Stop")
self.auth = False
return
break
else:
# Manual login, user is hidden
password = xbmcgui.Dialog().input("Enter password for user: %s" % username, option=xbmcgui.ALPHANUM_HIDE_INPUT)
sha1 = hashlib.sha1(password)
sha1 = sha1.hexdigest()
# Authenticate username and password
url = "%s/mediabrowser/Users/AuthenticateByName?format=json" % server
data = {'username': username, 'password': sha1}
self.logMsg(data, 2)
result = self.doUtils.downloadUrl(url, postBody=data, type="POST", authenticate=False)
accessToken = None
try:
self.logMsg("Auth_Reponse: %s" % result, 1)
accessToken = result[u'AccessToken']
except:
pass
if (result != None and accessToken != None):
self.currUser = username
xbmcgui.Dialog().notification("Emby server", "Welcome %s!" % self.currUser)
userId = result[u'User'][u'Id']
utils.settings("accessToken", accessToken)
utils.settings("userId%s" % username, userId)
self.logMsg("User Authenticated: %s" % accessToken)
self.loadCurrUser(authenticated=True)
self.WINDOW.setProperty("Server_status", "")
self.retry = 0
return
else:
self.logMsg("User authentication failed.")
utils.settings("accessToken", "")
utils.settings("userId%s" % username, "")
xbmcgui.Dialog().ok("Error connecting", "Invalid username or password.")
# Give two attempts at entering password
self.retry += 1
if self.retry == 2:
self.logMsg("Too many retries. You can retry by selecting the option in the addon settings.")
self.WINDOW.setProperty("Server_status", "Stop")
xbmcgui.Dialog().ok("Error connecting", "Failed to authenticate too many times. You can retry by selecting the option in the addon settings.")
self.auth = False
return
def resetClient(self):
username = self.getUsername()
self.logMsg("Reset UserClient authentication.", 1)
if (self.currToken != None):
# In case of 401, removed saved token
utils.settings("accessToken", "")
self.WINDOW.setProperty("accessToken%s" % username, "")
self.currToken = None
self.logMsg("User token has been removed.", 1)
self.auth = True
self.currUser = None
return
def run(self):
self.logMsg("|---- Starting UserClient ----|", 0)
while not self.KodiMonitor.abortRequested():
# Verify the log level
currLogLevel = self.getLogLevel()
if self.logLevel != currLogLevel:
# Set new log level
self.logLevel = currLogLevel
self.logMsg("New Log Level: %s" % currLogLevel, 0)
self.WINDOW.setProperty('getLogLevel', str(currLogLevel))
if (self.WINDOW.getProperty("Server_status") != ""):
status = self.WINDOW.getProperty("Server_status")
if status == "restricted":
# Parental control is restricting access
self.HasAccess = False
elif status == "401":
self.WINDOW.setProperty("Server_status", "Auth")
# Revoked token
self.resetClient()
if self.auth and (self.currUser == None):
status = self.WINDOW.getProperty("Server_status")
if (status == "") or (status == "Auth"):
self.auth = False
self.authenticate()
if (self.auth == False) and (self.currUser == None):
# Only if there's information found to login
server = self.getServer()
username = self.getUsername()
status = self.WINDOW.getProperty("Server_status")
# If user didn't enter a password when prompted
if status == "Stop":
pass
elif (server != "") and (username != ""):
self.logMsg("Server found: %s" % server)
self.logMsg("Username found: %s" % username)
self.auth = True
# If stopping the client didn't work
if self.stopClient == True:
break
if self.KodiMonitor.waitForAbort(1):
# Abort was requested while waiting. We should exit
break
self.doUtils.stopSession()
self.logMsg("|---- UserClient Stopped ----|", 0)
def stopClient(self):
# As last resort
self.stopClient = True

View file

@ -1,425 +0,0 @@
#################################################################################################
# utils
#################################################################################################
import xbmc
import xbmcgui
import xbmcaddon
import xbmcvfs
import json
import os
import cProfile
import pstats
import time
import inspect
import sqlite3
import string
import unicodedata
import xml.etree.ElementTree as etree
from API import API
from PlayUtils import PlayUtils
from DownloadUtils import DownloadUtils
downloadUtils = DownloadUtils()
addon = xbmcaddon.Addon()
language = addon.getLocalizedString
def logMsg(title, msg, level = 1):
WINDOW = xbmcgui.Window(10000)
# Get the logLevel set in UserClient
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))
except UnicodeEncodeError:
xbmc.log(title + " -> " + inspect.stack()[1][3] + " : " + str(msg.encode('utf-8')))
else:
try:
xbmc.log(title + " -> " + str(msg))
except UnicodeEncodeError:
xbmc.log(title + " -> " + str(msg.encode('utf-8')))
def convertEncoding(data):
#nasty hack to make sure we have a unicode string
try:
return data.decode('utf-8')
except:
return data
def KodiSQL(type="video"):
if type == "music":
dbPath = getKodiMusicDBPath()
elif type == "texture":
dbPath = xbmc.translatePath("special://database/Textures13.db").decode('utf-8')
else:
dbPath = getKodiVideoDBPath()
connection = sqlite3.connect(dbPath)
return connection
def getKodiVideoDBPath():
kodibuild = xbmc.getInfoLabel('System.BuildVersion')[:2]
dbVersion = {
"13": 78, # Gotham
"14": 90, # Helix
"15": 93, # Isengard
"16": 99 # Jarvis
}
dbPath = xbmc.translatePath(
"special://database/MyVideos%s.db"
% dbVersion.get(kodibuild, "")).decode('utf-8')
return dbPath
def getKodiMusicDBPath():
kodibuild = xbmc.getInfoLabel('System.BuildVersion')[:2]
dbVersion = {
"13": 46, # Gotham
"14": 48, # Helix
"15": 52, # Isengard
"16": 56 # Jarvis
}
dbPath = xbmc.translatePath(
"special://database/MyMusic%s.db"
% 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 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'))
fileTimeStamp = time.strftime("%Y-%m-%d %H-%M-%S")
tabFileNamepath = os.path.join(addondir, "profiles")
tabFileName = os.path.join(addondir, "profiles" , profileName + "_profile_(" + fileTimeStamp + ").tab")
if not xbmcvfs.exists(tabFileNamepath):
xbmcvfs.mkdir(tabFileNamepath)
f = open(tabFileName, 'wb')
f.write("NumbCalls\tTotalTime\tCumulativeTime\tFunctionName\tFileName\r\n")
for (key, value) in ps.stats.items():
(filename, count, func_name) = key
(ccalls, ncalls, total_time, cumulative_time, callers) = value
try:
f.write(str(ncalls) + "\t" + "{:10.4f}".format(total_time) + "\t" + "{:10.4f}".format(cumulative_time) + "\t" + func_name + "\t" + filename + "\r\n")
except ValueError:
f.write(str(ncalls) + "\t" + "{0}".format(total_time) + "\t" + "{0}".format(cumulative_time) + "\t" + func_name + "\t" + filename + "\r\n")
f.close()
def indent(elem, level=0):
# Prettify xml trees
i = "\n" + level*" "
if len(elem):
if not elem.text or not elem.text.strip():
elem.text = i + " "
if not elem.tail or not elem.tail.strip():
elem.tail = i
for elem in elem:
indent(elem, level+1)
if not elem.tail or not elem.tail.strip():
elem.tail = i
else:
if level and (not elem.tail or not elem.tail.strip()):
elem.tail = i
def createSources():
# To make Master lock compatible
path = xbmc.translatePath("special://profile/").decode("utf-8")
xmlpath = "%ssources.xml" % path
if xbmcvfs.exists(xmlpath):
# Modify the existing file
try:
xmlparse = etree.parse(xmlpath)
except:
root = etree.Element('sources')
else:
root = xmlparse.getroot()
video = root.find('video')
if video is None:
video = etree.SubElement(root, 'video')
else:
# We need to create the file
root = etree.Element('sources')
video = etree.SubElement(root, 'video')
# Add elements
etree.SubElement(video, 'default', attrib={'pathversion': "1"})
# First dummy source
source_one = etree.SubElement(video, 'source')
etree.SubElement(source_one, 'name').text = "Emby"
etree.SubElement(source_one, 'path', attrib={'pathversion': "1"}).text = (
"smb://embydummy/dummypath1/"
)
etree.SubElement(source_one, 'allowsharing').text = "true"
# Second dummy source
source_two = etree.SubElement(video, 'source')
etree.SubElement(source_two, 'name').text = "Emby"
etree.SubElement(source_two, 'path', attrib={'pathversion': "1"}).text = (
"smb://embydummy/dummypath2/"
)
etree.SubElement(source_two, 'allowsharing').text = "true"
try:
indent(root)
except:pass
etree.ElementTree(root).write(xmlpath)
def pathsubstitution(add=True):
path = xbmc.translatePath('special://userdata').decode('utf-8')
xmlpath = "%sadvancedsettings.xml" % path
xmlpathexists = xbmcvfs.exists(xmlpath)
# original address
originalServer = settings('ipaddress')
originalPort = settings('port')
originalHttp = settings('https') == "true"
if originalHttp:
originalHttp = "https"
else:
originalHttp = "http"
# Process add or deletion
if add:
# second address
secondServer = settings('secondipaddress')
secondPort = settings('secondport')
secondHttp = settings('secondhttps') == "true"
if secondHttp:
secondHttp = "https"
else:
secondHttp = "http"
logMsg("EMBY", "Original address: %s://%s:%s, alternate is: %s://%s:%s" % (originalHttp, originalServer, originalPort, secondHttp, secondServer, secondPort), 1)
if xmlpathexists:
# we need to modify the file.
try:
xmlparse = etree.parse(xmlpath)
except: # Document is blank
root = etree.Element('advancedsettings')
else:
root = xmlparse.getroot()
pathsubs = root.find('pathsubstitution')
if pathsubs is None:
pathsubs = etree.SubElement(root, 'pathsubstitution')
else:
# we need to create the file.
root = etree.Element('advancedsettings')
pathsubs = etree.SubElement(root, 'pathsubstitution')
substitute = etree.SubElement(pathsubs, 'substitute')
# From original address
etree.SubElement(substitute, 'from').text = "%s://%s:%s" % (originalHttp, originalServer, originalPort)
# To secondary address
etree.SubElement(substitute, 'to').text = "%s://%s:%s" % (secondHttp, secondServer, secondPort)
etree.ElementTree(root).write(xmlpath)
settings('pathsub', "true")
else: # delete the path substitution, we don't need it anymore.
logMsg("EMBY", "Alternate address is disabled, removing path substitution for: %s://%s:%s" % (originalHttp, originalServer, originalPort), 1)
xmlparse = etree.parse(xmlpath)
root = xmlparse.getroot()
iterator = root.getiterator("pathsubstitution")
for substitutes in iterator:
for substitute in substitutes:
frominsert = substitute.find(".//from").text == "%s://%s:%s" % (originalHttp, originalServer, originalPort)
if frominsert:
# Found a match, in case there's more than one substitution.
substitutes.remove(substitute)
etree.ElementTree(root).write(xmlpath)
settings('pathsub', "false")
def settings(setting, value = None):
# Get or add addon setting
addon = xbmcaddon.Addon()
if value:
addon.setSetting(setting, value)
else:
return addon.getSetting(setting)
def window(property, value = None, clear = False):
# Get or set window property
WINDOW = xbmcgui.Window(10000)
if clear:
WINDOW.clearProperty(property)
elif value:
WINDOW.setProperty(property, value)
else:
return WINDOW.getProperty(property)
def normalize_string(text):
# For theme media, do not modify unless
# modified in TV Tunes
text = text.replace(":", "")
text = text.replace("/", "-")
text = text.replace("\\", "-")
text = text.replace("<", "")
text = text.replace(">", "")
text = text.replace("*", "")
text = text.replace("?", "")
text = text.replace('|', "")
text = text.strip()
# Remove dots from the last character as windows can not have directories
# with dots at the end
text = text.rstrip('.')
text = unicodedata.normalize('NFKD', unicode(text, 'utf-8')).encode('ascii', 'ignore')
return text
def normalize_nodes(text):
# For video nodes
text = text.replace(":", "")
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.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 reloadProfile():
# Useful to reload the add-on without restarting Kodi.
profile = xbmc.getInfoLabel('System.ProfileName')
xbmc.executebuiltin("LoadProfile(%s)" % profile)
def reset():
WINDOW = xbmcgui.Window( 10000 )
return_value = xbmcgui.Dialog().yesno("Warning", "Are you sure you want to reset your local Kodi database?")
if return_value == 0:
return
# Because the settings dialog could be open
# it seems to override settings so we need to close it before we reset settings.
xbmc.executebuiltin("Dialog.Close(all,true)")
#cleanup video nodes
import shutil
path = "special://profile/library/video/"
if xbmcvfs.exists(path):
allDirs, allFiles = xbmcvfs.listdir(path)
for dir in allDirs:
if dir.startswith("Emby "):
shutil.rmtree(xbmc.translatePath("special://profile/library/video/" + dir))
for file in allFiles:
if file.startswith("emby"):
xbmcvfs.delete(path + file)
settings('SyncInstallRunDone', "false")
# Ask if user information should be deleted too.
return_user = xbmcgui.Dialog().yesno("Warning", "Reset all Emby Addon settings?")
if return_user == 1:
WINDOW.setProperty('deletesettings', "true")
addon = xbmcaddon.Addon()
addondir = xbmc.translatePath(addon.getAddonInfo('profile')).decode('utf-8')
dataPath = "%ssettings.xml" % addondir
xbmcvfs.delete(dataPath)
logMsg("EMBY", "Deleting: settings.xml", 1)
# first stop any db sync
WINDOW.setProperty("SyncDatabaseShouldStop", "true")
count = 0
while(WINDOW.getProperty("SyncDatabaseRunning") == "true"):
xbmc.log("Sync Running, will wait : " + str(count))
count += 1
if(count > 10):
dialog = xbmcgui.Dialog()
dialog.ok('Warning', 'Could not stop DB sync, you should try again.')
return
xbmc.sleep(1000)
# delete video db table data
print "Doing Video DB Reset"
connection = KodiSQL("video")
cursor = connection.cursor( )
cursor.execute('SELECT tbl_name FROM sqlite_master WHERE type="table"')
rows = cursor.fetchall()
for row in rows:
tableName = row[0]
if(tableName != "version"):
cursor.execute("DELETE FROM " + tableName)
cursor.execute("DROP TABLE IF EXISTS emby")
connection.commit()
cursor.close()
if settings('enableMusicSync') == "true":
# delete video db table data
print "Doing Music DB Reset"
connection = KodiSQL("music")
cursor = connection.cursor( )
cursor.execute('SELECT tbl_name FROM sqlite_master WHERE type="table"')
rows = cursor.fetchall()
for row in rows:
tableName = row[0]
if(tableName != "version"):
cursor.execute("DELETE FROM " + tableName)
cursor.execute("DROP TABLE IF EXISTS emby")
connection.commit()
cursor.close()
# reset the install run flag
#settings('SyncInstallRunDone', "false")
#WINDOW.setProperty("SyncInstallRunDone", "false")
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")

View file

@ -1,466 +0,0 @@
#################################################################################################
# VideoNodes - utils to create video nodes listings in kodi for the emby addon
#################################################################################################
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
from ReadEmbyDB import ReadEmbyDB
WINDOW = xbmcgui.Window(10000)
addonSettings = xbmcaddon.Addon()
language = addonSettings.getLocalizedString
class VideoNodes():
def buildVideoNodeForView(self, tagname, type, windowPropId):
#this method will build a video node for a particular Emby view (= tag in kodi)
#we set some window props here to for easy future reference and to be used in skins (for easy access only)
tagname_normalized = utils.normalize_nodes(tagname.encode('utf-8'))
libraryPath = xbmc.translatePath("special://profile/library/video/Emby - %s/" %tagname_normalized)
kodiVersion = 14
if xbmc.getInfoLabel("System.BuildVersion").startswith("15") or xbmc.getInfoLabel("System.BuildVersion").startswith("16"):
kodiVersion = 15
#create tag node - index
xbmcvfs.mkdir(libraryPath)
nodefile = os.path.join(libraryPath, "index.xml")
root = etree.Element("node", {"order":"0"})
etree.SubElement(root, "label").text = tagname
etree.SubElement(root, "icon").text = "special://home/addons/plugin.video.emby/icon.png"
path = "library://video/Emby - %s/" %tagname_normalized
WINDOW.setProperty("Emby.nodes.%s.index" %str(windowPropId),path)
try:
etree.ElementTree(root).write(nodefile, xml_declaration=True)
except:
etree.ElementTree(root).write(nodefile)
#create tag node - all items
nodefile = os.path.join(libraryPath, tagname_normalized + "_all.xml")
root = etree.Element("node", {"order":"1", "type":"filter"})
etree.SubElement(root, "label").text = tagname
etree.SubElement(root, "match").text = "all"
etree.SubElement(root, "content").text = type
etree.SubElement(root, "icon").text = "special://home/addons/plugin.video.emby/icon.png"
etree.SubElement(root, "order", {"direction":"ascending"}).text = "sorttitle"
Rule = etree.SubElement(root, "rule", {"field":"tag","operator":"is"})
WINDOW.setProperty("Emby.nodes.%s.title" %str(windowPropId),tagname)
path = "library://video/Emby - %s/%s_all.xml"%(tagname_normalized,tagname_normalized)
WINDOW.setProperty("Emby.nodes.%s.path" %str(windowPropId),"ActivateWindow(Video,%s,return)"%path)
WINDOW.setProperty("Emby.nodes.%s.content" %str(windowPropId),path)
WINDOW.setProperty("Emby.nodes.%s.type" %str(windowPropId),type)
etree.SubElement(Rule, "value").text = tagname
try:
etree.ElementTree(root).write(nodefile, xml_declaration=True)
except:
etree.ElementTree(root).write(nodefile)
#create tag node - recent items
nodefile = os.path.join(libraryPath, tagname_normalized + "_recent.xml")
root = etree.Element("node", {"order":"2", "type":"filter"})
if type == "tvshows":
label = language(30170)
else:
label = language(30174)
etree.SubElement(root, "label").text = label
etree.SubElement(root, "match").text = "all"
etree.SubElement(root, "content").text = type
etree.SubElement(root, "icon").text = "special://home/addons/plugin.video.emby/icon.png"
Rule = etree.SubElement(root, "rule", {"field":"tag","operator":"is"})
etree.SubElement(Rule, "value").text = tagname
etree.SubElement(root, "order", {"direction":"descending"}).text = "dateadded"
#set limit to 25 --> currently hardcoded --> TODO: add a setting for this ?
etree.SubElement(root, "limit").text = "25"
#exclude watched items --> currently hardcoded --> TODO: add a setting for this ?
Rule2 = etree.SubElement(root, "rule", {"field":"playcount","operator":"is"})
etree.SubElement(Rule2, "value").text = "0"
WINDOW.setProperty("Emby.nodes.%s.recent.title" %str(windowPropId),label)
path = "library://video/Emby - %s/%s_recent.xml"%(tagname_normalized,tagname_normalized)
WINDOW.setProperty("Emby.nodes.%s.recent.path" %str(windowPropId),"ActivateWindow(Video,%s,return)"%path)
WINDOW.setProperty("Emby.nodes.%s.recent.content" %str(windowPropId),path)
try:
etree.ElementTree(root).write(nodefile, xml_declaration=True)
except:
etree.ElementTree(root).write(nodefile)
#create tag node - inprogress items
nodefile = os.path.join(libraryPath, tagname_normalized + "_progress.xml")
root = etree.Element("node", {"order":"3", "type":"filter"})
if type == "tvshows":
label = language(30171)
else:
label = language(30177)
etree.SubElement(root, "label").text = label
etree.SubElement(root, "match").text = "all"
etree.SubElement(root, "content").text = type
etree.SubElement(root, "icon").text = "special://home/addons/plugin.video.emby/icon.png"
Rule = etree.SubElement(root, "rule", {"field":"tag","operator":"is"})
etree.SubElement(Rule, "value").text = tagname
#set limit to 25 --> currently hardcoded --> TODO: add a setting for this ?
etree.SubElement(root, "limit").text = "25"
Rule2 = etree.SubElement(root, "rule", {"field":"inprogress","operator":"true"})
WINDOW.setProperty("Emby.nodes.%s.inprogress.title" %str(windowPropId),label)
path = "library://video/Emby - %s/%s_progress.xml"%(tagname_normalized,tagname_normalized)
WINDOW.setProperty("Emby.nodes.%s.inprogress.path" %str(windowPropId),"ActivateWindow(Video,%s,return)"%path)
WINDOW.setProperty("Emby.nodes.%s.inprogress.content" %str(windowPropId),path)
try:
etree.ElementTree(root).write(nodefile, xml_declaration=True)
except:
etree.ElementTree(root).write(nodefile)
#some movies-only nodes
if type == "movies":
#unwatched movies
nodefile = os.path.join(libraryPath, tagname_normalized + "_unwatched.xml")
root = etree.Element("node", {"order":"4", "type":"filter"})
label = language(30189)
etree.SubElement(root, "label").text = label
etree.SubElement(root, "match").text = "all"
etree.SubElement(root, "content").text = "movies"
etree.SubElement(root, "icon").text = "special://home/addons/plugin.video.emby/icon.png"
Rule = etree.SubElement(root, "rule", {"field":"tag","operator":"is"})
etree.SubElement(Rule, "value").text = tagname
Rule = etree.SubElement(root, "rule", {"field":"playcount","operator":"is"})
etree.SubElement(Rule, "value").text = "0"
etree.SubElement(root, "order", {"direction":"ascending"}).text = "sorttitle"
Rule2 = etree.SubElement(root, "rule", {"field":"playcount","operator":"is"})
etree.SubElement(Rule2, "value").text = "0"
WINDOW.setProperty("Emby.nodes.%s.unwatched.title" %str(windowPropId),label)
path = "library://video/Emby - %s/%s_unwatched.xml"%(tagname_normalized,tagname_normalized)
WINDOW.setProperty("Emby.nodes.%s.unwatched.path" %str(windowPropId),"ActivateWindow(Video,%s,return)"%path)
WINDOW.setProperty("Emby.nodes.%s.unwatched.content" %str(windowPropId),path)
try:
etree.ElementTree(root).write(nodefile, xml_declaration=True)
except:
etree.ElementTree(root).write(nodefile)
#sets
nodefile = os.path.join(libraryPath, tagname_normalized + "_sets.xml")
root = etree.Element("node", {"order":"9", "type":"filter"})
label = xbmc.getLocalizedString(20434)
etree.SubElement(root, "label").text = label
etree.SubElement(root, "match").text = "all"
etree.SubElement(root, "group").text = "sets"
etree.SubElement(root, "content").text = type
etree.SubElement(root, "icon").text = "special://home/addons/plugin.video.emby/icon.png"
etree.SubElement(root, "order", {"direction":"ascending"}).text = "sorttitle"
Rule = etree.SubElement(root, "rule", {"field":"tag","operator":"is"})
etree.SubElement(Rule, "value").text = tagname
WINDOW.setProperty("Emby.nodes.%s.sets.title" %str(windowPropId),label)
path = "library://video/Emby - %s/%s_sets.xml"%(tagname_normalized,tagname_normalized)
WINDOW.setProperty("Emby.nodes.%s.sets.path" %str(windowPropId),"ActivateWindow(Video,%s,return)"%path)
WINDOW.setProperty("Emby.nodes.%s.sets.content" %str(windowPropId),path)
try:
etree.ElementTree(root).write(nodefile, xml_declaration=True)
except:
etree.ElementTree(root).write(nodefile)
#create tag node - genres
nodefile = os.path.join(libraryPath, tagname_normalized + "_genres.xml")
root = etree.Element("node", {"order":"9", "type":"filter"})
label = xbmc.getLocalizedString(135)
etree.SubElement(root, "label").text = label
etree.SubElement(root, "match").text = "all"
etree.SubElement(root, "group").text = "genres"
etree.SubElement(root, "content").text = type
etree.SubElement(root, "icon").text = "special://home/addons/plugin.video.emby/icon.png"
etree.SubElement(root, "order", {"direction":"ascending"}).text = "sorttitle"
Rule = etree.SubElement(root, "rule", {"field":"tag","operator":"is"})
etree.SubElement(Rule, "value").text = tagname
WINDOW.setProperty("Emby.nodes.%s.genres.title" %str(windowPropId),label)
path = "library://video/Emby - %s/%s_genres.xml"%(tagname_normalized,tagname_normalized)
WINDOW.setProperty("Emby.nodes.%s.genres.path" %str(windowPropId),"ActivateWindow(Video,%s,return)"%path)
WINDOW.setProperty("Emby.nodes.%s.genres.content" %str(windowPropId),path)
try:
etree.ElementTree(root).write(nodefile, xml_declaration=True)
except:
etree.ElementTree(root).write(nodefile)
#create tag node - random items
nodefile = os.path.join(libraryPath, tagname_normalized + "_random.xml")
root = etree.Element("node", {"order":"10", "type":"filter"})
label = language(30229)
etree.SubElement(root, "label").text = label
etree.SubElement(root, "match").text = "all"
etree.SubElement(root, "content").text = type
etree.SubElement(root, "icon").text = "special://home/addons/plugin.video.emby/icon.png"
Rule = etree.SubElement(root, "rule", {"field":"tag","operator":"is"})
etree.SubElement(Rule, "value").text = tagname
#set limit to 25 --> currently hardcoded --> TODO: add a setting for this ?
etree.SubElement(root, "limit").text = "25"
etree.SubElement(root, "order", {"direction":"ascending"}).text = "random"
WINDOW.setProperty("Emby.nodes.%s.random.title" %str(windowPropId),label)
path = "library://video/Emby - %s/%s_random.xml"%(tagname_normalized,tagname_normalized)
WINDOW.setProperty("Emby.nodes.%s.random.path" %str(windowPropId),"ActivateWindow(Video,%s,return)"%path)
WINDOW.setProperty("Emby.nodes.%s.random.content" %str(windowPropId),path)
try:
etree.ElementTree(root).write(nodefile, xml_declaration=True)
except:
etree.ElementTree(root).write(nodefile)
#create tag node - recommended items
nodefile = os.path.join(libraryPath, tagname_normalized + "_recommended.xml")
root = etree.Element("node", {"order":"10", "type":"filter"})
label = language(30230)
etree.SubElement(root, "label").text = label
etree.SubElement(root, "match").text = "all"
etree.SubElement(root, "content").text = type
etree.SubElement(root, "icon").text = "special://home/addons/plugin.video.emby/icon.png"
Rule = etree.SubElement(root, "rule", {"field":"tag","operator":"is"})
etree.SubElement(Rule, "value").text = tagname
Rule2 = etree.SubElement(root, "rule", {"field":"playcount","operator":"is"})
etree.SubElement(Rule2, "value").text = "0"
Rule3 = etree.SubElement(root, "rule", {"field":"rating","operator":"greaterthan"})
etree.SubElement(Rule3, "value").text = "7"
#set limit to 25 --> currently hardcoded --> TODO: add a setting for this ?
etree.SubElement(root, "limit").text = "25"
etree.SubElement(root, "order", {"direction":"descending"}).text = "rating"
WINDOW.setProperty("Emby.nodes.%s.random.title" %str(windowPropId),label)
path = "library://video/Emby - %s/%s_recommended.xml"%(tagname_normalized,tagname_normalized)
WINDOW.setProperty("Emby.nodes.%s.recommended.path" %str(windowPropId),"ActivateWindow(Video,%s,return)"%path)
WINDOW.setProperty("Emby.nodes.%s.recommended.content" %str(windowPropId),path)
try:
etree.ElementTree(root).write(nodefile, xml_declaration=True)
except:
etree.ElementTree(root).write(nodefile)
#### TAGS ONLY FOR TV SHOWS COLLECTIONS ####
if type == "tvshows":
#as from kodi isengard you can use tags for episodes to filter
#for below isengard we still use the plugin's entrypoint to build a listing
if kodiVersion == 15:
#create tag node - recent episodes
nodefile = os.path.join(libraryPath, tagname_normalized + "_recent_episodes.xml")
root = etree.Element("node", {"order":"3", "type":"filter"})
label = language(30175)
etree.SubElement(root, "label").text = label
etree.SubElement(root, "match").text = "all"
etree.SubElement(root, "content").text = "episodes"
etree.SubElement(root, "icon").text = "special://home/addons/plugin.video.emby/icon.png"
etree.SubElement(root, "order", {"direction":"descending"}).text = "dateadded"
Rule = etree.SubElement(root, "rule", {"field":"tag","operator":"is"})
etree.SubElement(Rule, "value").text = tagname
#set limit to 25 --> currently hardcoded --> TODO: add a setting for this ?
etree.SubElement(root, "limit").text = "25"
#exclude watched items --> currently hardcoded --> TODO: add a setting for this ?
Rule2 = etree.SubElement(root, "rule", {"field":"playcount","operator":"is"})
etree.SubElement(Rule2, "value").text = "0"
WINDOW.setProperty("Emby.nodes.%s.recentepisodes.title" %str(windowPropId),label)
path = "library://video/Emby - %s/%s_recent_episodes.xml"%(tagname_normalized,tagname_normalized)
WINDOW.setProperty("Emby.nodes.%s.recentepisodes.path" %str(windowPropId),"ActivateWindow(Video,%s,return)"%path)
WINDOW.setProperty("Emby.nodes.%s.recentepisodes.content" %str(windowPropId),path)
try:
etree.ElementTree(root).write(nodefile, xml_declaration=True)
except:
etree.ElementTree(root).write(nodefile)
#create tag node - inprogress episodes
nodefile = os.path.join(libraryPath, tagname_normalized + "_progress_episodes.xml")
root = etree.Element("node", {"order":"4", "type":"filter"})
label = language(30178)
etree.SubElement(root, "label").text = label
etree.SubElement(root, "match").text = "all"
etree.SubElement(root, "content").text = "episodes"
etree.SubElement(root, "icon").text = "special://home/addons/plugin.video.emby/icon.png"
Rule = etree.SubElement(root, "rule", {"field":"tag","operator":"is"})
etree.SubElement(Rule, "value").text = tagname
#set limit to 25 --> currently hardcoded --> TODO: add a setting for this ?
etree.SubElement(root, "limit").text = "25"
Rule2 = etree.SubElement(root, "rule", {"field":"inprogress","operator":"true"})
WINDOW.setProperty("Emby.nodes.%s.inprogressepisodes.title" %str(windowPropId),label)
path = "library://video/Emby - %s/%s_progress_episodes.xml"%(tagname_normalized,tagname_normalized)
WINDOW.setProperty("Emby.nodes.%s.inprogressepisodes.path" %str(windowPropId),"ActivateWindow(Video,%s,return)"%path)
WINDOW.setProperty("Emby.nodes.%s.inprogressepisodes.content" %str(windowPropId),path)
try:
etree.ElementTree(root).write(nodefile, xml_declaration=True)
except:
etree.ElementTree(root).write(nodefile)
if kodiVersion == 14:
#create tag node - recent episodes
nodefile = os.path.join(libraryPath, tagname_normalized + "_recent_episodes.xml")
root = etree.Element("node", {"order":"4", "type":"folder"})
label = language(30175)
etree.SubElement(root, "label").text = label
etree.SubElement(root, "content").text = "episodes"
etree.SubElement(root, "icon").text = "special://home/addons/plugin.video.emby/icon.png"
path = "plugin://plugin.video.emby/?id=%s&mode=recentepisodes&limit=25" %tagname
etree.SubElement(root, "path").text = path
WINDOW.setProperty("Emby.nodes.%s.recentepisodes.title" %str(windowPropId),label)
path = "library://video/Emby - %s/%s_recent_episodes.xml"%(tagname_normalized,tagname_normalized)
WINDOW.setProperty("Emby.nodes.%s.recentepisodes.path" %str(windowPropId),"ActivateWindow(Video,%s,return)"%path)
WINDOW.setProperty("Emby.nodes.%s.recentepisodes.content" %str(windowPropId),path)
try:
etree.ElementTree(root).write(nodefile, xml_declaration=True)
except:
etree.ElementTree(root).write(nodefile)
#create tag node - inprogress items
nodefile = os.path.join(libraryPath, tagname_normalized + "_progress_episodes.xml")
root = etree.Element("node", {"order":"5", "type":"folder"})
label = language(30178)
etree.SubElement(root, "label").text = label
etree.SubElement(root, "content").text = "episodes"
etree.SubElement(root, "icon").text = "special://home/addons/plugin.video.emby/icon.png"
path = "plugin://plugin.video.emby/?id=%s&mode=inprogressepisodes&limit=25" %tagname
etree.SubElement(root, "path").text = path
WINDOW.setProperty("Emby.nodes.%s.inprogressepisodes.title" %str(windowPropId),label)
path = "library://video/Emby - %s/%s_progress_episodes.xml"%(tagname_normalized,tagname_normalized)
WINDOW.setProperty("Emby.nodes.%s.inprogressepisodes.path" %str(windowPropId),"ActivateWindow(Video,%s,return)"%path)
WINDOW.setProperty("Emby.nodes.%s.inprogressepisodes.content" %str(windowPropId),path)
try:
etree.ElementTree(root).write(nodefile, xml_declaration=True)
except:
etree.ElementTree(root).write(nodefile)
#create tag node - nextup items
#for nextup we always use the dynamic content approach with the plugin's entrypoint because it involves a custom query
nodefile = os.path.join(libraryPath, tagname_normalized + "_nextup_episodes.xml")
root = etree.Element("node", {"order":"6", "type":"folder"})
label = language(30179)
etree.SubElement(root, "label").text = label
etree.SubElement(root, "content").text = "episodes"
path = "plugin://plugin.video.emby/?id=%s&mode=nextup&limit=25" %tagname
etree.SubElement(root, "path").text = path
etree.SubElement(root, "icon").text = "special://home/addons/plugin.video.emby/icon.png"
WINDOW.setProperty("Emby.nodes.%s.nextepisodes.title" %str(windowPropId),label)
path = "library://video/Emby - %s/%s_nextup_episodes.xml"%(tagname_normalized,tagname_normalized)
WINDOW.setProperty("Emby.nodes.%s.nextepisodes.path" %str(windowPropId),"ActivateWindow(Video,%s,return)"%path)
WINDOW.setProperty("Emby.nodes.%s.nextepisodes.content" %str(windowPropId),path)
try:
etree.ElementTree(root).write(nodefile, xml_declaration=True)
except:
etree.ElementTree(root).write(nodefile)
def buildVideoNodesListing(self):
try:
# the library path doesn't exist on all systems
if not xbmcvfs.exists("special://profile/library/"):
xbmcvfs.mkdir("special://profile/library")
if not xbmcvfs.exists("special://profile/library/video/"):
#we need to copy over the default items
shutil.copytree(xbmc.translatePath("special://xbmc/system/library/video"), xbmc.translatePath("special://profile/library/video"))
#always cleanup existing Emby video nodes first because we don't want old stuff to stay in there
path = "special://profile/library/video/"
if xbmcvfs.exists(path):
allDirs, allFiles = xbmcvfs.listdir(path)
for dir in allDirs:
if dir.startswith("Emby "):
shutil.rmtree(xbmc.translatePath("special://profile/library/video/" + dir))
for file in allFiles:
if file.startswith("emby"):
xbmcvfs.delete(path + file)
#we build up a listing and set window props for all nodes we created
#the window props will be used by the main entry point to quickly build up the listing and can be used in skins (like titan) too for quick reference
#comment marcelveldt: please leave the window props as-is because I will be referencing them in titan skin...
totalNodesCount = 0
#build the listing for all views
views_movies = ReadEmbyDB().getCollections("movies")
if views_movies:
for view in views_movies:
title = view.get('title')
content = view.get('content')
if content == "mixed":
title = "%s - Movies" % title
self.buildVideoNodeForView(title, "movies", totalNodesCount)
totalNodesCount +=1
views_shows = ReadEmbyDB().getCollections("tvshows")
if views_shows:
for view in views_shows:
title = view.get('title')
content = view.get('content')
if content == "mixed":
title = "%s - TV Shows" % title
self.buildVideoNodeForView(title, "tvshows", totalNodesCount)
totalNodesCount +=1
#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