Readd with lowercase name

This commit is contained in:
angelblue05 2015-12-24 14:07:00 -06:00
parent 9855ac4c94
commit 69884a1b54
11 changed files with 5708 additions and 0 deletions

378
resources/lib/api.py Normal file
View file

@ -0,0 +1,378 @@
# -*- 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

@ -0,0 +1,401 @@
# -*- coding: utf-8 -*-
##################################################################################################
import json
import requests
import logging
import xbmc
import xbmcgui
import utils
import clientinfo
##################################################################################################
# Disable requests logging
from requests.packages.urllib3.exceptions import InsecureRequestWarning
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
#logging.getLogger('requests').setLevel(logging.WARNING)
##################################################################################################
class DownloadUtils():
# Borg - multiple instances, shared state
_shared_state = {}
clientInfo = clientinfo.ClientInfo()
addonName = clientInfo.getAddonName()
# Requests session
s = None
timeout = 30
def __init__(self):
self.__dict__ = self._shared_state
def logMsg(self, msg, lvl=1):
className = self.__class__.__name__
utils.logMsg("%s %s" % (self.addonName, className), msg, 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}/emby/Sessions/Capabilities/Full?format=json"
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)
self.downloadUrl(url, postBody=data, type="POST")
self.logMsg("Posted capabilities to %s" % self.server, 2)
# Attempt at getting sessionId
url = "{server}/emby/Sessions?DeviceId=%s&format=json" % deviceId
result = self.downloadUrl(url)
try:
sessionId = result[0]['Id']
except (KeyError, TypeError):
self.logMsg("Failed to retrieve sessionId.", 1)
else:
self.logMsg("Session: %s" % result, 2)
self.logMsg("SessionId: %s" % sessionId, 1)
utils.window('emby_sessionId', value=sessionId)
# Post any permanent additional users
additionalUsers = utils.settings('additionalUsers')
if additionalUsers:
additionalUsers = additionalUsers.split(',')
self.logMsg(
"List of permanent users added to the session: %s"
% additionalUsers, 1)
# Get the user list from server to get the userId
url = "{server}/emby/Users?format=json"
result = self.downloadUrl(url)
for additional in additionalUsers:
addUser = additional.decode('utf-8').lower()
# Compare to server users to list of permanent additional users
for user in result:
username = user['Name'].lower()
if username in addUser:
userId = user['Id']
url = (
"{server}/emby/Sessions/%s/Users/%s?format=json"
% (sessionId, userId)
)
self.downloadUrl(url, postBody={}, type="POST")
def startSession(self):
self.deviceId = self.clientInfo.getDeviceId()
# 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, 1)
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.getDeviceId()
version = clientInfo.getVersion()
if not authenticate:
# If user is not authenticated
auth = (
'MediaBrowser Client="Kodi", Device="%s", DeviceId="%s", Version="%s"'
% (deviceName, deviceId, version))
header = {
'Content-type': 'application/json',
'Accept-encoding': 'gzip',
'Accept-Charset': 'UTF-8,*',
'Authorization': auth
}
self.logMsg("Header: %s" % header, 2)
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", parameters=None, authenticate=True):
self.logMsg("=== ENTER downloadUrl ===", 2)
timeout = self.timeout
default_link = ""
try:
# If user is authenticated
if (authenticate):
# Get requests session
try:
s = self.s
# Replace for the real values
url = url.replace("{server}", self.server)
url = url.replace("{UserId}", self.userId)
# Prepare request
if type == "GET":
r = s.get(url, json=postBody, params=parameters, timeout=timeout)
elif type == "POST":
r = s.post(url, json=postBody, timeout=timeout)
elif type == "DELETE":
r = s.delete(url, json=postBody, timeout=timeout)
except AttributeError:
# request session does not exists
# Get user information
self.userId = utils.window('emby_currUser')
self.server = utils.window('emby_server%s' % self.userId)
self.token = utils.window('emby_accessToken%s' % self.userId)
header = self.getHeader()
verifyssl = False
cert = None
# IF user enables ssl verification
if utils.settings('sslverify') == "true":
verifyssl = True
if utils.settings('sslcert') != "None":
cert = utils.settings('sslcert')
# Replace for the real values
url = url.replace("{server}", self.server)
url = url.replace("{UserId}", self.userId)
# Prepare request
if type == "GET":
r = requests.get(url,
json=postBody,
params=parameters,
headers=header,
timeout=timeout,
cert=cert,
verify=verifyssl)
elif type == "POST":
r = requests.post(url,
json=postBody,
headers=header,
timeout=timeout,
cert=cert,
verify=verifyssl)
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:
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,
params=parameters,
headers=header,
timeout=timeout,
verify=verifyssl)
elif type == "POST":
r = requests.post(url,
json=postBody,
headers=header,
timeout=timeout,
verify=verifyssl)
##### THE RESPONSE #####
self.logMsg(r.url, 2)
if r.status_code == 204:
# No body in the response
self.logMsg("====== 204 Success ======", 2)
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":
self.logMsg("Unable to convert the response for: %s" % url, 1)
else:
r.raise_for_status()
##### EXCEPTIONS #####
except requests.exceptions.ConnectionError as e:
# Make the addon aware of status
if utils.window('emby_online') != "false":
self.logMsg("Server unreachable at: %s" % url, 0)
self.logMsg(e, 2)
utils.window('emby_online', value="false")
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 = utils.window('emby_serverStatus')
if 'X-Application-Error-Code' in r.headers:
# Emby server errors
if r.headers['X-Application-Error-Code'] == "ParentalControl":
# Parental control - access restricted
utils.window('emby_serverStatus', value="restricted")
xbmcgui.Dialog().notification(
heading="Emby server",
message="Access restricted.",
icon=xbmcgui.NOTIFICATION_ERROR,
time=5000)
return False
elif r.headers['X-Application-Error-Code'] == "UnauthorizedAccessException":
# User tried to do something his emby account doesn't allow
pass
elif status not in ("401", "Auth"):
# Tell userclient token has been revoked.
utils.window('emby_serverStatus', value="401")
self.logMsg("HTTP Error: %s" % e, 0)
xbmcgui.Dialog().notification(
heading="Error connecting",
message="Unauthorized.",
icon=xbmcgui.NOTIFICATION_ERROR)
return 401
elif r.status_code in (301, 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

842
resources/lib/entrypoint.py Normal file
View file

@ -0,0 +1,842 @@
# -*- coding: utf-8 -*-
#################################################################################################
import json
import os
import sys
import urlparse
import xbmc
import xbmcaddon
import xbmcgui
import xbmcvfs
import xbmcplugin
import artwork
import utils
import clientinfo
import downloadutils
import read_embyserver as embyserver
import embydb_functions as embydb
import playlist
import playbackutils as pbutils
import playutils
import api
#################################################################################################
def doPlayback(itemid, dbid):
emby = embyserver.Read_EmbyServer()
item = emby.getItem(itemid)
pbutils.PlaybackUtils(item).play(itemid, dbid)
##### DO RESET AUTH #####
def resetAuth():
# User tried login and failed too many times
resp = xbmcgui.Dialog().yesno(
heading="Warning",
line1=(
"Emby might lock your account if you fail to log in too many times. "
"Proceed anyway?"))
if resp == 1:
utils.logMsg("EMBY", "Reset login attempts.", 1)
utils.window('emby_serverStatus', value="Auth")
else:
xbmc.executebuiltin('Addon.OpenSettings(plugin.video.emby)')
def addDirectoryItem(label, path, folder=True):
li = xbmcgui.ListItem(label, path=path)
li.setThumbnailImage("special://home/addons/plugin.video.emby/icon.png")
li.setArt({"fanart":"special://home/addons/plugin.video.emby/fanart.jpg"})
li.setArt({"landscape":"special://home/addons/plugin.video.emby/fanart.jpg"})
xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=path, listitem=li, isFolder=folder)
def doMainListing():
xbmcplugin.setContent(int(sys.argv[1]), 'files')
# Get emby nodes from the window props
embyprops = utils.window('Emby.nodes.total')
if embyprops:
totalnodes = int(embyprops)
for i in range(totalnodes):
path = utils.window('Emby.nodes.%s.index' % i)
if not path:
path = utils.window('Emby.nodes.%s.content' % i)
label = utils.window('Emby.nodes.%s.title' % i)
if path:
addDirectoryItem(label, path)
# some extra entries for settings and stuff. TODO --> localize the labels
addDirectoryItem("Network credentials", "plugin://plugin.video.emby/?mode=passwords", False)
addDirectoryItem("Settings", "plugin://plugin.video.emby/?mode=settings", False)
addDirectoryItem("Add user to session", "plugin://plugin.video.emby/?mode=adduser", False)
#addDirectoryItem("Cache all images to Kodi texture cache (advanced)", "plugin://plugin.video.emby/?mode=texturecache")
addDirectoryItem("Perform manual sync", "plugin://plugin.video.emby/?mode=manualsync", False)
addDirectoryItem(
label="Repair local database (force update all content)",
path="plugin://plugin.video.emby/?mode=repair",
folder=False)
addDirectoryItem(
label="Perform local database reset (full resync)",
path="plugin://plugin.video.emby/?mode=reset",
folder=False)
addDirectoryItem(
label="Sync Emby Theme Media to Kodi",
path="plugin://plugin.video.emby/?mode=thememedia",
folder=False)
xbmcplugin.endOfDirectory(int(sys.argv[1]))
##### ADD ADDITIONAL USERS #####
def addUser():
doUtils = downloadutils.DownloadUtils()
clientInfo = clientinfo.ClientInfo()
deviceId = clientInfo.getDeviceId()
deviceName = clientInfo.getDeviceName()
userid = utils.window('emby_currUser')
dialog = xbmcgui.Dialog()
# Get session
url = "{server}/emby/Sessions?DeviceId=%s&format=json" % deviceId
result = doUtils.downloadUrl(url)
try:
sessionId = result[0]['Id']
additionalUsers = result[0]['AdditionalUsers']
# Add user to session
userlist = {}
users = []
url = "{server}/emby/Users?IsDisabled=false&IsHidden=false&format=json"
result = doUtils.downloadUrl(url)
# pull the list of users
for user in result:
name = user['Name']
userId = user['Id']
if userid != userId:
userlist[name] = userId
users.append(name)
# Display dialog if there's additional users
if additionalUsers:
option = dialog.select("Add/Remove user from the session", ["Add user", "Remove user"])
# Users currently in the session
additionalUserlist = {}
additionalUsername = []
# Users currently in the session
for user in additionalUsers:
name = user['UserName']
userId = user['UserId']
additionalUserlist[name] = userId
additionalUsername.append(name)
if option == 1:
# User selected Remove user
resp = dialog.select("Remove user from the session", additionalUsername)
if resp > -1:
selected = additionalUsername[resp]
selected_userId = additionalUserlist[selected]
url = "{server}/emby/Sessions/%s/Users/%s" % (sessionId, selected_userId)
doUtils.downloadUrl(url, postBody={}, type="DELETE")
dialog.notification(
heading="Success!",
message="%s removed from viewing session" % selected,
icon="special://home/addons/plugin.video.emby/icon.png",
time=1000)
# clear picture
position = utils.window('EmbyAdditionalUserPosition.%s' % selected_userId)
utils.window('EmbyAdditionalUserImage.%s' % position, clear=True)
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
utils.logMsg("EMBY", "Displaying list of users: %s" % users)
resp = dialog.select("Add user to the session", users)
# post additional user
if resp > -1:
selected = users[resp]
selected_userId = userlist[selected]
url = "{server}/emby/Sessions/%s/Users/%s" % (sessionId, selected_userId)
doUtils.downloadUrl(url, postBody={}, type="POST")
dialog.notification(
heading="Success!",
message="%s added to viewing session" % selected,
icon="special://home/addons/plugin.video.emby/icon.png",
time=1000)
except:
utils.logMsg("EMBY", "Failed to add user to session.")
dialog.notification(
heading="Error",
message="Unable to add/remove user from the session.",
icon=xbmcgui.NOTIFICATION_ERROR)
# Add additional user images
# always clear the individual items first
totalNodes = 10
for i in range(totalNodes):
if not utils.window('EmbyAdditionalUserImage.%s' % i):
break
utils.window('EmbyAdditionalUserImage.%s' % i)
url = "{server}/emby/Sessions?DeviceId=%s" % deviceId
result = doUtils.downloadUrl(url)
additionalUsers = result[0]['AdditionalUsers']
count = 0
for additionaluser in additionalUsers:
url = "{server}/emby/Users/%s?format=json" % additionaluser['UserId']
result = doUtils.downloadUrl(url)
utils.window('EmbyAdditionalUserImage.%s' % count,
value=artwork.Artwork().getUserArtwork(result, 'Primary'))
utils.window('EmbyAdditionalUserPosition.%s' % additionaluser['UserId'], value=str(count))
count +=1
##### THEME MUSIC/VIDEOS #####
def getThemeMedia():
doUtils = downloadutils.DownloadUtils()
dialog = xbmcgui.Dialog()
playback = None
# Choose playback method
resp = dialog.select("Playback method for your themes", ["Direct Play", "Direct Stream"])
if resp == 0:
playback = "DirectPlay"
elif resp == 1:
playback = "DirectStream"
else:
return
library = xbmc.translatePath(
"special://profile/addon_data/plugin.video.emby/library/").decode('utf-8')
# Create library directory
if not xbmcvfs.exists(library):
xbmcvfs.mkdir(library)
# Set custom path for user
tvtunes_path = xbmc.translatePath(
"special://profile/addon_data/script.tvtunes/").decode('utf-8')
if xbmcvfs.exists(tvtunes_path):
tvtunes = xbmcaddon.Addon(id="script.tvtunes")
tvtunes.setSetting('custom_path_enable', "true")
tvtunes.setSetting('custom_path', library)
utils.logMsg("EMBY", "TV Tunes custom path is enabled and set.", 1)
else:
# if it does not exist this will not work so warn user
# often they need to edit the settings first for it to be created.
dialog.ok(
heading="Warning",
line1=(
"The settings file does not exist in tvtunes. ",
"Go to the tvtunes addon and change a setting, then come back and re-run."))
xbmc.executebuiltin('Addon.OpenSettings(script.tvtunes)')
return
# Get every user view Id
embyconn = utils.kodiSQL('emby')
embycursor = embyconn.cursor()
emby_db = embydb.Embydb_Functions(embycursor)
viewids = emby_db.getViews()
embycursor.close()
# Get Ids with Theme Videos
itemIds = {}
for view in viewids:
url = "{server}/emby/Users/{UserId}/Items?HasThemeVideo=True&ParentId=%s&format=json" % view
result = doUtils.downloadUrl(url)
if result['TotalRecordCount'] != 0:
for item in result['Items']:
itemId = item['Id']
folderName = item['Name']
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}/emby/Items/%s/ThemeVideos?format=json" % itemId
result = doUtils.downloadUrl(url)
# Create nfo and write themes to it
nfo_file = open(nfo_path, 'w')
pathstowrite = ""
# May be more than one theme
for theme in result['Items']:
putils = playutils.PlayUtils(theme)
if playback == "DirectPlay":
playurl = putils.directPlay()
else:
playurl = putils.directStream()
pathstowrite += ('<file>%s</file>' % playurl.encode('utf-8'))
# Check if the item has theme songs and add them
url = "{server}/emby/Items/%s/ThemeSongs?format=json" % itemId
result = doUtils.downloadUrl(url)
# May be more than one theme
for theme in result['Items']:
putils = playutils.PlayUtils(theme)
if playback == "DirectPlay":
playurl = putils.directPlay()
else:
playurl = putils.directStream()
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 viewids:
url = "{server}/emby/Users/{UserId}/Items?HasThemeSong=True&ParentId=%s&format=json" % view
result = doUtils.downloadUrl(url)
if result['TotalRecordCount'] != 0:
for item in result['Items']:
itemId = item['Id']
folderName = item['Name']
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}/emby/Items/%s/ThemeSongs?format=json" % itemId
result = doUtils.downloadUrl(url)
# Create nfo and write themes to it
nfo_file = open(nfo_path, 'w')
pathstowrite = ""
# May be more than one theme
for theme in result['Items']:
putils = playutils.PlayUtils(theme)
if playback == "DirectPlay":
playurl = putils.directPlay()
else:
playurl = putils.directStream()
pathstowrite += ('<file>%s</file>' % playurl.encode('utf-8'))
nfo_file.write(
'<tvtunes>%s</tvtunes>' % pathstowrite
)
# Close nfo file
nfo_file.close()
dialog.notification(
heading="Emby for Kodi",
message="Themes added!",
icon="special://home/addons/plugin.video.emby/icon.png",
time=1000,
sound=False)
##### BROWSE EMBY CHANNELS #####
def BrowseChannels(itemid, folderid=None):
_addon_id = int(sys.argv[1])
_addon_url = sys.argv[0]
doUtils = downloadutils.DownloadUtils()
art = artwork.Artwork()
xbmcplugin.setContent(int(sys.argv[1]), 'files')
if folderid:
url = (
"{server}/emby/Channels/%s/Items?userid={UserId}&folderid=%s&format=json"
% (itemid, folderid))
elif itemid == "0":
# id 0 is the root channels folder
url = "{server}/emby/Channels?{UserId}&format=json"
else:
url = "{server}/emby/Channels/%s/Items?UserId={UserId}&format=json" % itemid
result = doUtils.downloadUrl(url)
try:
channels = result['Items']
except TypeError:
pass
else:
for item in channels:
API = api.API(item)
itemid = item['Id']
itemtype = item['Type']
title = item.get('Name', "Missing Title")
li = xbmcgui.ListItem(title)
if itemtype == "ChannelFolderItem":
isFolder = True
else:
isFolder = False
channelId = item.get('ChannelId', "")
channelName = item.get('ChannelName', "")
premieredate = API.getPremiereDate()
# Process Genres
genre = API.getGenres()
# Process UserData
overlay = 0
userdata = API.getUserData()
seektime = userdata['Resume']
played = userdata['Played']
if played:
overlay = 7
else:
overlay = 6
favorite = userdata['Favorite']
if favorite:
overlay = 5
playcount = userdata['PlayCount']
if playcount is None:
playcount = 0
# Populate the details list
details = {
'title': title,
'channelname': channelName,
'plot': API.getOverview(),
'Overlay': str(overlay),
'playcount': str(playcount)
}
if itemtype == "ChannelVideoItem":
xbmcplugin.setContent(_addon_id, 'movies')
elif itemtype == "ChannelAudioItem":
xbmcplugin.setContent(_addon_id, 'songs')
# Populate the extradata list and artwork
pbutils.PlaybackUtils(item).setArtwork(li)
extradata = {
'id': itemid,
'rating': item.get('CommunityRating'),
'year': item.get('ProductionYear'),
'premieredate': premieredate,
'genre': genre,
'playcount': str(playcount),
'itemtype': itemtype
}
li.setInfo('video', infoLabels=extradata)
li.setThumbnailImage(art.getAllArtwork(item)['Primary'])
li.setIconImage('DefaultTVShows.png')
if itemtype == "Channel":
path = "%s?id=%s&mode=channels" % (_addon_url, itemid)
xbmcplugin.addDirectoryItem(handle=_addon_id, url=path, listitem=li, isFolder=True)
elif isFolder:
path = "%s?id=%s&mode=channelsfolder&folderid=%s" % (_addon_url, channelId, itemid)
xbmcplugin.addDirectoryItem(handle=_addon_id, url=path, listitem=li, isFolder=True)
else:
path = "%s?id=%s&mode=play" % (_addon_url, itemid)
li.setProperty('IsPlayable', 'true')
xbmcplugin.addDirectoryItem(handle=_addon_id, url=path, listitem=li)
xbmcplugin.endOfDirectory(handle=int(sys.argv[1]))
##### LISTITEM SETUP FOR VIDEONODES #####
def createListItem(item):
title = item['title']
li = xbmcgui.ListItem(title)
li.setProperty('IsPlayable', "true")
metadata = {
'Title': title,
'duration': str(item['runtime']/60),
'Plot': item['plot'],
'Playcount': item['playcount']
}
if "episode" in item:
episode = item['episode']
metadata['Episode'] = episode
if "season" in item:
season = item['season']
metadata['Season'] = season
if season and episode:
li.setProperty('episodeno', "s%.2de%.2d" % (season, episode))
if "firstaired" in item:
metadata['Premiered'] = item['firstaired']
if "showtitle" in item:
metadata['TVshowTitle'] = item['showtitle']
if "rating" in item:
metadata['Rating'] = str(round(float(item['rating']),1))
if "director" in item:
metadata['Director'] = " / ".join(item['director'])
if "writer" in item:
metadata['Writer'] = " / ".join(item['writer'])
if "cast" in item:
cast = []
castandrole = []
for person in item['cast']:
name = person['name']
cast.append(name)
castandrole.append((name, person['role']))
metadata['Cast'] = cast
metadata['CastAndRole'] = castandrole
li.setInfo(type="Video", infoLabels=metadata)
li.setProperty('resumetime', str(item['resume']['position']))
li.setProperty('totaltime', str(item['resume']['total']))
li.setArt(item['art'])
li.setThumbnailImage(item['art'].get('thumb',''))
li.setIconImage('DefaultTVShows.png')
li.setProperty('dbid', str(item['episodeid']))
li.setProperty('fanart_image', item['art'].get('tvshow.fanart',''))
for key, value in item['streamdetails'].iteritems():
for stream in value:
li.addStreamInfo(key, stream)
return li
##### GET NEXTUP EPISODES FOR TAGNAME #####
def getNextUpEpisodes(tagname, limit):
count = 0
# if the addon is called with nextup parameter,
# we return the nextepisodes list of the given tagname
xbmcplugin.setContent(int(sys.argv[1]), 'episodes')
# First we get a list of all the TV shows - filtered by tag
query = {
'jsonrpc': "2.0",
'id': "libTvShows",
'method': "VideoLibrary.GetTVShows",
'params': {
'sort': {'order': "descending", 'method': "lastplayed"},
'filter': {
'and': [
{'operator': "true", 'field': "inprogress", 'value': ""},
{'operator': "contains", 'field': "tag", 'value': "%s" % tagname}
]},
'properties': ['title', 'studio', 'mpaa', 'file', 'art']
}
}
result = xbmc.executeJSONRPC(json.dumps(query))
result = json.loads(result)
# If we found any, find the oldest unwatched show for each one.
try:
items = result['result']['tvshows']
except (KeyError, TypeError):
pass
else:
for item in items:
if utils.settings('ignoreSpecialsNextEpisodes') == "true":
query = {
'jsonrpc': "2.0",
'id': 1,
'method': "VideoLibrary.GetEpisodes",
'params': {
'tvshowid': item['tvshowid'],
'sort': {'method': "episode"},
'filter': {
'and': [
{'operator': "lessthan", 'field': "playcount", 'value': "1"},
{'operator': "greaterthan", 'field': "season", 'value': "0"}
]},
'properties': [
"title", "playcount", "season", "episode", "showtitle",
"plot", "file", "rating", "resume", "tvshowid", "art",
"streamdetails", "firstaired", "runtime", "writer",
"dateadded", "lastplayed"
],
'limits': {"end": 1}
}
}
else:
query = {
'jsonrpc': "2.0",
'id': 1,
'method': "VideoLibrary.GetEpisodes",
'params': {
'tvshowid': item['tvshowid'],
'sort': {'method': "episode"},
'filter': {'operator': "lessthan", 'field': "playcount", 'value': "1"},
'properties': [
"title", "playcount", "season", "episode", "showtitle",
"plot", "file", "rating", "resume", "tvshowid", "art",
"streamdetails", "firstaired", "runtime", "writer",
"dateadded", "lastplayed"
],
'limits': {"end": 1}
}
}
result = xbmc.executeJSONRPC(json.dumps(query))
result = json.loads(result)
try:
episodes = result['result']['episodes']
except (KeyError, TypeError):
pass
else:
for episode in episodes:
li = createListItem(episode)
xbmcplugin.addDirectoryItem(
handle=int(sys.argv[1]),
url=item['file'],
listitem=li)
count += 1
if count == limit:
break
xbmcplugin.endOfDirectory(handle=int(sys.argv[1]))
##### GET INPROGRESS EPISODES FOR TAGNAME #####
def getInProgressEpisodes(tagname, limit):
count = 0
# if the addon is called with inprogressepisodes parameter,
# we return the inprogressepisodes list of the given tagname
xbmcplugin.setContent(int(sys.argv[1]), 'episodes')
# First we get a list of all the in-progress TV shows - filtered by tag
query = {
'jsonrpc': "2.0",
'id': "libTvShows",
'method': "VideoLibrary.GetTVShows",
'params': {
'sort': {'order': "descending", 'method': "lastplayed"},
'filter': {
'and': [
{'operator': "true", 'field': "inprogress", 'value': ""},
{'operator': "contains", 'field': "tag", 'value': "%s" % tagname}
]},
'properties': ['title', 'studio', 'mpaa', 'file', 'art']
}
}
result = xbmc.executeJSONRPC(json.dumps(query))
result = json.loads(result)
# If we found any, find the oldest unwatched show for each one.
try:
items = result['result']['tvshows']
except (KeyError, TypeError):
pass
else:
for item in items:
query = {
'jsonrpc': "2.0",
'id': 1,
'method': "VideoLibrary.GetEpisodes",
'params': {
'tvshowid': item['tvshowid'],
'sort': {'method': "episode"},
'filter': {'operator': "true", 'field': "inprogress", 'value': ""},
'properties': [
"title", "playcount", "season", "episode", "showtitle", "plot",
"file", "rating", "resume", "tvshowid", "art", "cast",
"streamdetails", "firstaired", "runtime", "writer",
"dateadded", "lastplayed"
]
}
}
result = xbmc.executeJSONRPC(json.dumps(query))
result = json.loads(result)
try:
episodes = result['result']['episodes']
except (KeyError, TypeError):
pass
else:
for episode in episodes:
li = createListItem(episode)
xbmcplugin.addDirectoryItem(
handle=int(sys.argv[1]),
url=item['file'],
listitem=li)
count += 1
if count == limit:
break
xbmcplugin.endOfDirectory(handle=int(sys.argv[1]))
##### GET RECENT EPISODES FOR TAGNAME #####
def getRecentEpisodes(tagname, limit):
count = 0
# if the addon is called with recentepisodes parameter,
# we return the recentepisodes list of the given tagname
xbmcplugin.setContent(int(sys.argv[1]), 'episodes')
# First we get a list of all the TV shows - filtered by tag
query = {
'jsonrpc': "2.0",
'id': "libTvShows",
'method': "VideoLibrary.GetTVShows",
'params': {
'sort': {'order': "descending", 'method': "dateadded"},
'filter': {'operator': "contains", 'field': "tag", 'value': "%s" % tagname},
'properties': ["title","sorttitle"]
}
}
result = xbmc.executeJSONRPC(json.dumps(query))
result = json.loads(result)
# If we found any, find the oldest unwatched show for each one.
try:
items = result['result']['tvshows']
except (KeyError, TypeError):
pass
else:
allshowsIds = set()
for item in items:
allshowsIds.add(item['tvshowid'])
query = {
'jsonrpc': "2.0",
'id': 1,
'method': "VideoLibrary.GetEpisodes",
'params': {
'sort': {'order': "descending", 'method': "dateadded"},
'filter': {'operator': "lessthan", 'field': "playcount", 'value': "1"},
'properties': [
"title", "playcount", "season", "episode", "showtitle", "plot",
"file", "rating", "resume", "tvshowid", "art", "streamdetails",
"firstaired", "runtime", "cast", "writer", "dateadded", "lastplayed"
],
"limits": {"end": limit}
}
}
result = xbmc.executeJSONRPC(json.dumps(query))
result = json.loads(result)
try:
episodes = result['result']['episodes']
except (KeyError, TypeError):
pass
else:
for episode in episodes:
if episode['tvshowid'] in allshowsIds:
li = createListItem(episode)
xbmcplugin.addDirectoryItem(
handle=int(sys.argv[1]),
url=item['file'],
listitem=li)
count += 1
if count == limit:
break
xbmcplugin.endOfDirectory(handle=int(sys.argv[1]))
##### GET EXTRAFANART FOR LISTITEM #####
def getExtraFanArt():
emby = embyserver.Read_EmbyServer()
art = artwork.Artwork()
# Get extrafanart for listitem
# this will only be used for skins that actually call the listitem's path + fanart dir...
try:
# Only do this if the listitem has actually changed
itemPath = xbmc.getInfoLabel("ListItem.FileNameAndPath")
if not itemPath:
itemPath = xbmc.getInfoLabel("ListItem.Path")
if any([x in itemPath for x in ['tvshows', 'musicvideos', 'movies']]):
params = urlparse.parse_qs(itemPath)
embyId = params['id'][0]
utils.logMsg("EMBY", "Requesting extrafanart for Id: %s" % embyId, 1)
# We need to store the images locally for this to work
# because of the caching system in xbmc
fanartDir = xbmc.translatePath("special://thumbnails/emby/%s/" % embyId).decode('utf-8')
if not xbmcvfs.exists(fanartDir):
# Download the images to the cache directory
xbmcvfs.mkdirs(fanartDir)
item = emby.getItem(embyId)
if item:
backdrops = art.getAllArtwork(item)['Backdrop']
tags = item['BackdropImageTags']
count = 0
for backdrop in backdrops:
# Same ordering as in artwork
tag = tags[count]
fanartFile = os.path.join(fanartDir, "fanart%s.jpg" % tag)
li = xbmcgui.ListItem(tag, path=fanartFile)
xbmcplugin.addDirectoryItem(
handle=int(sys.argv[1]),
url=fanartFile,
listitem=li)
xbmcvfs.copy(backdrop, fanartFile)
count += 1
else:
utils.logMsg("EMBY", "Found cached backdrop.", 2)
# Use existing cached images
dirs, files = xbmcvfs.listdir(fanartDir)
for file in files:
fanartFile = os.path.join(fanartDir, file)
li = xbmcgui.ListItem(file, path=fanartFile)
xbmcplugin.addDirectoryItem(
handle=int(sys.argv[1]),
url=fanartFile,
listitem=li)
except Exception as e:
utils.logMsg("EMBY", "Error getting extrafanart: %s" % e, 1)
# Always do endofdirectory to prevent errors in the logs
xbmcplugin.endOfDirectory(int(sys.argv[1]))

View file

@ -0,0 +1,195 @@
# -*- coding: utf-8 -*-
#################################################################################################
import json
import xbmc
import xbmcgui
import clientinfo
import downloadutils
import embydb_functions as embydb
import playbackutils as pbutils
import utils
#################################################################################################
class KodiMonitor(xbmc.Monitor):
def __init__(self):
self.clientInfo = clientinfo.ClientInfo()
self.addonName = self.clientInfo.getAddonName()
self.doUtils = downloadutils.DownloadUtils()
self.logMsg("Kodi monitor started.", 1)
def logMsg(self, msg, lvl=1):
self.className = self.__class__.__name__
utils.logMsg("%s %s" % (self.addonName, self.className), msg, lvl)
def onScanStarted(self, library):
self.logMsg("Kodi library scan %s running." % library, 2)
if library == "video":
utils.window('emby_kodiScan', value="true")
def onScanFinished(self, library):
self.logMsg("Kodi library scan %s finished." % library, 2)
if library == "video":
utils.window('emby_kodiScan', clear=True)
def onNotification(self, sender, method, data):
doUtils = self.doUtils
if method not in ("Playlist.OnAdd"):
self.logMsg("Method: %s Data: %s" % (method, data), 1)
if data:
data = json.loads(data)
if method == "Player.OnPlay":
# Set up report progress for emby playback
item = data.get('item')
try:
kodiid = item['id']
type = item['type']
except (KeyError, TypeError):
self.logMsg("Properties already set for item.", 1)
else:
if ((utils.settings('useDirectPaths') == "1" and not type == "song") or
(type == "song" and utils.settings('disableMusic') == "false")):
# Set up properties for player
embyconn = utils.kodiSQL('emby')
embycursor = embyconn.cursor()
emby_db = embydb.Embydb_Functions(embycursor)
emby_dbitem = emby_db.getItem_byKodiId(kodiid, type)
try:
itemid = emby_dbitem[0]
except TypeError:
self.logMsg("No kodiid returned.", 1)
else:
url = "{server}/emby/Users/{UserId}/Items/%s?format=json" % itemid
result = doUtils.downloadUrl(url)
self.logMsg("Item: %s" % result, 2)
playurl = None
count = 0
while not playurl and count < 2:
try:
playurl = xbmc.Player().getPlayingFile()
except RuntimeError:
count += 1
xbmc.sleep(200)
else:
listItem = xbmcgui.ListItem()
playback = pbutils.PlaybackUtils(result)
if type == "song" and utils.settings('streamMusic') == "true":
utils.window('emby_%s.playmethod' % playurl,
value="DirectStream")
else:
utils.window('emby_%s.playmethod' % playurl,
value="DirectPlay")
# Set properties for player.py
playback.setProperties(playurl, listItem)
finally:
embycursor.close()
elif method == "VideoLibrary.OnUpdate":
# Manually marking as watched/unwatched
playcount = data.get('playcount')
item = data.get('item')
try:
kodiid = item['id']
type = item['type']
except (KeyError, TypeError):
self.logMsg("Item is invalid for playstate update.", 1)
else:
# Send notification to the server.
embyconn = utils.kodiSQL('emby')
embycursor = embyconn.cursor()
emby_db = embydb.Embydb_Functions(embycursor)
emby_dbitem = emby_db.getItem_byKodiId(kodiid, type)
try:
itemid = emby_dbitem[0]
except TypeError:
self.logMsg("Could not find itemid in emby database.", 1)
else:
# Stop from manually marking as watched unwatched, with actual playback.
if utils.window('emby_skipWatched%s' % itemid) == "true":
# property is set in player.py
utils.window('emby_skipWatched%s' % itemid, clear=True)
else:
# notify the server
url = "{server}/emby/Users/{UserId}/PlayedItems/%s?format=json" % itemid
if playcount != 0:
doUtils.downloadUrl(url, type="POST")
self.logMsg("Mark as watched for itemid: %s" % itemid, 1)
else:
doUtils.downloadUrl(url, type="DELETE")
self.logMsg("Mark as unwatched for itemid: %s" % itemid, 1)
finally:
embycursor.close()
elif method == "VideoLibrary.OnRemove":
try:
kodiid = data['id']
type = data['type']
except (KeyError, TypeError):
self.logMsg("Item is invalid for emby deletion.", 1)
else:
# Send the delete action to the server.
offerDelete = False
if type == "episode" and utils.settings('deleteTV') == "true":
offerDelete = True
elif type == "movie" and utils.settings('deleteMovies') == "true":
offerDelete = True
if utils.settings('offerDelete') != "true":
# Delete could be disabled, even if the subsetting is enabled.
offerDelete = False
if offerDelete:
embyconn = utils.kodiSQL('emby')
embycursor = embyconn.cursor()
emby_db = embydb.Embydb_Functions(embycursor)
emby_dbitem = emby_db.getItem_byKodiId(kodiid, type)
try:
itemid = emby_dbitem[0]
except TypeError:
self.logMsg("Could not find itemid in emby database.", 1)
else:
if utils.settings('skipConfirmDelete') != "true":
resp = xbmcgui.Dialog().yesno(
heading="Confirm delete",
line1="Delete file on Emby Server?")
if not resp:
self.logMsg("User skipped deletion.", 1)
embycursor.close()
return
url = "{server}/emby/Items/%s?format=json" % itemid
self.logMsg("Deleting request: %s" % itemid)
doUtils.downloadUrl(url, type="DELETE")
finally:
embycursor.close()
elif method == "System.OnWake":
# Allow network to wake up
xbmc.sleep(10000)
utils.window('emby_onWake', value="true")
elif method == "Playlist.OnClear":
utils.window('emby_customPlaylist', clear=True, windowid=10101)
#xbmcgui.Window(10101).clearProperties()
self.logMsg("Clear playlist properties.")

1338
resources/lib/librarysync.py Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,353 @@
# -*- coding: utf-8 -*-
#################################################################################################
import json
import sys
import xbmc
import xbmcgui
import xbmcplugin
import api
import artwork
import clientinfo
import downloadutils
import playutils as putils
import playlist
import read_embyserver as embyserver
import utils
#################################################################################################
class PlaybackUtils():
def __init__(self, item):
self.item = item
self.API = api.API(self.item)
self.clientInfo = clientinfo.ClientInfo()
self.addonName = self.clientInfo.getAddonName()
self.doUtils = downloadutils.DownloadUtils()
self.userid = utils.window('emby_currUser')
self.server = utils.window('emby_server%s' % self.userid)
self.artwork = artwork.Artwork()
self.emby = embyserver.Read_EmbyServer()
self.pl = playlist.Playlist()
def logMsg(self, msg, lvl=1):
self.className = self.__class__.__name__
utils.logMsg("%s %s" % (self.addonName, self.className), msg, lvl)
def play(self, itemid, dbid=None):
self.logMsg("Play called.", 1)
doUtils = self.doUtils
item = self.item
API = self.API
listitem = xbmcgui.ListItem()
playutils = putils.PlayUtils(item)
playurl = playutils.getPlayUrl()
if not playurl:
return xbmcplugin.setResolvedUrl(int(sys.argv[1]), False, listitem)
if dbid is None:
# Item is not in Kodi database
listitem.setPath(playurl)
self.setProperties(playurl, listitem)
return xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, listitem)
############### ORGANIZE CURRENT PLAYLIST ################
homeScreen = xbmc.getCondVisibility('Window.IsActive(home)')
playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
startPos = max(playlist.getposition(), 0) # Can return -1
sizePlaylist = playlist.size()
currentPosition = startPos
propertiesPlayback = utils.window('emby_playbackProps', windowid=10101) == "true"
introsPlaylist = False
dummyPlaylist = False
self.logMsg("Playlist start position: %s" % startPos, 1)
self.logMsg("Playlist plugin position: %s" % currentPosition, 1)
self.logMsg("Playlist size: %s" % sizePlaylist, 1)
############### RESUME POINT ################
userdata = API.getUserData()
seektime = API.adjustResume(userdata['Resume'])
# We need to ensure we add the intro and additional parts only once.
# Otherwise we get a loop.
if not propertiesPlayback:
utils.window('emby_playbackProps', value="true", windowid=10101)
self.logMsg("Setting up properties in playlist.", 1)
if (not homeScreen and not seektime and
utils.window('emby_customPlaylist', windowid=10101) != "true"):
self.logMsg("Adding dummy file to playlist.", 2)
dummyPlaylist = True
playlist.add(playurl, listitem, index=startPos)
# Remove the original item from playlist
self.pl.removefromPlaylist(startPos+1)
# Readd the original item to playlist - via jsonrpc so we have full metadata
self.pl.insertintoPlaylist(currentPosition+1, dbid, item['Type'].lower())
currentPosition += 1
############### -- CHECK FOR INTROS ################
if utils.settings('enableCinema') == "true" and not seektime:
# if we have any play them when the movie/show is not being resumed
url = "{server}/emby/Users/{UserId}/Items/%s/Intros?format=json" % itemid
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.
introListItem = xbmcgui.ListItem()
introPlayurl = putils.PlayUtils(intro).getPlayUrl()
self.logMsg("Adding Intro: %s" % introPlayurl, 1)
# Set listitem and properties for intros
pbutils = PlaybackUtils(intro)
pbutils.setProperties(introPlayurl, introListItem)
self.pl.insertintoPlaylist(currentPosition, url=introPlayurl)
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.pl.addtoPlaylist(dbid, item['Type'].lower())
# Ensure that additional parts are played after the main item
currentPosition += 1
############### -- CHECK FOR ADDITIONAL PARTS ################
if item.get('PartCount'):
# Only add to the playlist after intros have played
partcount = item['PartCount']
url = "{server}/emby/Videos/%s/AdditionalParts?format=json" % itemid
parts = doUtils.downloadUrl(url)
for part in parts['Items']:
additionalListItem = xbmcgui.ListItem()
additionalPlayurl = putils.PlayUtils(part).getPlayUrl()
self.logMsg("Adding additional part: %s" % partcount, 1)
# Set listitem and properties for each additional parts
pbutils = PlaybackUtils(part)
pbutils.setProperties(additionalPlayurl, additionalListItem)
pbutils.setArtwork(additionalListItem)
playlist.add(additionalPlayurl, additionalListItem, index=currentPosition)
self.pl.verifyPlaylist()
currentPosition += 1
if dummyPlaylist:
# Added a dummy file to the playlist,
# because the first item is going to fail automatically.
self.logMsg("Processed as a playlist. First item is skipped.", 1)
return xbmcplugin.setResolvedUrl(int(sys.argv[1]), False, listitem)
# We just skipped adding properties. Reset flag for next time.
elif propertiesPlayback:
self.logMsg("Resetting properties playback flag.", 2)
utils.window('emby_playbackProps', clear=True, windowid=10101)
#self.pl.verifyPlaylist()
########## SETUP MAIN ITEM ##########
# For transcoding only, ask for audio/subs pref
if utils.window('emby_%s.playmethod' % playurl) == "Transcode":
playurl = playutils.audioSubsPref(playurl)
utils.window('emby_%s.playmethod' % playurl, value="Transcode")
listitem.setPath(playurl)
self.setProperties(playurl, listitem)
############### PLAYBACK ################
if homeScreen and seektime:
self.logMsg("Play as a widget item.", 1)
self.setListItem(listitem)
xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, listitem)
elif ((introsPlaylist and utils.window('emby_customPlaylist', windowid=10101) == "true") or
(homeScreen and not sizePlaylist)):
# Playlist was created just now, play it.
self.logMsg("Play playlist.", 1)
xbmc.Player().play(playlist, startpos=startPos)
else:
self.logMsg("Play as a regular item.", 1)
xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, listitem)
def setProperties(self, playurl, listitem):
# Set all properties necessary for plugin path playback
item = self.item
itemid = item['Id']
itemtype = item['Type']
embyitem = "emby_%s" % playurl
utils.window('%s.runtime' % embyitem, value=str(item.get('RunTimeTicks')))
utils.window('%s.type' % embyitem, value=itemtype)
utils.window('%s.itemid' % embyitem, value=itemid)
if itemtype == "Episode":
utils.window('%s.refreshid' % embyitem, value=item.get('SeriesId'))
else:
utils.window('%s.refreshid' % embyitem, value=itemid)
# Append external subtitles to stream
playmethod = utils.window('%s.playmethod' % embyitem)
# Only for direct play and direct stream
subtitles = self.externalSubs(playurl)
if playmethod in ("DirectStream", "Transcode"):
# Direct play automatically appends external
listitem.setSubtitles(subtitles)
self.setArtwork(listitem)
def externalSubs(self, playurl):
externalsubs = []
mapping = {}
item = self.item
itemid = item['Id']
try:
mediastreams = item['MediaSources'][0]['MediaStreams']
except (TypeError, KeyError, IndexError):
return
kodiindex = 0
for stream in mediastreams:
index = stream['Index']
# Since Emby returns all possible tracks together, have to pull only external subtitles.
# IsTextSubtitleStream if true, is available to download from emby.
if (stream['Type'] == "Subtitle" and
stream['IsExternal'] and stream['IsTextSubtitleStream']):
# Direct stream
url = ("%s/Videos/%s/%s/Subtitles/%s/Stream.srt"
% (self.server, itemid, itemid, index))
# map external subtitles for mapping
mapping[kodiindex] = index
externalsubs.append(url)
kodiindex += 1
mapping = json.dumps(mapping)
utils.window('emby_%s.indexMapping' % playurl, value=mapping)
return externalsubs
def setArtwork(self, listItem):
# Set up item and item info
item = self.item
artwork = self.artwork
allartwork = artwork.getAllArtwork(item, parentInfo=True)
# Set artwork for listitem
arttypes = {
'poster': "Primary",
'tvshow.poster': "Primary",
'clearart': "Art",
'tvshow.clearart': "Art",
'clearlogo': "Logo",
'tvshow.clearlogo': "Logo",
'discart': "Disc",
'fanart_image': "Backdrop",
'landscape': "Thumb"
}
for arttype in arttypes:
art = arttypes[arttype]
if art == "Backdrop":
try: # Backdrop is a list, grab the first backdrop
self.setArtProp(listItem, arttype, allartwork[art][0])
except: pass
else:
self.setArtProp(listItem, arttype, allartwork[art])
def setArtProp(self, listItem, arttype, path):
if arttype in (
'thumb', 'fanart_image', 'small_poster', 'tiny_poster',
'medium_landscape', 'medium_poster', 'small_fanartimage',
'medium_fanartimage', 'fanart_noindicators'):
listItem.setProperty(arttype, path)
else:
listItem.setArt({arttype: path})
def setListItem(self, listItem):
item = self.item
type = item['Type']
API = self.API
people = API.getPeople()
studios = API.getStudios()
metadata = {
'title': item.get('Name', "Missing name"),
'year': item.get('ProductionYear'),
'plot': API.getOverview(),
'director': people.get('Director'),
'writer': people.get('Writer'),
'mpaa': API.getMpaa(),
'genre': " / ".join(item['Genres']),
'studio': " / ".join(studios),
'aired': API.getPremiereDate(),
'rating': item.get('CommunityRating'),
'votes': item.get('VoteCount')
}
if "Episode" in type:
# Only for tv shows
thumbId = item.get('SeriesId')
season = item.get('ParentIndexNumber', -1)
episode = item.get('IndexNumber', -1)
show = item.get('SeriesName', "")
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)

502
resources/lib/player.py Normal file
View file

@ -0,0 +1,502 @@
# -*- coding: utf-8 -*-
#################################################################################################
import json
import xbmc
import xbmcgui
import utils
import clientinfo
import downloadutils
import kodidb_functions as kodidb
import websocket_client as wsc
#################################################################################################
class Player(xbmc.Player):
# Borg - multiple instances, shared state
_shared_state = {}
played_info = {}
playStats = {}
currentFile = None
def __init__(self):
self.__dict__ = self._shared_state
self.clientInfo = clientinfo.ClientInfo()
self.addonName = self.clientInfo.getAddonName()
self.doUtils = downloadutils.DownloadUtils()
self.ws = wsc.WebSocket_Client()
self.xbmcplayer = xbmc.Player()
self.logMsg("Starting playback monitor.", 2)
def logMsg(self, msg, lvl=1):
self.className = self.__class__.__name__
utils.logMsg("%s %s" % (self.addonName, self.className), msg, 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("emby_%s.itemid" % currentFile)
tryCount = 0
while not itemId:
xbmc.sleep(200)
itemId = utils.window("emby_%s.itemid" % currentFile)
if tryCount == 20: # try 20 times or about 10 seconds
self.logMsg("Could not find itemId, cancelling playback report...", 1)
break
else: tryCount += 1
else:
self.logMsg("ONPLAYBACK_STARTED: %s itemid: %s" % (currentFile, itemId), 0)
# Only proceed if an itemId was found.
embyitem = "emby_%s" % currentFile
runtime = utils.window("%s.runtime" % embyitem)
refresh_id = utils.window("%s.refreshid" % embyitem)
playMethod = utils.window("%s.playmethod" % embyitem)
itemType = utils.window("%s.type" % embyitem)
utils.window('emby_skipWatched%s' % itemId, value="true")
seekTime = xbmcplayer.getTime()
# Get playback volume
volume_query = {
"jsonrpc": "2.0",
"id": 1,
"method": "Application.GetProperties",
"params": {
"properties": ["volume", "muted"]
}
}
result = xbmc.executeJSONRPC(json.dumps(volume_query))
result = json.loads(result)
result = result.get('result')
volume = result.get('volume')
muted = result.get('muted')
# Postdata structure to send to Emby server
url = "{server}/emby/Sessions/Playing"
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
tracks_query = {
"jsonrpc": "2.0",
"id": 1,
"method": "Player.GetProperties",
"params": {
"playerid": 1,
"properties": ["currentsubtitle","currentaudiostream","subtitleenabled"]
}
}
result = xbmc.executeJSONRPC(json.dumps(tracks_query))
result = json.loads(result)
result = result.get('result')
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("%s.indexMapping" % embyitem)
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
subindex = indexSubs - len(externalIndex) + audioTracks + 1
postdata['SubtitleStreamIndex'] = subindex
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, Kodi runtime: %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_info[currentFile] = data
self.logMsg("ADDING_FILE: %s" % self.played_info, 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_info.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",
"id": 1,
"method": "Application.GetProperties",
"params": {
"properties": ["volume", "muted"]
}
}
result = xbmc.executeJSONRPC(json.dumps(volume_query))
result = json.loads(result)
result = result.get('result')
volume = result.get('volume')
muted = result.get('muted')
# Postdata for the websocketclient report
postdata = {
'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
tracks_query = {
"jsonrpc": "2.0",
"id": 1,
"method": "Player.GetProperties",
"params": {
"playerid": 1,
"properties": ["currentsubtitle","currentaudiostream","subtitleenabled"]
}
}
result = xbmc.executeJSONRPC(json.dumps(tracks_query))
result = json.loads(result)
result = result.get('result')
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("emby_%s.indexMapping" % 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
subindex = [externalIndex[str(indexSubs)]] * 2
data['SubtitleStreamIndex'], postdata['SubtitleStreamIndex'] = subindex
else:
# Internal subtitle currently selected
subindex = [indexSubs - len(externalIndex) + audioTracks + 1] * 2
data['SubtitleStreamIndex'], postdata['SubtitleStreamIndex'] = subindex
else: # Direct paths enabled scenario or no external subtitles set
subindex = [indexSubs + audioTracks + 1] * 2
data['SubtitleStreamIndex'], postdata['SubtitleStreamIndex'] = subindex
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_info.get(currentFile):
self.played_info[currentFile]['paused'] = True
self.reportPlayback()
def onPlayBackResumed( self ):
currentFile = self.currentFile
self.logMsg("PLAYBACK_RESUMED: %s" % currentFile, 2)
if self.played_info.get(currentFile):
self.played_info[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_info.get(currentFile):
position = self.xbmcplayer.getTime()
self.played_info[currentFile]['currentPosition'] = position
self.reportPlayback()
def onPlayBackStopped( self ):
# Will be called when user stops xbmc playing a file
self.logMsg("ONPLAYBACK_STOPPED", 2)
xbmcgui.Window(10101).clearProperties()
self.logMsg("Clear playlist properties.")
self.stopAll()
def onPlayBackEnded( self ):
# Will be called when xbmc stops playing a file
self.logMsg("ONPLAYBACK_ENDED", 2)
self.stopAll()
def stopAll(self):
doUtils = self.doUtils
if not self.played_info:
return
self.logMsg("Played_information: %s" % self.played_info, 1)
# Process each items
for item in self.played_info:
data = self.played_info.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:
try:
percentComplete = (currentPosition * 10000000) / int(runtime)
except ZeroDivisionError:
# Runtime is 0.
percentComplete = 0
markPlayedAt = float(utils.settings('markPlayed')) / 100
self.logMsg(
"Percent complete: %s Mark played at: %s"
% (percentComplete, markPlayedAt), 1)
# Prevent manually mark as watched in Kodi monitor
utils.window('emby_skipWatched%s' % itemid, value="true")
self.stopPlayback(data)
# Stop transcoding
if playMethod == "Transcode":
self.logMsg("Transcoding for %s terminated." % itemid, 1)
deviceId = self.clientInfo.getDeviceId()
url = "{server}/emby/Videos/ActiveEncodings?DeviceId=%s" % deviceId
doUtils.downloadUrl(url, type="DELETE")
# Send the delete action to the server.
offerDelete = False
if type == "Episode" and utils.settings('deleteTV') == "true":
offerDelete = True
elif type == "Movie" and utils.settings('deleteMovies') == "true":
offerDelete = True
if utils.settings('offerDelete') != "true":
# Delete could be disabled, even if the subsetting is enabled.
offerDelete = False
if percentComplete >= markPlayedAt and offerDelete:
if utils.settings('skipConfirmDelete') != "true":
resp = xbmcgui.Dialog().yesno(
heading="Confirm delete",
line1="Delete file on Emby Server?")
if not resp:
self.logMsg("User skipped deletion.", 1)
continue
url = "{server}/emby/Items/%s?format=json" % itemid
self.logMsg("Deleting request: %s" % itemid)
doUtils.downloadUrl(url, type="DELETE")
self.played_info.clear()
def stopPlayback(self, data):
self.logMsg("stopPlayback called", 2)
itemId = data['item_id']
currentPosition = data['currentPosition']
positionTicks = int(currentPosition * 10000000)
url = "{server}/emby/Sessions/Playing/Stopped"
postdata = {
'ItemId': itemId,
'MediaSourceId': itemId,
'PositionTicks': positionTicks
}
self.doUtils.downloadUrl(url, postBody=postdata, type="POST")

397
resources/lib/playutils.py Normal file
View file

@ -0,0 +1,397 @@
# -*- coding: utf-8 -*-
#################################################################################################
import xbmc
import xbmcgui
import xbmcvfs
import clientinfo
import utils
#################################################################################################
class PlayUtils():
def __init__(self, item):
self.item = item
self.clientInfo = clientinfo.ClientInfo()
self.addonName = self.clientInfo.getAddonName()
self.userid = utils.window('emby_currUser')
self.server = utils.window('emby_server%s' % self.userid)
def logMsg(self, msg, lvl=1):
self.className = self.__class__.__name__
utils.logMsg("%s %s" % (self.addonName, self.className), msg, lvl)
def getPlayUrl(self):
item = self.item
playurl = None
if item['MediaSources'][0]['Protocol'] == "Http":
# Only play as http
self.logMsg("File protocol is http.", 1)
playurl = self.httpPlay()
utils.window('emby_%s.playmethod' % playurl, value="DirectStream")
elif self.isDirectPlay():
self.logMsg("File is direct playing.", 1)
playurl = self.directPlay()
playurl = playurl.encode('utf-8')
# Set playmethod property
utils.window('emby_%s.playmethod' % playurl, value="DirectPlay")
elif self.isDirectStream():
self.logMsg("File is direct streaming.", 1)
playurl = self.directStream()
# Set playmethod property
utils.window('emby_%s.playmethod' % playurl, value="DirectStream")
elif self.isTranscoding():
self.logMsg("File is transcoding.", 1)
playurl = self.transcoding()
# Set playmethod property
utils.window('emby_%s.playmethod' % playurl, value="Transcode")
return playurl
def httpPlay(self):
# Audio, Video, Photo
item = self.item
server = self.server
itemid = item['Id']
mediatype = item['MediaType']
if type == "Audio":
playurl = "%s/emby/Audio/%s/stream" % (server, itemid)
else:
playurl = "%s/emby/Videos/%s/stream?static=true" % (server, itemid)
return playurl
def isDirectPlay(self):
item = self.item
# Requirement: Filesystem, Accessible path
if utils.settings('playFromStream') == "true":
# User forcing to play via HTTP
self.logMsg("Can't direct play, play from HTTP enabled.", 1)
return False
if (utils.settings('transcodeH265') == "true" and
result['MediaSources'][0]['Name'].startswith("1080P/H265")):
# Avoid H265 1080p
self.logMsg("Option to transcode 1080P/H265 enabled.", 1)
return False
canDirectPlay = item['MediaSources'][0]['SupportsDirectPlay']
# Make sure direct play is supported by the server
if not canDirectPlay:
self.logMsg("Can't direct play, server doesn't allow/support it.", 1)
return False
location = item['LocationType']
if location == "FileSystem":
# Verify the path
if not self.fileExists():
self.logMsg("Unable to direct play.")
try:
count = int(utils.settings('failCount'))
except ValueError:
count = 0
self.logMsg("Direct play failed: %s times." % count, 1)
if count < 2:
# Let the user know that direct play failed
utils.settings('failCount', value=str(count+1))
xbmcgui.Dialog().notification(
heading="Emby server",
message="Unable to direct play.",
icon="special://home/addons/plugin.video.emby/icon.png",
sound=False)
elif utils.settings('playFromStream') != "true":
# Permanently set direct stream as true
utils.settings('playFromStream', value="true")
utils.settings('failCount', value="0")
xbmcgui.Dialog().notification(
heading="Emby server",
message=("Direct play failed 3 times. Enabled play "
"from HTTP in the add-on settings."),
icon="special://home/addons/plugin.video.emby/icon.png",
sound=False)
return False
return True
def directPlay(self):
item = self.item
try:
playurl = item['MediaSources'][0]['Path']
except (IndexError, KeyError):
playurl = item['Path']
if item.get('VideoType'):
# Specific format modification
type = item['VideoType']
if type == "Dvd":
playurl = "%s/VIDEO_TS/VIDEO_TS.IFO" % playurl
elif type == "Bluray":
playurl = "%s/BDMV/index.bdmv" % playurl
# Assign network protocol
if playurl.startswith('\\\\'):
playurl = playurl.replace("\\\\", "smb://")
playurl = playurl.replace("\\", "/")
if "apple.com" in playurl:
USER_AGENT = "QuickTime/7.7.4"
playurl += "?|User-Agent=%s" % USER_AGENT
return playurl
def fileExists(self):
if 'Path' not in self.item:
# File has no path defined in server
return False
# Convert path to direct play
path = self.directPlay()
self.logMsg("Verifying path: %s" % path, 1)
if xbmcvfs.exists(path):
self.logMsg("Path exists.", 1)
return True
elif ":" not in path:
self.logMsg("Can't verify path, assumed linux. Still try to direct play.", 1)
return True
else:
self.logMsg("Failed to find file.")
return False
def isDirectStream(self):
item = self.item
if (utils.settings('transcodeH265') == "true" and
result['MediaSources'][0]['Name'].startswith("1080P/H265")):
# Avoid H265 1080p
self.logMsg("Option to transcode 1080P/H265 enabled.", 1)
return False
# Requirement: BitRate, supported encoding
canDirectStream = item['MediaSources'][0]['SupportsDirectStream']
# Make sure the server supports it
if not canDirectStream:
return False
# Verify the bitrate
if not self.isNetworkSufficient():
self.logMsg("The network speed is insufficient to direct stream file.", 1)
return False
return True
def directStream(self):
item = self.item
server = self.server
itemid = item['Id']
type = item['Type']
if 'Path' in item and item['Path'].endswith('.strm'):
# Allow strm loading when direct streaming
playurl = self.directPlay()
elif type == "Audio":
playurl = "%s/emby/Audio/%s/stream.mp3" % (server, itemid)
else:
playurl = "%s/emby/Videos/%s/stream?static=true" % (server, itemid)
return playurl
def isNetworkSufficient(self):
settings = self.getBitrate()*1000
try:
sourceBitrate = int(self.item['MediaSources'][0]['Bitrate'])
except (KeyError, TypeError):
self.logMsg("Bitrate value is missing.", 1)
else:
self.logMsg("The add-on settings bitrate is: %s, the video bitrate required is: %s"
% (settings, sourceBitrate), 1)
if settings < sourceBitrate:
return False
return True
def isTranscoding(self):
item = self.item
canTranscode = item['MediaSources'][0]['SupportsTranscoding']
# Make sure the server supports it
if not canTranscode:
return False
return True
def transcoding(self):
item = self.item
if 'Path' in item and item['Path'].endswith('.strm'):
# Allow strm loading when transcoding
playurl = self.directPlay()
else:
itemid = item['Id']
deviceId = self.clientInfo.getDeviceId()
playurl = (
"%s/emby/Videos/%s/master.m3u8?MediaSourceId=%s"
% (self.server, itemid, itemid)
)
playurl = (
"%s&VideoCodec=h264&AudioCodec=ac3&MaxAudioChannels=6&deviceId=%s&VideoBitrate=%s"
% (playurl, deviceId, self.getBitrate()*1000))
return playurl
def getBitrate(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 audioSubsPref(self, url):
# For transcoding only
# Present the list of audio to select from
audioStreamsList = {}
audioStreams = []
audioStreamsChannelsList = {}
subtitleStreamsList = {}
subtitleStreams = ['No subtitles']
selectAudioIndex = ""
selectSubsIndex = ""
playurlprefs = "%s" % url
item = self.item
try:
mediasources = item['MediaSources'][0]
mediastreams = mediasources['MediaStreams']
except (TypeError, KeyError, IndexError):
return
for stream in mediastreams:
# Since Emby returns all possible tracks together, have to sort them.
index = stream['Index']
type = stream['Type']
if 'Audio' in type:
codec = stream['Codec']
channelLayout = stream.get('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:
if stream['IsExternal']:
continue
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['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.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

471
resources/lib/userclient.py Normal file
View file

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

487
resources/lib/utils.py Normal file
View file

@ -0,0 +1,487 @@
# -*- coding: utf-8 -*-
#################################################################################################
import cProfile
import inspect
import pstats
import sqlite3
import time
import unicodedata
import xml.etree.ElementTree as etree
import xbmc
import xbmcaddon
import xbmcgui
import xbmcvfs
#################################################################################################
def logMsg(title, msg, level=1):
# Get the logLevel set in UserClient
try:
logLevel = int(window('emby_logLevel'))
except ValueError:
logLevel = 0
if logLevel >= level:
if logLevel == 2: # inspect.stack() is expensive
try:
xbmc.log("%s -> %s : %s" % (title, inspect.stack()[1][3], msg))
except UnicodeEncodeError:
xbmc.log("%s -> %s : %s" % (title, inspect.stack()[1][3], msg.encode('utf-8')))
else:
try:
xbmc.log("%s -> %s" % (title, msg))
except UnicodeEncodeError:
xbmc.log("%s -> %s" % (title, msg.encode('utf-8')))
def window(property, value=None, clear=False, windowid=10000):
# Get or set window property
WINDOW = xbmcgui.Window(windowid)
if clear:
WINDOW.clearProperty(property)
elif value is not None:
WINDOW.setProperty(property, value)
else:
return WINDOW.getProperty(property)
def settings(setting, value=None):
# Get or add addon setting
addon = xbmcaddon.Addon(id='plugin.video.emby')
if value is not None:
addon.setSetting(setting, value)
else:
return addon.getSetting(setting)
def language(stringid):
# Central string retrieval
addon = xbmcaddon.Addon(id='plugin.video.emby')
string = addon.getLocalizedString(stringid)
return string
def kodiSQL(type="video"):
if type == "emby":
dbPath = xbmc.translatePath("special://database/emby.db").decode('utf-8')
elif type == "music":
dbPath = getKodiMusicDBPath()
elif type == "texture":
dbPath = xbmc.translatePath("special://database/Textures13.db").decode('utf-8')
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 reset():
dialog = xbmcgui.Dialog()
resp = dialog.yesno("Warning", "Are you sure you want to reset your local Kodi database?")
if resp == 0:
return
# first stop any db sync
window('emby_shouldStop', value="true")
count = 10
while window('emby_dbScan') == "true":
logMsg("EMBY", "Sync is running, will retry: %s..." % count)
count -= 1
if count == 0:
dialog.ok("Warning", "Could not stop the database from running. Try again.")
return
xbmc.sleep(1000)
# Clean up the playlists
path = xbmc.translatePath("special://profile/playlists/video/").decode('utf-8')
dirs, files = xbmcvfs.listdir(path)
for file in files:
if file.startswith('Emby'):
xbmcvfs.delete("%s%s" % (path, file))
# Clean up the video nodes
import shutil
path = xbmc.translatePath("special://profile/library/video/").decode('utf-8')
dirs, files = xbmcvfs.listdir(path)
for dir in dirs:
if dir.startswith('Emby'):
shutil.rmtree("%s%s" % (path, dir))
for file in files:
if file.startswith('emby'):
xbmcvfs.delete("%s%s" % (path, file))
# Wipe the kodi databases
logMsg("EMBY", "Resetting the Kodi video database.")
connection = kodiSQL('video')
cursor = connection.cursor()
cursor.execute('SELECT tbl_name FROM sqlite_master WHERE type="table"')
rows = cursor.fetchall()
for row in rows:
tablename = row[0]
if tablename != "version":
cursor.execute("DELETE FROM " + tablename)
connection.commit()
cursor.close()
if settings('disableMusic') != "true":
logMsg("EMBY", "Resetting the Kodi music database.")
connection = kodiSQL('music')
cursor = connection.cursor()
cursor.execute('SELECT tbl_name FROM sqlite_master WHERE type="table"')
rows = cursor.fetchall()
for row in rows:
tablename = row[0]
if tablename != "version":
cursor.execute("DELETE FROM " + tablename)
connection.commit()
cursor.close()
# Wipe the emby database
logMsg("EMBY", "Resetting the Emby database.")
connection = kodiSQL('emby')
cursor = connection.cursor()
cursor.execute('SELECT tbl_name FROM sqlite_master WHERE type="table"')
rows = cursor.fetchall()
for row in rows:
tablename = row[0]
if tablename != "version":
cursor.execute("DELETE FROM " + tablename)
connection.commit()
cursor.close()
# reset the install run flag
settings('SyncInstallRunDone', value="false")
# Remove emby info
resp = dialog.yesno("Warning", "Reset all Emby Addon settings?")
if resp == 1:
# Delete the settings
addon = xbmcaddon.Addon()
addondir = xbmc.translatePath(addon.getAddonInfo('profile')).decode('utf-8')
dataPath = "%ssettings.xml" % addondir
xbmcvfs.delete(dataPath)
logMsg("EMBY", "Deleting: settings.xml", 1)
dialog.ok(
heading="Emby for Kodi",
line1="Database reset has completed, Kodi will now restart to apply the changes.")
xbmc.executebuiltin('RestartApp')
def startProfiling():
pr = cProfile.Profile()
pr.enable()
return pr
def stopProfiling(pr, profileName):
pr.disable()
ps = pstats.Stats(pr)
profiles = xbmc.translatePath("%sprofiles/"
% xbmcaddon.Addon().getAddonInfo('profile')).decode('utf-8')
if not xbmcvfs.exists(profiles):
# Create the profiles folder
xbmcvfs.mkdir(profiles)
timestamp = time.strftime("%Y-%m-%d %H-%M-%S")
profile = "%s%s_profile_(%s).tab" % (profiles, profileName, timestamp)
f = open(profile, 'wb')
f.write("NumbCalls\tTotalTime\tCumulativeTime\tFunctionName\tFileName\r\n")
for (key, value) in ps.stats.items():
(filename, count, func_name) = key
(ccalls, ncalls, total_time, cumulative_time, callers) = value
try:
f.write(
"%s\t%s\t%s\t%s\t%s\r\n"
% (ncalls, "{:10.4f}".format(total_time),
"{:10.4f}".format(cumulative_time), func_name, filename))
except ValueError:
f.write(
"%s\t%s\t%s\t%s\t%s\r\n"
% (ncalls, "{0}".format(total_time),
"{0}".format(cumulative_time), func_name, filename))
f.close()
def 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 normalize_string(text):
# For theme media, do not modify unless
# modified in TV Tunes
text = text.replace(":", "")
text = text.replace("/", "-")
text = text.replace("\\", "-")
text = text.replace("<", "")
text = text.replace(">", "")
text = text.replace("*", "")
text = text.replace("?", "")
text = text.replace('|', "")
text = text.strip()
# Remove dots from the last character as windows can not have directories
# with dots at the end
text = text.rstrip('.')
text = unicodedata.normalize('NFKD', unicode(text, 'utf-8')).encode('ascii', 'ignore')
return text
def indent(elem, level=0):
# Prettify xml trees
i = "\n" + level*" "
if len(elem):
if not elem.text or not elem.text.strip():
elem.text = i + " "
if not elem.tail or not elem.tail.strip():
elem.tail = i
for elem in elem:
indent(elem, level+1)
if not elem.tail or not elem.tail.strip():
elem.tail = i
else:
if level and (not elem.tail or not elem.tail.strip()):
elem.tail = i
def sourcesXML():
# To make Master lock compatible
path = xbmc.translatePath("special://profile/").decode('utf-8')
xmlpath = "%ssources.xml" % path
try:
xmlparse = etree.parse(xmlpath)
except: # Document is blank or missing
root = etree.Element('sources')
else:
root = xmlparse.getroot()
video = root.find('video')
if video is None:
video = etree.SubElement(root, 'video')
etree.SubElement(video, 'default', attrib={'pathversion': "1"})
# Add elements
for i in range(1, 3):
for source in root.findall('.//path'):
if source.text == "smb://embydummy/dummypath%s/" % i:
# Already there, skip
break
else:
source = etree.SubElement(video, 'source')
etree.SubElement(source, 'name').text = "Emby"
etree.SubElement(source, 'path', attrib={'pathversion': "1"}).text = (
"smb://embydummy/dummypath%s/" % i
)
etree.SubElement(source, 'allowsharing').text = "true"
# Prettify and write to file
try:
indent(root)
except: pass
etree.ElementTree(root).write(xmlpath)
def passwordsXML():
# To add network credentials
path = xbmc.translatePath("special://userdata/").decode('utf-8')
xmlpath = "%spasswords.xml" % path
try:
xmlparse = etree.parse(xmlpath)
except: # Document is blank or missing
root = etree.Element('passwords')
else:
root = xmlparse.getroot()
dialog = xbmcgui.Dialog()
credentials = settings('networkCreds')
if credentials:
# Present user with options
option = dialog.select("Modify/Remove network credentials", ["Modify", "Remove"])
if option < 0:
# User cancelled dialog
return
elif option == 1:
# User selected remove
iterator = root.getiterator('passwords')
for paths in iterator:
for path in paths:
if path.find('.//from').text == "smb://%s/" % credentials:
paths.remove(path)
logMsg("EMBY", "Successfully removed credentials for: %s"
% credentials, 1)
etree.ElementTree(root).write(xmlpath)
break
else:
logMsg("EMBY", "Failed to find saved server: %s in passwords.xml" % credentials, 1)
settings('networkCreds', value="")
xbmcgui.Dialog().notification(
heading="Emby for Kodi",
message="%s removed from passwords.xml!" % credentials,
icon="special://home/addons/plugin.video.emby/icon.png",
time=1000,
sound=False)
return
elif option == 0:
# User selected to modify
server = dialog.input("Modify the computer name or ip address", credentials)
if not server:
return
else:
# No credentials added
dialog.ok(
heading="Network credentials",
line1= (
"Input the server name or IP address as indicated in your emby library paths. "
'For example, the server name: \\\\SERVER-PC\\path\\ is "SERVER-PC".'))
server = dialog.input("Enter the server name or IP address", settings('ipaddress'))
if not server:
return
# Network username
user = dialog.input("Enter the network username")
if not user:
return
# Network password
password = dialog.input(
heading="Enter the network password",
option=xbmcgui.ALPHANUM_HIDE_INPUT)
if not password:
return
# Add elements
for path in root.findall('.//path'):
if path.find('.//from').text.lower() == "smb://%s/" % server.lower():
# Found the server, rewrite credentials
path.find('.//to').text = "smb://%s:%s@%s/" % (user, password, server)
break
else:
# Server not found, add it.
path = etree.SubElement(root, 'path')
etree.SubElement(path, 'from', attrib={'pathversion': "1"}).text = "smb://%s/" % server
topath = "smb://%s:%s@%s/" % (user, password, server)
etree.SubElement(path, 'to', attrib={'pathversion': "1"}).text = topath
# Force Kodi to see the credentials without restarting
xbmcvfs.exists(topath)
# Add credentials
settings('networkCreds', value="%s" % server)
logMsg("EMBY", "Added server: %s to passwords.xml" % server, 1)
# Prettify and write to file
try:
indent(root)
except: pass
etree.ElementTree(root).write(xmlpath)
dialog.notification(
heading="Emby for Kodi",
message="%s added to passwords.xml!" % server,
icon="special://home/addons/plugin.video.emby/icon.png",
time=1000,
sound=False)
def playlistXSP(mediatype, tagname, viewtype="", delete=False):
# Tagname is in unicode - actions: add or delete
tagname = tagname.encode('utf-8')
cleantagname = normalize_nodes(tagname)
path = xbmc.translatePath("special://profile/playlists/video/").decode('utf-8')
if viewtype == "mixed":
plname = "%s - %s" % (tagname, mediatype)
xsppath = "%sEmby %s - %s.xsp" % (path, cleantagname, mediatype)
else:
plname = tagname
xsppath = "%sEmby %s.xsp" % (path, cleantagname)
# Create the playlist directory
if not xbmcvfs.exists(path):
xbmcvfs.mkdirs(path)
# Only add the playlist if it doesn't already exists
if xbmcvfs.exists(xsppath):
if delete:
xbmcvfs.delete(xsppath)
logMsg("EMBY", "Successfully removed playlist: %s." % tagname, 1)
return
# Using write process since there's no guarantee the xml declaration works with etree
itemtypes = {
'homevideos': "movies"
}
f = open(xsppath, 'w')
f.write(
'<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>\n'
'<smartplaylist type="%s">\n\t'
'<name>Emby %s</name>\n\t'
'<match>all</match>\n\t'
'<rule field="tag" operator="is">\n\t\t'
'<value>%s</value>\n\t'
'</rule>'
% (itemtypes.get(mediatype, mediatype), plname, tagname))
f.close()
logMsg("EMBY", "Successfully added playlist: %s" % tagname)

344
resources/lib/videonodes.py Normal file
View file

@ -0,0 +1,344 @@
# -*- coding: utf-8 -*-
#################################################################################################
import shutil
import xml.etree.ElementTree as etree
import xbmc
import xbmcaddon
import xbmcvfs
import clientinfo
import utils
#################################################################################################
class VideoNodes(object):
def __init__(self):
clientInfo = clientinfo.ClientInfo()
self.addonName = clientInfo.getAddonName()
self.kodiversion = int(xbmc.getInfoLabel("System.BuildVersion")[:2])
def logMsg(self, msg, lvl=1):
className = self.__class__.__name__
utils.logMsg("%s %s" % (self.addonName, className), msg, lvl)
def commonRoot(self, order, label, tagname, roottype=1):
if roottype == 0:
# Index
root = etree.Element('node', attrib={'order': "%s" % order})
elif roottype == 1:
# Filter
root = etree.Element('node', attrib={'order': "%s" % order, 'type': "filter"})
etree.SubElement(root, 'match').text = "all"
# Add tag rule
rule = etree.SubElement(root, 'rule', attrib={'field': "tag", 'operator': "is"})
etree.SubElement(rule, 'value').text = tagname
else:
# Folder
root = etree.Element('node', attrib={'order': "%s" % order, 'type': "folder"})
etree.SubElement(root, 'label').text = label
etree.SubElement(root, 'icon').text = "special://home/addons/plugin.video.emby/icon.png"
return root
def viewNode(self, indexnumber, tagname, mediatype, viewtype, delete=False):
kodiversion = self.kodiversion
if mediatype == "homevideos":
# Treat homevideos as movies
mediatype = "movies"
tagname = tagname.encode('utf-8')
cleantagname = utils.normalize_nodes(tagname)
if viewtype == "mixed":
dirname = "%s - %s" % (cleantagname, mediatype)
else:
dirname = cleantagname
path = xbmc.translatePath("special://profile/library/video/").decode('utf-8')
nodepath = xbmc.translatePath(
"special://profile/library/video/Emby - %s/" % dirname).decode('utf-8')
# Verify the video directory
if not xbmcvfs.exists(path):
shutil.copytree(
src=xbmc.translatePath("special://xbmc/system/library/video/").decode('utf-8'),
dst=xbmc.translatePath("special://profile/library/video/").decode('utf-8'))
xbmcvfs.exists(path)
# Create the node directory
if not xbmcvfs.exists(nodepath):
# We need to copy over the default items
xbmcvfs.mkdirs(nodepath)
else:
if delete:
dirs, files = xbmcvfs.listdir(nodepath)
for file in files:
xbmcvfs.delete(nodepath + file)
self.logMsg("Sucessfully removed videonode: %s." % tagname, 1)
return
# Create index entry
nodeXML = "%sindex.xml" % nodepath
# Set windows property
path = "library://video/Emby - %s/" % dirname
for i in range(1, indexnumber):
# Verify to make sure we don't create duplicates
if utils.window('Emby.nodes.%s.index' % i) == path:
return
utils.window('Emby.nodes.%s.index' % indexnumber, value=path)
# Root
root = self.commonRoot(order=0, label=dirname, tagname=tagname, roottype=0)
try:
utils.indent(root)
except: pass
etree.ElementTree(root).write(nodeXML)
nodetypes = {
'1': "all",
'2': "recent",
'3': "recentepisodes",
'4': "inprogress",
'5': "inprogressepisodes",
'6': "unwatched",
'7': "nextupepisodes",
'8': "sets",
'9': "genres",
'10': "random",
'11': "recommended"
}
mediatypes = {
# label according to nodetype per mediatype
'movies': {
'1': tagname,
'2': 30174,
'4': 30177,
'6': 30189,
'8': 20434,
'9': 135,
'10': 30229,
'11': 30230},
'tvshows': {
'1': tagname,
'2': 30170,
'3': 30175,
'4': 30171,
'5': 30178,
'7': 30179,
'9': 135,
'10': 30229,
'11': 30230},
}
nodes = mediatypes[mediatype]
for node in nodes:
nodetype = nodetypes[node]
nodeXML = "%s%s_%s.xml" % (nodepath, cleantagname, nodetype)
# Get label
stringid = nodes[node]
if node != '1':
label = utils.language(stringid)
if not label:
label = xbmc.getLocalizedString(stringid)
else:
label = stringid
# Set window properties
if nodetype == "nextupepisodes":
# Custom query
path = "plugin://plugin.video.emby/?id=%s&mode=nextup&limit=25" % tagname
elif kodiversion == 14 and nodetype == "recentepisodes":
# Custom query
path = "plugin://plugin.video.emby/?id=%s&mode=recentepisodes&limit=25" % tagname
elif kodiversion == 14 and nodetype == "inprogressepisodes":
# Custom query
path = "plugin://plugin.video.emby/?id=%s&mode=inprogressepisodes&limit=25"% tagname
else:
path = "library://video/Emby - %s/%s_%s.xml" % (dirname, cleantagname, nodetype)
windowpath = "ActivateWindow(Video, %s, return)" % path
if nodetype == "all":
if viewtype == "mixed":
templabel = dirname
else:
templabel = label
embynode = "Emby.nodes.%s" % indexnumber
utils.window('%s.title' % embynode, value=templabel)
utils.window('%s.path' % embynode, value=windowpath)
utils.window('%s.content' % embynode, value=path)
utils.window('%s.type' % embynode, value=mediatype)
else:
embynode = "Emby.nodes.%s.%s" % (indexnumber, nodetype)
utils.window('%s.title' % embynode, value=label)
utils.window('%s.path' % embynode, value=windowpath)
utils.window('%s.content' % embynode, value=path)
if xbmcvfs.exists(nodeXML):
# Don't recreate xml if already exists
continue
# Create the root
if nodetype == "nextupepisodes" or (kodiversion == 14 and
nodetype in ('recentepisodes', 'inprogressepisodes')):
# Folder type with plugin path
root = self.commonRoot(order=node, label=label, tagname=tagname, roottype=2)
etree.SubElement(root, 'path').text = path
etree.SubElement(root, 'content').text = "episodes"
else:
root = self.commonRoot(order=node, label=label, tagname=tagname)
if nodetype in ('recentepisodes', 'inprogressepisodes'):
etree.SubElement(root, 'content').text = "episodes"
else:
etree.SubElement(root, 'content').text = mediatype
limit = "25"
# Elements per nodetype
if nodetype == "all":
etree.SubElement(root, 'order', {'direction': "ascending"}).text = "sorttitle"
elif nodetype == "recent":
etree.SubElement(root, 'order', {'direction': "descending"}).text = "dateadded"
etree.SubElement(root, 'limit').text = limit
rule = etree.SubElement(root, 'rule', {'field': "playcount", 'operator': "is"})
etree.SubElement(rule, 'value').text = "0"
elif nodetype == "inprogress":
etree.SubElement(root, 'rule', {'field': "inprogress", 'operator': "true"})
etree.SubElement(root, 'limit').text = limit
elif nodetype == "genres":
etree.SubElement(root, 'order', {'direction': "ascending"}).text = "sorttitle"
etree.SubElement(root, 'group').text = "genres"
elif nodetype == "unwatched":
etree.SubElement(root, 'order', {'direction': "ascending"}).text = "sorttitle"
rule = etree.SubElement(root, "rule", {'field': "playcount", 'operator': "is"})
etree.SubElement(rule, 'value').text = "0"
elif nodetype == "sets":
etree.SubElement(root, 'order', {'direction': "ascending"}).text = "sorttitle"
etree.SubElement(root, 'group').text = "sets"
elif nodetype == "random":
etree.SubElement(root, 'order', {'direction': "ascending"}).text = "random"
etree.SubElement(root, 'limit').text = limit
elif nodetype == "recommended":
etree.SubElement(root, 'order', {'direction': "descending"}).text = "rating"
etree.SubElement(root, 'limit').text = limit
rule = etree.SubElement(root, 'rule', {'field': "playcount", 'operator': "is"})
etree.SubElement(rule, 'value').text = "0"
rule2 = etree.SubElement(root, 'rule',
attrib={'field': "rating", 'operator': "greaterthan"})
etree.SubElement(rule2, 'value').text = "7"
elif nodetype == "recentepisodes":
# Kodi Isengard, Jarvis
etree.SubElement(root, 'order', {'direction': "descending"}).text = "dateadded"
etree.SubElement(root, 'limit').text = limit
rule = etree.SubElement(root, 'rule', {'field': "playcount", 'operator': "is"})
etree.SubElement(rule, 'value').text = "0"
elif nodetype == "inprogressepisodes":
# Kodi Isengard, Jarvis
etree.SubElement(root, 'limit').text = "25"
rule = etree.SubElement(root, 'rule',
attrib={'field': "inprogress", 'operator':"true"})
try:
utils.indent(root)
except: pass
etree.ElementTree(root).write(nodeXML)
def singleNode(self, indexnumber, tagname, mediatype, itemtype):
tagname = tagname.encode('utf-8')
cleantagname = utils.normalize_nodes(tagname)
nodepath = xbmc.translatePath("special://profile/library/video/").decode('utf-8')
nodeXML = "%semby_%s.xml" % (nodepath, cleantagname)
path = "library://video/emby_%s.xml" % (cleantagname)
windowpath = "ActivateWindow(Video, %s, return)" % path
# Create the video node directory
if not xbmcvfs.exists(nodepath):
# We need to copy over the default items
shutil.copytree(
src=xbmc.translatePath("special://xbmc/system/library/video").decode('utf-8'),
dst=xbmc.translatePath("special://profile/library/video").decode('utf-8'))
xbmcvfs.exists(path)
labels = {
'Favorite movies': 30180,
'Favorite tvshows': 30181,
'channels': 30173
}
label = utils.language(labels[tagname])
embynode = "Emby.nodes.%s" % indexnumber
utils.window('%s.title' % embynode, value=label)
utils.window('%s.path' % embynode, value=windowpath)
utils.window('%s.content' % embynode, value=path)
utils.window('%s.type' % embynode, value=itemtype)
if xbmcvfs.exists(nodeXML):
# Don't recreate xml if already exists
return
if itemtype == "channels":
root = self.commonRoot(order=1, label=label, tagname=tagname, roottype=2)
etree.SubElement(root, 'path').text = "plugin://plugin.video.emby/?id=0&mode=channels"
else:
root = self.commonRoot(order=1, label=label, tagname=tagname)
etree.SubElement(root, 'order', {'direction': "ascending"}).text = "sorttitle"
etree.SubElement(root, 'content').text = mediatype
try:
utils.indent(root)
except: pass
etree.ElementTree(root).write(nodeXML)
def clearProperties(self):
self.logMsg("Clearing nodes properties.", 1)
embyprops = utils.window('Emby.nodes.total')
propnames = [
"index","path","title","content",
"inprogress.content","inprogress.title",
"inprogress.content","inprogress.path",
"nextepisodes.title","nextepisodes.content",
"nextepisodes.path","unwatched.title",
"unwatched.content","unwatched.path",
"recent.title","recent.content","recent.path",
"recentepisodes.title","recentepisodes.content",
"recentepisodes.path","inprogressepisodes.title",
"inprogressepisodes.content","inprogressepisodes.path"
]
if embyprops:
totalnodes = int(embyprops)
for i in range(totalnodes):
for prop in propnames:
utils.window('Emby.nodes.%s.%s' % (str(i), prop), clear=True)