Merge pull request #31 from Meta-Man/dev2

Dev2
This commit is contained in:
Ian Mclaughlin 2016-04-12 15:50:53 +01:00
commit bec455e8f7
24 changed files with 12471 additions and 12835 deletions

View file

@ -1,159 +1,158 @@
# -*- coding: utf-8 -*-
#################################################################################################
import os
import sys
import urlparse
import xbmc
import xbmcaddon
import xbmcgui
addon_ = xbmcaddon.Addon(id='plugin.video.emby')
addon_path = addon_.getAddonInfo('path').decode('utf-8')
base_resource = xbmc.translatePath(os.path.join(addon_path, 'resources', 'lib')).decode('utf-8')
sys.path.append(base_resource)
import artwork
import utils
import clientinfo
import downloadutils
import librarysync
import read_embyserver as embyserver
import embydb_functions as embydb
import kodidb_functions as kodidb
import musicutils as musicutils
import api
def logMsg(msg, lvl=1):
utils.logMsg("%s %s" % ("EMBY", "Contextmenu"), msg, lvl)
#Kodi contextmenu item to configure the emby settings
#for now used to set ratings but can later be used to sync individual items etc.
if __name__ == '__main__':
itemid = xbmc.getInfoLabel("ListItem.DBID").decode("utf-8")
itemtype = xbmc.getInfoLabel("ListItem.DBTYPE").decode("utf-8")
emby = embyserver.Read_EmbyServer()
embyid = ""
if not itemtype and xbmc.getCondVisibility("Container.Content(albums)"): itemtype = "album"
if not itemtype and xbmc.getCondVisibility("Container.Content(artists)"): itemtype = "artist"
if not itemtype and xbmc.getCondVisibility("Container.Content(songs)"): itemtype = "song"
if not itemtype and xbmc.getCondVisibility("Container.Content(pictures)"): itemtype = "picture"
if (not itemid or itemid == "-1") and xbmc.getInfoLabel("ListItem.Property(embyid)"):
embyid = xbmc.getInfoLabel("ListItem.Property(embyid)")
else:
embyconn = utils.kodiSQL('emby')
embycursor = embyconn.cursor()
emby_db = embydb.Embydb_Functions(embycursor)
item = emby_db.getItem_byKodiId(itemid, itemtype)
embycursor.close()
if item: embyid = item[0]
logMsg("Contextmenu opened for embyid: %s - itemtype: %s" %(embyid,itemtype))
if embyid:
item = emby.getItem(embyid)
API = api.API(item)
userdata = API.getUserData()
likes = userdata['Likes']
favourite = userdata['Favorite']
options=[]
if likes == True:
#clear like for the item
options.append(utils.language(30402))
if likes == False or likes == None:
#Like the item
options.append(utils.language(30403))
if likes == True or likes == None:
#Dislike the item
options.append(utils.language(30404))
if favourite == False:
#Add to emby favourites
options.append(utils.language(30405))
if favourite == True:
#Remove from emby favourites
options.append(utils.language(30406))
if itemtype == "song":
#Set custom song rating
options.append(utils.language(30407))
#delete item
options.append(utils.language(30409))
#addon settings
options.append(utils.language(30408))
#display select dialog and process results
header = utils.language(30401)
ret = xbmcgui.Dialog().select(header, options)
if ret != -1:
if options[ret] == utils.language(30402):
emby.updateUserRating(embyid, deletelike=True)
if options[ret] == utils.language(30403):
emby.updateUserRating(embyid, like=True)
if options[ret] == utils.language(30404):
emby.updateUserRating(embyid, like=False)
if options[ret] == utils.language(30405):
emby.updateUserRating(embyid, favourite=True)
if options[ret] == utils.language(30406):
emby.updateUserRating(embyid, favourite=False)
if options[ret] == utils.language(30407):
kodiconn = utils.kodiSQL('music')
kodicursor = kodiconn.cursor()
query = ' '.join(("SELECT rating", "FROM song", "WHERE idSong = ?" ))
kodicursor.execute(query, (itemid,))
currentvalue = int(round(float(kodicursor.fetchone()[0]),0))
newvalue = xbmcgui.Dialog().numeric(0, "Set custom song rating (0-5)", str(currentvalue))
if newvalue:
newvalue = int(newvalue)
if newvalue > 5: newvalue = "5"
if utils.settings('enableUpdateSongRating') == "true":
musicutils.updateRatingToFile(newvalue, API.getFilePath())
if utils.settings('enableExportSongRating') == "true":
like, favourite, deletelike = musicutils.getEmbyRatingFromKodiRating(newvalue)
emby.updateUserRating(embyid, like, favourite, deletelike)
query = ' '.join(( "UPDATE song","SET rating = ?", "WHERE idSong = ?" ))
kodicursor.execute(query, (newvalue,itemid,))
kodiconn.commit()
if options[ret] == utils.language(30408):
#Open addon settings
xbmc.executebuiltin("Addon.OpenSettings(plugin.video.emby)")
if options[ret] == utils.language(30409):
#delete item from the server
delete = True
if utils.settings('skipContextMenu') != "true":
resp = xbmcgui.Dialog().yesno(
heading="Confirm delete",
line1=("Delete file from Emby Server? This will "
"also delete the file(s) from disk!"))
if not resp:
logMsg("User skipped deletion for: %s." % embyid, 1)
delete = False
if delete:
import downloadutils
doUtils = downloadutils.DownloadUtils()
url = "{server}/emby/Items/%s?format=json" % embyid
logMsg("Deleting request: %s" % embyid, 0)
doUtils.downloadUrl(url, type="DELETE")
'''if utils.settings('skipContextMenu') != "true":
if xbmcgui.Dialog().yesno(
heading="Confirm delete",
line1=("Delete file on Emby Server? This will "
"also delete the file(s) from disk!")):
import downloadutils
doUtils = downloadutils.DownloadUtils()
url = "{server}/emby/Items/%s?format=json" % embyid
doUtils.downloadUrl(url, type="DELETE")'''
xbmc.sleep(500)
# -*- coding: utf-8 -*-
#################################################################################################
import os
import sys
import urlparse
import xbmc
import xbmcaddon
import xbmcgui
addon_ = xbmcaddon.Addon(id='plugin.video.emby')
addon_path = addon_.getAddonInfo('path').decode('utf-8')
base_resource = xbmc.translatePath(os.path.join(addon_path, 'resources', 'lib')).decode('utf-8')
sys.path.append(base_resource)
import artwork
import utils
import clientinfo
import downloadutils
import librarysync
import read_embyserver as embyserver
import embydb_functions as embydb
import kodidb_functions as kodidb
import musicutils as musicutils
import api
def logMsg(msg, lvl=1):
utils.logMsg("%s %s" % ("EMBY", "Contextmenu"), msg, lvl)
#Kodi contextmenu item to configure the emby settings
#for now used to set ratings but can later be used to sync individual items etc.
if __name__ == '__main__':
itemid = xbmc.getInfoLabel("ListItem.DBID").decode("utf-8")
itemtype = xbmc.getInfoLabel("ListItem.DBTYPE").decode("utf-8")
emby = embyserver.Read_EmbyServer()
embyid = ""
if not itemtype and xbmc.getCondVisibility("Container.Content(albums)"): itemtype = "album"
if not itemtype and xbmc.getCondVisibility("Container.Content(artists)"): itemtype = "artist"
if not itemtype and xbmc.getCondVisibility("Container.Content(songs)"): itemtype = "song"
if not itemtype and xbmc.getCondVisibility("Container.Content(pictures)"): itemtype = "picture"
if (not itemid or itemid == "-1") and xbmc.getInfoLabel("ListItem.Property(embyid)"):
embyid = xbmc.getInfoLabel("ListItem.Property(embyid)")
else:
embyconn = utils.kodiSQL('emby')
embycursor = embyconn.cursor()
emby_db = embydb.Embydb_Functions(embycursor)
item = emby_db.getItem_byKodiId(itemid, itemtype)
embycursor.close()
if item: embyid = item[0]
logMsg("Contextmenu opened for embyid: %s - itemtype: %s" %(embyid,itemtype))
if embyid:
item = emby.getItem(embyid)
API = api.API(item)
userdata = API.getUserData()
likes = userdata['Likes']
favourite = userdata['Favorite']
options=[]
if likes == True:
#clear like for the item
options.append(utils.language(30402))
if likes == False or likes == None:
#Like the item
options.append(utils.language(30403))
if likes == True or likes == None:
#Dislike the item
options.append(utils.language(30404))
if favourite == False:
#Add to emby favourites
options.append(utils.language(30405))
if favourite == True:
#Remove from emby favourites
options.append(utils.language(30406))
if itemtype == "song":
#Set custom song rating
options.append(utils.language(30407))
#delete item
options.append(utils.language(30409))
#addon settings
options.append(utils.language(30408))
#display select dialog and process results
header = utils.language(30401)
ret = xbmcgui.Dialog().select(header, options)
if ret != -1:
if options[ret] == utils.language(30402):
emby.updateUserRating(embyid, deletelike=True)
if options[ret] == utils.language(30403):
emby.updateUserRating(embyid, like=True)
if options[ret] == utils.language(30404):
emby.updateUserRating(embyid, like=False)
if options[ret] == utils.language(30405):
emby.updateUserRating(embyid, favourite=True)
if options[ret] == utils.language(30406):
emby.updateUserRating(embyid, favourite=False)
if options[ret] == utils.language(30407):
kodiconn = utils.kodiSQL('music')
kodicursor = kodiconn.cursor()
query = ' '.join(("SELECT rating", "FROM song", "WHERE idSong = ?" ))
kodicursor.execute(query, (itemid,))
currentvalue = int(round(float(kodicursor.fetchone()[0]),0))
newvalue = xbmcgui.Dialog().numeric(0, "Set custom song rating (0-5)", str(currentvalue))
if newvalue:
newvalue = int(newvalue)
if newvalue > 5: newvalue = "5"
if utils.settings('enableUpdateSongRating') == "true":
musicutils.updateRatingToFile(newvalue, API.getFilePath())
if utils.settings('enableExportSongRating') == "true":
like, favourite, deletelike = musicutils.getEmbyRatingFromKodiRating(newvalue)
emby.updateUserRating(embyid, like, favourite, deletelike)
query = ' '.join(( "UPDATE song","SET rating = ?", "WHERE idSong = ?" ))
kodicursor.execute(query, (newvalue,itemid,))
kodiconn.commit()
if options[ret] == utils.language(30408):
#Open addon settings
xbmc.executebuiltin("Addon.OpenSettings(plugin.video.emby)")
if options[ret] == utils.language(30409):
#delete item from the server
delete = True
if utils.settings('skipContextMenu') != "true":
resp = xbmcgui.Dialog().yesno(
heading="Confirm delete",
line1=("Delete file from Emby Server? This will "
"also delete the file(s) from disk!"))
if not resp:
logMsg("User skipped deletion for: %s." % embyid, 1)
delete = False
if delete:
import downloadutils
doUtils = downloadutils.DownloadUtils()
url = "{server}/emby/Items/%s?format=json" % embyid
logMsg("Deleting request: %s" % embyid, 0)
doUtils.downloadUrl(url, action_type="DELETE")
'''if utils.settings('skipContextMenu') != "true":
if xbmcgui.Dialog().yesno(
heading="Confirm delete",
line1=("Delete file on Emby Server? This will "
"also delete the file(s) from disk!")):
import downloadutils
doUtils = downloadutils.DownloadUtils()
doUtils.downloadUrl("{server}/emby/Items/%s?format=json" % embyid, action_type="DELETE")'''
xbmc.sleep(500)
xbmc.executebuiltin("Container.Update")

View file

@ -323,7 +323,7 @@
<string id="33020">Gathering tv shows from:</string>
<string id="33021">Gathering:</string>
<string id="33022">Detected the database needs to be recreated for this version of Emby for Kodi. Proceed?</string>
<string id="33023">Emby for Kod may not work correctly until the database is reset.</string>
<string id="33023">Emby for Kodi may not work correctly until the database is reset.</string>
<string id="33024">Cancelling the database syncing process. The current Kodi version is unsupported.</string>
<string id="33025">completed in:</string>
<string id="33026">Comparing movies from:</string>

View file

@ -37,7 +37,7 @@ class API():
try:
userdata = self.item['UserData']
except KeyError: # No userdata found.
pass
@ -57,7 +57,7 @@ class API():
lastPlayedDate = userdata.get('LastPlayedDate')
if lastPlayedDate:
lastPlayedDate = lastPlayedDate.split('.')[0].replace('T', " ")
if userdata['Played']:
# Playcount is tied to the watch status
played = True
@ -91,10 +91,10 @@ class API():
try:
people = self.item['People']
except KeyError:
pass
else:
for person in people:
@ -116,17 +116,16 @@ class API():
}
def getMediaStreams(self):
item = self.item
videotracks = []
audiotracks = []
subtitlelanguages = []
try:
media_streams = item['MediaSources'][0]['MediaStreams']
media_streams = self.item['MediaSources'][0]['MediaStreams']
except KeyError:
if not item.get("MediaStreams"): return None
media_streams = item['MediaStreams']
if not self.item.get("MediaStreams"): return None
media_streams = self.item['MediaStreams']
for media_stream in media_streams:
# Sort through Video, Audio, Subtitle
@ -141,12 +140,12 @@ class API():
'codec': codec,
'height': media_stream.get('Height'),
'width': media_stream.get('Width'),
'video3DFormat': item.get('Video3DFormat'),
'video3DFormat': self.item.get('Video3DFormat'),
'aspect': 1.85
}
try:
container = item['MediaSources'][0]['Container'].lower()
container = self.item['MediaSources'][0]['Container'].lower()
except:
container = ""
@ -161,16 +160,16 @@ class API():
track['codec'] = "avc1"
# Aspect ratio
if item.get('AspectRatio'):
if self.item.get('AspectRatio'):
# Metadata AR
aspect = item['AspectRatio']
aspect = self.item['AspectRatio']
else: # File AR
aspect = media_stream.get('AspectRatio', "0")
try:
aspectwidth, aspectheight = aspect.split(':')
track['aspect'] = round(float(aspectwidth) / float(aspectheight), 6)
except (ValueError, ZeroDivisionError):
width = track.get('width')
height = track.get('height')
@ -179,16 +178,16 @@ class API():
track['aspect'] = round(float(width / height), 6)
else:
track['aspect'] = 1.85
if item.get("RunTimeTicks"):
track['duration'] = item.get("RunTimeTicks") / 10000000.0
if self.item.get("RunTimeTicks"):
track['duration'] = self.item.get("RunTimeTicks") / 10000000.0
videotracks.append(track)
elif stream_type == "Audio":
# Codec, Channels, language
track = {
'codec': codec,
'channels': media_stream.get('Channels'),
'language': media_stream.get('Language')
@ -205,18 +204,17 @@ class API():
return {
'video': videotracks,
'video': videotracks,
'audio': audiotracks,
'subtitle': subtitlelanguages
}
def getRuntime(self):
item = self.item
try:
runtime = item['RunTimeTicks'] / 10000000.0
runtime = self.item['RunTimeTicks'] / 10000000.0
except KeyError:
runtime = item.get('CumulativeRunTimeTicks', 0) / 10000000.0
runtime = self.item.get('CumulativeRunTimeTicks', 0) / 10000000.0
return runtime
@ -234,20 +232,19 @@ class API():
def getStudios(self):
# Process Studios
item = self.item
studios = []
try:
studio = item['SeriesStudio']
studio = self.item['SeriesStudio']
studios.append(self.verifyStudio(studio))
except KeyError:
studioList = item['Studios']
studioList = self.item['Studios']
for studio in studioList:
name = studio['Name']
studios.append(self.verifyStudio(name))
return studios
def verifyStudio(self, studioName):
@ -265,12 +262,11 @@ class API():
def getChecksum(self):
# Use the etags checksum and userdata
item = self.item
userdata = item['UserData']
userdata = self.item['UserData']
checksum = "%s%s%s%s%s%s%s" % (
item['Etag'],
self.item['Etag'],
userdata['Played'],
userdata['IsFavorite'],
userdata.get('Likes',''),
@ -282,9 +278,8 @@ class API():
return checksum
def getGenres(self):
item = self.item
all_genres = ""
genres = item.get('Genres', item.get('SeriesGenres'))
genres = self.item.get('Genres', self.item.get('SeriesGenres'))
if genres:
all_genres = " / ".join(genres)
@ -344,7 +339,7 @@ class API():
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"
@ -362,9 +357,8 @@ class API():
def getFilePath(self):
item = self.item
try:
filepath = item['Path']
filepath = self.item['Path']
except KeyError:
filepath = ""
@ -375,17 +369,16 @@ class API():
filepath = filepath.replace("\\\\", "smb://")
filepath = filepath.replace("\\", "/")
if item.get('VideoType'):
videotype = item['VideoType']
if self.item.get('VideoType'):
videotype = self.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

File diff suppressed because it is too large Load diff

View file

@ -21,7 +21,7 @@ requests.packages.urllib3.disable_warnings(InsecurePlatformWarning)
class ConnectUtils():
# Borg - multiple instances, shared state
_shared_state = {}
clientInfo = clientinfo.ClientInfo()
@ -60,8 +60,6 @@ class ConnectUtils():
def startSession(self):
log = self.logMsg
self.deviceId = self.clientInfo.getDeviceId()
# User is identified from this point
@ -75,8 +73,8 @@ class ConnectUtils():
if self.sslclient is not None:
verify = self.sslclient
except:
log("Could not load SSL settings.", 1)
self.logMsg("Could not load SSL settings.", 1)
# Start session
self.c = requests.Session()
self.c.headers = header
@ -85,7 +83,7 @@ class ConnectUtils():
self.c.mount("http://", requests.adapters.HTTPAdapter(max_retries=1))
self.c.mount("https://", requests.adapters.HTTPAdapter(max_retries=1))
log("Requests session started on: %s" % self.server, 1)
self.logMsg("Requests session started on: %s" % self.server, 1)
def stopSession(self):
try:
@ -95,8 +93,7 @@ class ConnectUtils():
def getHeader(self, authenticate=True):
clientInfo = self.clientInfo
version = clientInfo.getVersion()
version = self.clientInfo.getVersion()
if not authenticate:
# If user is not authenticated
@ -105,9 +102,9 @@ class ConnectUtils():
'X-Application': "Kodi/%s" % version,
'Content-type': 'application/x-www-form-urlencoded; charset=UTF-8',
'Accept': "application/json"
}
}
self.logMsg("Header: %s" % header, 1)
else:
token = self.token
# Attached to the requests session
@ -117,18 +114,17 @@ class ConnectUtils():
'Accept': "application/json",
'X-Application': "Kodi/%s" % version,
'X-Connect-UserToken': token
}
}
self.logMsg("Header: %s" % header, 1)
return header
def doUrl(self, url, data=None, postBody=None, rtype="GET",
parameters=None, authenticate=True, timeout=None):
log = self.logMsg
window = utils.window
log("=== ENTER connectUrl ===", 2)
self.logMsg("=== ENTER connectUrl ===", 2)
default_link = ""
if timeout is None:
timeout = self.timeout
@ -137,7 +133,7 @@ class ConnectUtils():
try:
# If connect user is authenticated
if authenticate:
try:
try:
c = self.c
# Replace for the real values
url = url.replace("{server}", self.server)
@ -167,7 +163,7 @@ class ConnectUtils():
verifyssl = self.sslclient
except AttributeError:
pass
# Prepare request
if rtype == "GET":
r = requests.get(url,
@ -195,7 +191,7 @@ class ConnectUtils():
verifyssl = self.sslclient
except AttributeError:
pass
# Prepare request
if rtype == "GET":
r = requests.get(url,
@ -213,28 +209,28 @@ class ConnectUtils():
verify=verifyssl)
##### THE RESPONSE #####
log(r.url, 1)
log(r, 1)
self.logMsg(r.url, 1)
self.logMsg(r, 1)
if r.status_code == 204:
# No body in the response
log("====== 204 Success ======", 1)
self.logMsg("====== 204 Success ======", 1)
elif r.status_code == requests.codes.ok:
try:
try:
# UNICODE - JSON object
r = r.json()
log("====== 200 Success ======", 1)
log("Response: %s" % r, 1)
self.logMsg("====== 200 Success ======", 1)
self.logMsg("Response: %s" % r, 1)
return r
except:
if r.headers.get('content-type') != "text/html":
log("Unable to convert the response for: %s" % url, 1)
self.logMsg("Unable to convert the response for: %s" % url, 1)
else:
r.raise_for_status()
##### EXCEPTIONS #####
except requests.exceptions.ConnectionError as e:
@ -242,8 +238,8 @@ class ConnectUtils():
pass
except requests.exceptions.ConnectTimeout as e:
log("Server timeout at: %s" % url, 0)
log(e, 1)
self.logMsg("Server timeout at: %s" % url, 0)
self.logMsg(e, 1)
except requests.exceptions.HTTPError as e:
@ -259,11 +255,11 @@ class ConnectUtils():
pass
except requests.exceptions.SSLError as e:
log("Invalid SSL certificate for: %s" % url, 0)
log(e, 1)
self.logMsg("Invalid SSL certificate for: %s" % url, 0)
self.logMsg(e, 1)
except requests.exceptions.RequestException as e:
log("Unknown error connecting to: %s" % url, 0)
log(e, 1)
self.logMsg("Unknown error connecting to: %s" % url, 0)
self.logMsg(e, 1)
return default_link

View file

@ -23,7 +23,7 @@ requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
class DownloadUtils():
# Borg - multiple instances, shared state
_shared_state = {}
clientInfo = clientinfo.ClientInfo()
@ -77,11 +77,11 @@ class DownloadUtils():
# 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,"
@ -97,7 +97,7 @@ class DownloadUtils():
self.logMsg("Capabilities URL: %s" % url, 2)
self.logMsg("Postdata: %s" % data, 2)
self.downloadUrl(url, postBody=data, type="POST")
self.downloadUrl(url, postBody=data, action_type="POST")
self.logMsg("Posted capabilities to %s" % self.server, 2)
# Attempt at getting sessionId
@ -105,19 +105,19 @@ class DownloadUtils():
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"
@ -140,13 +140,11 @@ class DownloadUtils():
"{server}/emby/Sessions/%s/Users/%s?format=json"
% (sessionId, userId)
)
self.downloadUrl(url, postBody={}, type="POST")
self.downloadUrl(url, postBody={}, action_type="POST")
def startSession(self):
log = self.logMsg
self.deviceId = self.clientInfo.getDeviceId()
# User is identified from this point
@ -160,8 +158,8 @@ class DownloadUtils():
if self.sslclient is not None:
verify = self.sslclient
except:
log("Could not load SSL settings.", 1)
self.logMsg("Could not load SSL settings.", 1)
# Start session
self.s = requests.Session()
self.s.headers = header
@ -170,7 +168,7 @@ class DownloadUtils():
self.s.mount("http://", requests.adapters.HTTPAdapter(max_retries=1))
self.s.mount("https://", requests.adapters.HTTPAdapter(max_retries=1))
log("Requests session started on: %s" % self.server, 1)
self.logMsg("Requests session started on: %s" % self.server, 1)
def stopSession(self):
try:
@ -180,12 +178,10 @@ class DownloadUtils():
def getHeader(self, authenticate=True):
clientInfo = self.clientInfo
deviceName = clientInfo.getDeviceName()
deviceName = self.clientInfo.getDeviceName()
deviceName = utils.normalize_string(deviceName.encode('utf-8'))
deviceId = clientInfo.getDeviceId()
version = clientInfo.getVersion()
deviceId = self.clientInfo.getDeviceId()
version = self.clientInfo.getVersion()
if not authenticate:
# If user is not authenticated
@ -198,9 +194,9 @@ class DownloadUtils():
'Accept-encoding': 'gzip',
'Accept-Charset': 'UTF-8,*',
'Authorization': auth
}
}
self.logMsg("Header: %s" % header, 2)
else:
userId = self.userId
token = self.token
@ -215,36 +211,35 @@ class DownloadUtils():
'Accept-Charset': 'UTF-8,*',
'Authorization': auth,
'X-MediaBrowser-Token': token
}
}
self.logMsg("Header: %s" % header, 2)
return header
def downloadUrl(self, url, postBody=None, type="GET", parameters=None, authenticate=True):
def downloadUrl(self, url, postBody=None, action_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:
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)
if action_type == "GET":
r = s.get(url, json=postBody, params=parameters, timeout=self.timeout)
elif action_type == "POST":
r = s.post(url, json=postBody, timeout=self.timeout)
elif action_type == "DELETE":
r = s.delete(url, json=postBody, timeout=self.timeout)
except AttributeError:
# request session does not exists
# Get user information
@ -266,26 +261,26 @@ class DownloadUtils():
url = url.replace("{UserId}", self.userId)
# Prepare request
if type == "GET":
if action_type == "GET":
r = requests.get(url,
json=postBody,
params=parameters,
headers=header,
timeout=timeout,
timeout=self.timeout,
verify=verifyssl)
elif type == "POST":
elif action_type == "POST":
r = requests.post(url,
json=postBody,
headers=header,
timeout=timeout,
timeout=self.timeout,
verify=verifyssl)
elif type == "DELETE":
elif action_type == "DELETE":
r = requests.delete(url,
json=postBody,
headers=header,
timeout=timeout,
timeout=self.timeout,
verify=verifyssl)
# If user is not authenticated
@ -301,23 +296,23 @@ class DownloadUtils():
verifyssl = self.sslclient
except AttributeError:
pass
# Prepare request
if type == "GET":
if action_type == "GET":
r = requests.get(url,
json=postBody,
params=parameters,
headers=header,
timeout=timeout,
timeout=self.timeout,
verify=verifyssl)
elif type == "POST":
elif action_type == "POST":
r = requests.post(url,
json=postBody,
headers=header,
timeout=timeout,
timeout=self.timeout,
verify=verifyssl)
##### THE RESPONSE #####
self.logMsg(r.url, 2)
if r.status_code == 204:
@ -325,8 +320,8 @@ class DownloadUtils():
self.logMsg("====== 204 Success ======", 2)
elif r.status_code == requests.codes.ok:
try:
try:
# UNICODE - JSON object
r = r.json()
self.logMsg("====== 200 Success ======", 2)
@ -338,7 +333,7 @@ class DownloadUtils():
self.logMsg("Unable to convert the response for: %s" % url, 1)
else:
r.raise_for_status()
##### EXCEPTIONS #####
except requests.exceptions.ConnectionError as e:
@ -369,7 +364,7 @@ class DownloadUtils():
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

View file

@ -1,325 +1,292 @@
# -*- coding: utf-8 -*-
#################################################################################################
import utils
import clientinfo
#################################################################################################
class Embydb_Functions():
def __init__(self, embycursor):
self.embycursor = embycursor
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 getViews(self):
embycursor = self.embycursor
views = []
query = ' '.join((
"SELECT view_id",
"FROM view"
))
embycursor.execute(query)
rows = embycursor.fetchall()
for row in rows:
views.append(row[0])
return views
def getView_byId(self, viewid):
embycursor = self.embycursor
query = ' '.join((
"SELECT view_name, media_type, kodi_tagid",
"FROM view",
"WHERE view_id = ?"
))
embycursor.execute(query, (viewid,))
view = embycursor.fetchone()
return view
def getView_byType(self, mediatype):
embycursor = self.embycursor
views = []
query = ' '.join((
"SELECT view_id, view_name",
"FROM view",
"WHERE media_type = ?"
))
embycursor.execute(query, (mediatype,))
rows = embycursor.fetchall()
for row in rows:
views.append({
'id': row[0],
'name': row[1]
})
return views
def getView_byName(self, tagname):
embycursor = self.embycursor
query = ' '.join((
"SELECT view_id",
"FROM view",
"WHERE view_name = ?"
))
embycursor.execute(query, (tagname,))
try:
view = embycursor.fetchone()[0]
except TypeError:
view = None
return view
def addView(self, embyid, name, mediatype, tagid):
query = (
'''
INSERT INTO view(
view_id, view_name, media_type, kodi_tagid)
VALUES (?, ?, ?, ?)
'''
)
self.embycursor.execute(query, (embyid, name, mediatype, tagid))
def updateView(self, name, tagid, mediafolderid):
query = ' '.join((
"UPDATE view",
"SET view_name = ?, kodi_tagid = ?",
"WHERE view_id = ?"
))
self.embycursor.execute(query, (name, tagid, mediafolderid))
def removeView(self, viewid):
query = ' '.join((
"DELETE FROM view",
"WHERE view_id = ?"
))
self.embycursor.execute(query, (viewid,))
def getItem_byId(self, embyid):
embycursor = self.embycursor
query = ' '.join((
"SELECT kodi_id, kodi_fileid, kodi_pathid, parent_id, media_type, emby_type",
"FROM emby",
"WHERE emby_id = ?"
))
try:
embycursor.execute(query, (embyid,))
item = embycursor.fetchone()
return item
except: return None
def getItem_byWildId(self, embyid):
embycursor = self.embycursor
query = ' '.join((
"SELECT kodi_id, media_type",
"FROM emby",
"WHERE emby_id LIKE ?"
))
embycursor.execute(query, (embyid+"%",))
items = embycursor.fetchall()
return items
def getItem_byView(self, mediafolderid):
embycursor = self.embycursor
query = ' '.join((
"SELECT kodi_id",
"FROM emby",
"WHERE media_folder = ?"
))
embycursor.execute(query, (mediafolderid,))
items = embycursor.fetchall()
return items
def getItem_byKodiId(self, kodiid, mediatype):
embycursor = self.embycursor
query = ' '.join((
"SELECT emby_id, parent_id",
"FROM emby",
"WHERE kodi_id = ?",
"AND media_type = ?"
))
embycursor.execute(query, (kodiid, mediatype,))
item = embycursor.fetchone()
return item
def getItem_byParentId(self, parentid, mediatype):
embycursor = self.embycursor
query = ' '.join((
"SELECT emby_id, kodi_id, kodi_fileid",
"FROM emby",
"WHERE parent_id = ?",
"AND media_type = ?"
))
embycursor.execute(query, (parentid, mediatype,))
items = embycursor.fetchall()
return items
def getItemId_byParentId(self, parentid, mediatype):
embycursor = self.embycursor
query = ' '.join((
"SELECT emby_id, kodi_id",
"FROM emby",
"WHERE parent_id = ?",
"AND media_type = ?"
))
embycursor.execute(query, (parentid, mediatype,))
items = embycursor.fetchall()
return items
def getChecksum(self, mediatype):
embycursor = self.embycursor
query = ' '.join((
"SELECT emby_id, checksum",
"FROM emby",
"WHERE emby_type = ?"
))
embycursor.execute(query, (mediatype,))
items = embycursor.fetchall()
return items
def getMediaType_byId(self, embyid):
embycursor = self.embycursor
query = ' '.join((
"SELECT emby_type",
"FROM emby",
"WHERE emby_id = ?"
))
embycursor.execute(query, (embyid,))
try:
itemtype = embycursor.fetchone()[0]
except TypeError:
itemtype = None
return itemtype
def sortby_mediaType(self, itemids, unsorted=True):
sorted_items = {}
for itemid in itemids:
mediatype = self.getMediaType_byId(itemid)
if mediatype:
sorted_items.setdefault(mediatype, []).append(itemid)
elif unsorted:
sorted_items.setdefault('Unsorted', []).append(itemid)
return sorted_items
def addReference(self, embyid, kodiid, embytype, mediatype, fileid=None, pathid=None,
parentid=None, checksum=None, mediafolderid=None):
query = (
'''
INSERT OR REPLACE INTO emby(
emby_id, kodi_id, kodi_fileid, kodi_pathid, emby_type, media_type, parent_id,
checksum, media_folder)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
'''
)
self.embycursor.execute(query, (embyid, kodiid, fileid, pathid, embytype, mediatype,
parentid, checksum, mediafolderid))
def updateReference(self, embyid, checksum):
query = "UPDATE emby SET checksum = ? WHERE emby_id = ?"
self.embycursor.execute(query, (checksum, embyid))
def updateParentId(self, embyid, parent_kodiid):
query = "UPDATE emby SET parent_id = ? WHERE emby_id = ?"
self.embycursor.execute(query, (parent_kodiid, embyid))
def removeItems_byParentId(self, parent_kodiid, mediatype):
query = ' '.join((
"DELETE FROM emby",
"WHERE parent_id = ?",
"AND media_type = ?"
))
self.embycursor.execute(query, (parent_kodiid, mediatype,))
def removeItem_byKodiId(self, kodiid, mediatype):
query = ' '.join((
"DELETE FROM emby",
"WHERE kodi_id = ?",
"AND media_type = ?"
))
self.embycursor.execute(query, (kodiid, mediatype,))
def removeItem(self, embyid):
query = "DELETE FROM emby WHERE emby_id = ?"
self.embycursor.execute(query, (embyid,))
def removeWildItem(self, embyid):
query = "DELETE FROM emby WHERE emby_id LIKE ?"
self.embycursor.execute(query, (embyid+"%",))
# -*- coding: utf-8 -*-
#################################################################################################
import utils
import clientinfo
#################################################################################################
class Embydb_Functions():
def __init__(self, embycursor):
self.embycursor = embycursor
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 getViews(self):
views = []
query = ' '.join((
"SELECT view_id",
"FROM view"
))
self.embycursor.execute(query)
rows = self.embycursor.fetchall()
for row in rows:
views.append(row[0])
return views
def getView_byId(self, viewid):
query = ' '.join((
"SELECT view_name, media_type, kodi_tagid",
"FROM view",
"WHERE view_id = ?"
))
self.embycursor.execute(query, (viewid,))
view = self.embycursor.fetchone()
return view
def getView_byType(self, mediatype):
views = []
query = ' '.join((
"SELECT view_id, view_name",
"FROM view",
"WHERE media_type = ?"
))
self.embycursor.execute(query, (mediatype,))
rows = self.embycursor.fetchall()
for row in rows:
views.append({
'id': row[0],
'name': row[1]
})
return views
def getView_byName(self, tagname):
query = ' '.join((
"SELECT view_id",
"FROM view",
"WHERE view_name = ?"
))
self.embycursor.execute(query, (tagname,))
try:
view = self.embycursor.fetchone()[0]
except TypeError:
view = None
return view
def addView(self, embyid, name, mediatype, tagid):
query = (
'''
INSERT INTO view(
view_id, view_name, media_type, kodi_tagid)
VALUES (?, ?, ?, ?)
'''
)
self.embycursor.execute(query, (embyid, name, mediatype, tagid))
def updateView(self, name, tagid, mediafolderid):
query = ' '.join((
"UPDATE view",
"SET view_name = ?, kodi_tagid = ?",
"WHERE view_id = ?"
))
self.embycursor.execute(query, (name, tagid, mediafolderid))
def removeView(self, viewid):
query = ' '.join((
"DELETE FROM view",
"WHERE view_id = ?"
))
self.embycursor.execute(query, (viewid,))
def getItem_byId(self, embyid):
query = ' '.join((
"SELECT kodi_id, kodi_fileid, kodi_pathid, parent_id, media_type, emby_type",
"FROM emby",
"WHERE emby_id = ?"
))
try:
self.embycursor.execute(query, (embyid,))
item = self.embycursor.fetchone()
return item
except: return None
def getItem_byWildId(self, embyid):
query = ' '.join((
"SELECT kodi_id, media_type",
"FROM emby",
"WHERE emby_id LIKE ?"
))
self.embycursor.execute(query, (embyid+"%",))
return self.embycursor.fetchall()
def getItem_byView(self, mediafolderid):
query = ' '.join((
"SELECT kodi_id",
"FROM emby",
"WHERE media_folder = ?"
))
self.embycursor.execute(query, (mediafolderid,))
return self.embycursor.fetchall()
def getItem_byKodiId(self, kodiid, mediatype):
query = ' '.join((
"SELECT emby_id, parent_id",
"FROM emby",
"WHERE kodi_id = ?",
"AND media_type = ?"
))
self.embycursor.execute(query, (kodiid, mediatype,))
return self.embycursor.fetchone()
def getItem_byParentId(self, parentid, mediatype):
query = ' '.join((
"SELECT emby_id, kodi_id, kodi_fileid",
"FROM emby",
"WHERE parent_id = ?",
"AND media_type = ?"
))
self.embycursor.execute(query, (parentid, mediatype,))
return self.embycursor.fetchall()
def getItemId_byParentId(self, parentid, mediatype):
query = ' '.join((
"SELECT emby_id, kodi_id",
"FROM emby",
"WHERE parent_id = ?",
"AND media_type = ?"
))
self.embycursor.execute(query, (parentid, mediatype,))
return self.embycursor.fetchall()
def getChecksum(self, mediatype):
query = ' '.join((
"SELECT emby_id, checksum",
"FROM emby",
"WHERE emby_type = ?"
))
self.embycursor.execute(query, (mediatype,))
return self.embycursor.fetchall()
def getMediaType_byId(self, embyid):
query = ' '.join((
"SELECT emby_type",
"FROM emby",
"WHERE emby_id = ?"
))
self.embycursor.execute(query, (embyid,))
try:
itemtype = self.embycursor.fetchone()[0]
except TypeError:
itemtype = None
return itemtype
def sortby_mediaType(self, itemids, unsorted=True):
sorted_items = {}
for itemid in itemids:
mediatype = self.getMediaType_byId(itemid)
if mediatype:
sorted_items.setdefault(mediatype, []).append(itemid)
elif unsorted:
sorted_items.setdefault('Unsorted', []).append(itemid)
return sorted_items
def addReference(self, embyid, kodiid, embytype, mediatype, fileid=None, pathid=None,
parentid=None, checksum=None, mediafolderid=None):
query = (
'''
INSERT OR REPLACE INTO emby(
emby_id, kodi_id, kodi_fileid, kodi_pathid, emby_type, media_type, parent_id,
checksum, media_folder)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
'''
)
self.embycursor.execute(query, (embyid, kodiid, fileid, pathid, embytype, mediatype,
parentid, checksum, mediafolderid))
def updateReference(self, embyid, checksum):
query = "UPDATE emby SET checksum = ? WHERE emby_id = ?"
self.embycursor.execute(query, (checksum, embyid))
def updateParentId(self, embyid, parent_kodiid):
query = "UPDATE emby SET parent_id = ? WHERE emby_id = ?"
self.embycursor.execute(query, (parent_kodiid, embyid))
def removeItems_byParentId(self, parent_kodiid, mediatype):
query = ' '.join((
"DELETE FROM emby",
"WHERE parent_id = ?",
"AND media_type = ?"
))
self.embycursor.execute(query, (parent_kodiid, mediatype,))
def removeItem_byKodiId(self, kodiid, mediatype):
query = ' '.join((
"DELETE FROM emby",
"WHERE kodi_id = ?",
"AND media_type = ?"
))
self.embycursor.execute(query, (kodiid, mediatype,))
def removeItem(self, embyid):
query = "DELETE FROM emby WHERE emby_id = ?"
self.embycursor.execute(query, (embyid,))
def removeWildItem(self, embyid):
query = "DELETE FROM emby WHERE emby_id LIKE ?"
self.embycursor.execute(query, (embyid+"%",))

File diff suppressed because it is too large Load diff

View file

@ -176,8 +176,8 @@ class InitialSetup():
sock.setsockopt(socket.SOL_IP, socket.IP_MULTICAST_LOOP, 1)
sock.setsockopt(socket.IPPROTO_IP, socket.SO_REUSEADDR, 1)
self.logMsg("MultiGroup : %s" % str(MULTI_GROUP), 2);
self.logMsg("Sending UDP Data: %s" % MESSAGE, 2);
self.logMsg("MultiGroup : %s" % str(MULTI_GROUP), 2)
self.logMsg("Sending UDP Data: %s" % MESSAGE, 2)
sock.sendto(MESSAGE, MULTI_GROUP)
try:

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,209 +1,209 @@
# -*- 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 onSettingsChanged(self):
# Monitor emby settings
# Review reset setting at a later time, need to be adjusted to account for initial setup
# changes.
'''currentPath = utils.settings('useDirectPaths')
if utils.window('emby_pluginpath') != currentPath:
# Plugin path value changed. Offer to reset
self.logMsg("Changed to playback mode detected", 1)
utils.window('emby_pluginpath', value=currentPath)
resp = xbmcgui.Dialog().yesno(
heading="Playback mode change detected",
line1=(
"Detected the playback mode has changed. The database "
"needs to be recreated for the change to be applied. "
"Proceed?"))
if resp:
utils.reset()'''
currentLog = utils.settings('logLevel')
if utils.window('emby_logLevel') != currentLog:
# The log level changed, set new prop
self.logMsg("New log level: %s" % currentLog, 1)
utils.window('emby_logLevel', value=currentLog)
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,'utf-8')
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("Item is invalid for playstate update.", 1)
else:
if ((utils.settings('useDirectPaths') == "1" and not type == "song") or
(type == "song" and utils.settings('enableMusic') == "true")):
# 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":
# Removed function, because with plugin paths + clean library, it will wipe
# entire library if user has permissions. Instead, use the emby context menu available
# in Isengard and higher version
pass
'''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.
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('skipContextMenu') != "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":
# -*- 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 onSettingsChanged(self):
# Monitor emby settings
# Review reset setting at a later time, need to be adjusted to account for initial setup
# changes.
'''currentPath = utils.settings('useDirectPaths')
if utils.window('emby_pluginpath') != currentPath:
# Plugin path value changed. Offer to reset
self.logMsg("Changed to playback mode detected", 1)
utils.window('emby_pluginpath', value=currentPath)
resp = xbmcgui.Dialog().yesno(
heading="Playback mode change detected",
line1=(
"Detected the playback mode has changed. The database "
"needs to be recreated for the change to be applied. "
"Proceed?"))
if resp:
utils.reset()'''
currentLog = utils.settings('logLevel')
if utils.window('emby_logLevel') != currentLog:
# The log level changed, set new prop
self.logMsg("New log level: %s" % currentLog, 1)
utils.window('emby_logLevel', value=currentLog)
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,'utf-8')
if method == "Player.OnPlay":
# Set up report progress for emby playback
item = data.get('item')
try:
kodiid = item['id']
item_type = item['type']
except (KeyError, TypeError):
self.logMsg("Item is invalid for playstate update.", 1)
else:
if ((utils.settings('useDirectPaths') == "1" and not item_type == "song") or
(item_type == "song" and utils.settings('enableMusic') == "true")):
# 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, item_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 item_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']
item_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, item_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, action_type="POST")
self.logMsg("Mark as watched for itemid: %s" % itemid, 1)
else:
doUtils.downloadUrl(url, action_type="DELETE")
self.logMsg("Mark as unwatched for itemid: %s" % itemid, 1)
finally:
embycursor.close()
elif method == "VideoLibrary.OnRemove":
# Removed function, because with plugin paths + clean library, it will wipe
# entire library if user has permissions. Instead, use the emby context menu available
# in Isengard and higher version
pass
'''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.
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('skipContextMenu') != "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, action_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":
pass

File diff suppressed because it is too large Load diff

View file

@ -1,286 +1,287 @@
# -*- coding: utf-8 -*-
#################################################################################################
import os
import xbmc
import xbmcaddon
import xbmcvfs
from mutagen.flac import FLAC, Picture
from mutagen.id3 import ID3
from mutagen import id3
import base64
import read_embyserver as embyserver
import utils
#################################################################################################
# Helper for the music library, intended to fix missing song ID3 tags on Emby
def logMsg(msg, lvl=1):
utils.logMsg("%s %s" % ("Emby", "musictools"), msg, lvl)
def getRealFileName(filename, isTemp=False):
#get the filename path accessible by python if possible...
if not xbmcvfs.exists(filename):
logMsg( "File does not exist! %s" %(filename), 0)
return (False, "")
#if we use os.path method on older python versions (sunch as some android builds), we need to pass arguments as string
if os.path.supports_unicode_filenames:
checkfile = filename
else:
checkfile = filename.encode("utf-8")
# determine if our python module is able to access the file directly...
if os.path.exists(checkfile):
filename = filename
elif os.path.exists(checkfile.replace("smb://","\\\\").replace("/","\\")):
filename = filename.replace("smb://","\\\\").replace("/","\\")
else:
#file can not be accessed by python directly, we copy it for processing...
isTemp = True
if "/" in filename: filepart = filename.split("/")[-1]
else: filepart = filename.split("\\")[-1]
tempfile = "special://temp/"+filepart
xbmcvfs.copy(filename, tempfile)
filename = xbmc.translatePath(tempfile).decode("utf-8")
return (isTemp,filename)
def getEmbyRatingFromKodiRating(rating):
# Translation needed between Kodi/ID3 rating and emby likes/favourites:
# 3+ rating in ID3 = emby like
# 5+ rating in ID3 = emby favourite
# rating 0 = emby dislike
# rating 1-2 = emby no likes or dislikes (returns 1 in results)
favourite = False
deletelike = False
like = False
if (rating >= 3): like = True
if (rating == 0): like = False
if (rating == 1 or rating == 2): deletelike = True
if (rating >= 5): favourite = True
return(like, favourite, deletelike)
def getAdditionalSongTags(embyid, emby_rating, API, kodicursor, emby_db, enableimportsongrating, enableexportsongrating, enableupdatesongrating):
emby = embyserver.Read_EmbyServer()
previous_values = None
filename = API.getFilePath()
rating = 0
emby_rating = int(round(emby_rating, 0))
#get file rating and comment tag from file itself.
if enableimportsongrating:
file_rating, comment, hasEmbeddedCover = getSongTags(filename)
else:
file_rating = 0
comment = ""
hasEmbeddedCover = False
emby_dbitem = emby_db.getItem_byId(embyid)
try:
kodiid = emby_dbitem[0]
except TypeError:
# Item is not in database.
currentvalue = None
else:
query = "SELECT rating FROM song WHERE idSong = ?"
kodicursor.execute(query, (kodiid,))
try:
currentvalue = int(round(float(kodicursor.fetchone()[0]),0))
except: currentvalue = None
# Only proceed if we actually have a rating from the file
if file_rating is None and currentvalue:
return (currentvalue, comment, False)
elif file_rating is None and not currentvalue:
return (emby_rating, comment, False)
logMsg("getAdditionalSongTags --> embyid: %s - emby_rating: %s - file_rating: %s - current rating in kodidb: %s" %(embyid, emby_rating, file_rating, currentvalue))
updateFileRating = False
updateEmbyRating = False
if currentvalue != None:
# we need to translate the emby values...
if emby_rating == 1 and currentvalue == 2:
emby_rating = 2
if emby_rating == 3 and currentvalue == 4:
emby_rating = 4
#if updating rating into file is disabled, we ignore the rating in the file...
if not enableupdatesongrating:
file_rating = currentvalue
#if convert emby likes/favourites convert to song rating is disabled, we ignore the emby rating...
if not enableexportsongrating:
emby_rating = currentvalue
if (emby_rating == file_rating) and (file_rating != currentvalue):
#the rating has been updated from kodi itself, update change to both emby ands file
rating = currentvalue
updateFileRating = True
updateEmbyRating = True
elif (emby_rating != currentvalue) and (file_rating == currentvalue):
#emby rating changed - update the file
rating = emby_rating
updateFileRating = True
elif (file_rating != currentvalue) and (emby_rating == currentvalue):
#file rating was updated, sync change to emby
rating = file_rating
updateEmbyRating = True
elif (emby_rating != currentvalue) and (file_rating != currentvalue):
#both ratings have changed (corner case) - the highest rating wins...
if emby_rating > file_rating:
rating = emby_rating
updateFileRating = True
else:
rating = file_rating
updateEmbyRating = True
else:
#nothing has changed, just return the current value
rating = currentvalue
else:
# no rating yet in DB
if enableimportsongrating:
#prefer the file rating
rating = file_rating
#determine if we should also send the rating to emby server
if enableexportsongrating:
if emby_rating == 1 and file_rating == 2:
emby_rating = 2
if emby_rating == 3 and file_rating == 4:
emby_rating = 4
if emby_rating != file_rating:
updateEmbyRating = True
elif enableexportsongrating:
#set the initial rating to emby value
rating = emby_rating
if updateFileRating and enableupdatesongrating:
updateRatingToFile(rating, filename)
if updateEmbyRating and enableexportsongrating:
# sync details to emby server. Translation needed between ID3 rating and emby likes/favourites:
like, favourite, deletelike = getEmbyRatingFromKodiRating(rating)
utils.window("ignore-update-%s" %embyid, "true") #set temp windows prop to ignore the update from webclient update
emby.updateUserRating(embyid, like, favourite, deletelike)
return (rating, comment, hasEmbeddedCover)
def getSongTags(file):
# Get the actual ID3 tags for music songs as the server is lacking that info
rating = 0
comment = ""
hasEmbeddedCover = False
isTemp,filename = getRealFileName(file)
logMsg( "getting song ID3 tags for " + filename)
try:
###### FLAC FILES #############
if filename.lower().endswith(".flac"):
audio = FLAC(filename)
if audio.get("comment"):
comment = audio.get("comment")[0]
for pic in audio.pictures:
if pic.type == 3 and pic.data:
#the file has an embedded cover
hasEmbeddedCover = True
if audio.get("rating"):
rating = float(audio.get("rating")[0])
#flac rating is 0-100 and needs to be converted to 0-5 range
if rating > 5: rating = (rating / 100) * 5
###### MP3 FILES #############
elif filename.lower().endswith(".mp3"):
audio = ID3(filename)
if audio.get("APIC:Front Cover"):
if audio.get("APIC:Front Cover").data:
hasEmbeddedCover = True
if audio.get("comment"):
comment = audio.get("comment")[0]
if audio.get("POPM:Windows Media Player 9 Series"):
if audio.get("POPM:Windows Media Player 9 Series").rating:
rating = float(audio.get("POPM:Windows Media Player 9 Series").rating)
#POPM rating is 0-255 and needs to be converted to 0-5 range
if rating > 5: rating = (rating / 255) * 5
else:
logMsg( "Not supported fileformat or unable to access file: %s" %(filename))
#the rating must be a round value
rating = int(round(rating,0))
except Exception as e:
#file in use ?
utils.logMsg("Exception in getSongTags", str(e),0)
rating = None
#remove tempfile if needed....
if isTemp: xbmcvfs.delete(filename)
return (rating, comment, hasEmbeddedCover)
def updateRatingToFile(rating, file):
#update the rating from Emby to the file
f = xbmcvfs.File(file)
org_size = f.size()
f.close()
#create tempfile
if "/" in file: filepart = file.split("/")[-1]
else: filepart = file.split("\\")[-1]
tempfile = "special://temp/"+filepart
xbmcvfs.copy(file, tempfile)
tempfile = xbmc.translatePath(tempfile).decode("utf-8")
logMsg( "setting song rating: %s for filename: %s - using tempfile: %s" %(rating,file,tempfile))
if not tempfile:
return
try:
if tempfile.lower().endswith(".flac"):
audio = FLAC(tempfile)
calcrating = int(round((float(rating) / 5) * 100, 0))
audio["rating"] = str(calcrating)
audio.save()
elif tempfile.lower().endswith(".mp3"):
audio = ID3(tempfile)
calcrating = int(round((float(rating) / 5) * 255, 0))
audio.add(id3.POPM(email="Windows Media Player 9 Series", rating=calcrating, count=1))
audio.save()
else:
logMsg( "Not supported fileformat: %s" %(tempfile))
#once we have succesfully written the flags we move the temp file to destination, otherwise not proceeding and just delete the temp
#safety check: we check the file size of the temp file before proceeding with overwite of original file
f = xbmcvfs.File(tempfile)
checksum_size = f.size()
f.close()
if checksum_size >= org_size:
xbmcvfs.delete(file)
xbmcvfs.copy(tempfile,file)
else:
logMsg( "Checksum mismatch for filename: %s - using tempfile: %s - not proceeding with file overwite!" %(rating,file,tempfile))
#always delete the tempfile
xbmcvfs.delete(tempfile)
except Exception as e:
#file in use ?
logMsg("Exception in updateRatingToFile %s" %e,0)
# -*- coding: utf-8 -*-
#################################################################################################
import os
import xbmc
import xbmcaddon
import xbmcvfs
from mutagen.flac import FLAC, Picture
from mutagen.id3 import ID3
from mutagen import id3
import base64
import read_embyserver as embyserver
import utils
#################################################################################################
# Helper for the music library, intended to fix missing song ID3 tags on Emby
def logMsg(msg, lvl=1):
utils.logMsg("%s %s" % ("Emby", "musictools"), msg, lvl)
def getRealFileName(filename, isTemp=False):
#get the filename path accessible by python if possible...
if not xbmcvfs.exists(filename):
logMsg( "File does not exist! %s" %(filename), 0)
return (False, "")
#if we use os.path method on older python versions (sunch as some android builds), we need to pass arguments as string
if os.path.supports_unicode_filenames:
checkfile = filename
else:
checkfile = filename.encode("utf-8")
# determine if our python module is able to access the file directly...
if os.path.exists(checkfile):
filename = filename
elif os.path.exists(checkfile.replace("smb://","\\\\").replace("/","\\")):
filename = filename.replace("smb://","\\\\").replace("/","\\")
else:
#file can not be accessed by python directly, we copy it for processing...
isTemp = True
if "/" in filename: filepart = filename.split("/")[-1]
else: filepart = filename.split("\\")[-1]
tempfile = "special://temp/"+filepart
xbmcvfs.copy(filename, tempfile)
filename = xbmc.translatePath(tempfile).decode("utf-8")
return (isTemp,filename)
def getEmbyRatingFromKodiRating(rating):
# Translation needed between Kodi/ID3 rating and emby likes/favourites:
# 3+ rating in ID3 = emby like
# 5+ rating in ID3 = emby favourite
# rating 0 = emby dislike
# rating 1-2 = emby no likes or dislikes (returns 1 in results)
favourite = False
deletelike = False
like = False
if (rating >= 3): like = True
if (rating == 0): like = False
if (rating == 1 or rating == 2): deletelike = True
if (rating >= 5): favourite = True
return(like, favourite, deletelike)
def getAdditionalSongTags(embyid, emby_rating, API, kodicursor, emby_db, enableimportsongrating, enableexportsongrating, enableupdatesongrating):
emby = embyserver.Read_EmbyServer()
previous_values = None
filename = API.getFilePath()
rating = 0
emby_rating = int(round(emby_rating, 0))
#get file rating and comment tag from file itself.
if enableimportsongrating:
file_rating, comment, hasEmbeddedCover = getSongTags(filename)
else:
file_rating = 0
comment = ""
hasEmbeddedCover = False
emby_dbitem = emby_db.getItem_byId(embyid)
try:
kodiid = emby_dbitem[0]
except TypeError:
# Item is not in database.
currentvalue = None
else:
query = "SELECT rating FROM song WHERE idSong = ?"
kodicursor.execute(query, (kodiid,))
try:
currentvalue = int(round(float(kodicursor.fetchone()[0]),0))
except: currentvalue = None
# Only proceed if we actually have a rating from the file
if file_rating is None and currentvalue:
return (currentvalue, comment, False)
elif file_rating is None and not currentvalue:
return (emby_rating, comment, False)
logMsg("getAdditionalSongTags --> embyid: %s - emby_rating: %s - file_rating: %s - current rating in kodidb: %s" %(embyid, emby_rating, file_rating, currentvalue))
updateFileRating = False
updateEmbyRating = False
if currentvalue != None:
# we need to translate the emby values...
if emby_rating == 1 and currentvalue == 2:
emby_rating = 2
if emby_rating == 3 and currentvalue == 4:
emby_rating = 4
#if updating rating into file is disabled, we ignore the rating in the file...
if not enableupdatesongrating:
file_rating = currentvalue
#if convert emby likes/favourites convert to song rating is disabled, we ignore the emby rating...
if not enableexportsongrating:
emby_rating = currentvalue
if (emby_rating == file_rating) and (file_rating != currentvalue):
#the rating has been updated from kodi itself, update change to both emby ands file
rating = currentvalue
updateFileRating = True
updateEmbyRating = True
elif (emby_rating != currentvalue) and (file_rating == currentvalue):
#emby rating changed - update the file
rating = emby_rating
updateFileRating = True
elif (file_rating != currentvalue) and (emby_rating == currentvalue):
#file rating was updated, sync change to emby
rating = file_rating
updateEmbyRating = True
elif (emby_rating != currentvalue) and (file_rating != currentvalue):
#both ratings have changed (corner case) - the highest rating wins...
if emby_rating > file_rating:
rating = emby_rating
updateFileRating = True
else:
rating = file_rating
updateEmbyRating = True
else:
#nothing has changed, just return the current value
rating = currentvalue
else:
# no rating yet in DB
if enableimportsongrating:
#prefer the file rating
rating = file_rating
#determine if we should also send the rating to emby server
if enableexportsongrating:
if emby_rating == 1 and file_rating == 2:
emby_rating = 2
if emby_rating == 3 and file_rating == 4:
emby_rating = 4
if emby_rating != file_rating:
updateEmbyRating = True
elif enableexportsongrating:
#set the initial rating to emby value
rating = emby_rating
if updateFileRating and enableupdatesongrating:
updateRatingToFile(rating, filename)
if updateEmbyRating and enableexportsongrating:
# sync details to emby server. Translation needed between ID3 rating and emby likes/favourites:
like, favourite, deletelike = getEmbyRatingFromKodiRating(rating)
utils.window("ignore-update-%s" %embyid, "true") #set temp windows prop to ignore the update from webclient update
emby.updateUserRating(embyid, like, favourite, deletelike)
return (rating, comment, hasEmbeddedCover)
def getSongTags(file):
# Get the actual ID3 tags for music songs as the server is lacking that info
rating = 0
comment = ""
hasEmbeddedCover = False
isTemp,filename = getRealFileName(file)
logMsg( "getting song ID3 tags for " + filename)
try:
###### FLAC FILES #############
if filename.lower().endswith(".flac"):
audio = FLAC(filename)
if audio.get("comment"):
comment = audio.get("comment")[0]
for pic in audio.pictures:
if pic.type == 3 and pic.data:
#the file has an embedded cover
hasEmbeddedCover = True
break
if audio.get("rating"):
rating = float(audio.get("rating")[0])
#flac rating is 0-100 and needs to be converted to 0-5 range
if rating > 5: rating = (rating / 100) * 5
###### MP3 FILES #############
elif filename.lower().endswith(".mp3"):
audio = ID3(filename)
if audio.get("APIC:Front Cover"):
if audio.get("APIC:Front Cover").data:
hasEmbeddedCover = True
if audio.get("comment"):
comment = audio.get("comment")[0]
if audio.get("POPM:Windows Media Player 9 Series"):
if audio.get("POPM:Windows Media Player 9 Series").rating:
rating = float(audio.get("POPM:Windows Media Player 9 Series").rating)
#POPM rating is 0-255 and needs to be converted to 0-5 range
if rating > 5: rating = (rating / 255) * 5
else:
logMsg( "Not supported fileformat or unable to access file: %s" %(filename))
#the rating must be a round value
rating = int(round(rating,0))
except Exception as e:
#file in use ?
utils.logMsg("Exception in getSongTags", str(e),0)
rating = None
#remove tempfile if needed....
if isTemp: xbmcvfs.delete(filename)
return (rating, comment, hasEmbeddedCover)
def updateRatingToFile(rating, file):
#update the rating from Emby to the file
f = xbmcvfs.File(file)
org_size = f.size()
f.close()
#create tempfile
if "/" in file: filepart = file.split("/")[-1]
else: filepart = file.split("\\")[-1]
tempfile = "special://temp/"+filepart
xbmcvfs.copy(file, tempfile)
tempfile = xbmc.translatePath(tempfile).decode("utf-8")
logMsg( "setting song rating: %s for filename: %s - using tempfile: %s" %(rating,file,tempfile))
if not tempfile:
return
try:
if tempfile.lower().endswith(".flac"):
audio = FLAC(tempfile)
calcrating = int(round((float(rating) / 5) * 100, 0))
audio["rating"] = str(calcrating)
audio.save()
elif tempfile.lower().endswith(".mp3"):
audio = ID3(tempfile)
calcrating = int(round((float(rating) / 5) * 255, 0))
audio.add(id3.POPM(email="Windows Media Player 9 Series", rating=calcrating, count=1))
audio.save()
else:
logMsg( "Not supported fileformat: %s" %(tempfile))
#once we have succesfully written the flags we move the temp file to destination, otherwise not proceeding and just delete the temp
#safety check: we check the file size of the temp file before proceeding with overwite of original file
f = xbmcvfs.File(tempfile)
checksum_size = f.size()
f.close()
if checksum_size >= org_size:
xbmcvfs.delete(file)
xbmcvfs.copy(tempfile,file)
else:
logMsg( "Checksum mismatch for filename: %s - using tempfile: %s - not proceeding with file overwite!" %(rating,file,tempfile))
#always delete the tempfile
xbmcvfs.delete(tempfile)
except Exception as e:
#file in use ?
logMsg("Exception in updateRatingToFile %s" %e,0)

View file

@ -1,358 +1,346 @@
# -*- 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().downloadUrl
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):
log = self.logMsg
window = utils.window
settings = utils.settings
doUtils = self.doUtils
item = self.item
API = self.API
listitem = xbmcgui.ListItem()
playutils = putils.PlayUtils(item)
log("Play called.", 1)
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 = window('emby_playbackProps') == "true"
introsPlaylist = False
dummyPlaylist = False
log("Playlist start position: %s" % startPos, 2)
log("Playlist plugin position: %s" % currentPosition, 2)
log("Playlist size: %s" % sizePlaylist, 2)
############### 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:
window('emby_playbackProps', value="true")
log("Setting up properties in playlist.", 1)
if (not homeScreen and not seektime and
window('emby_customPlaylist') != "true"):
log("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 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(url)
if intros['TotalRecordCount'] != 0:
getTrailers = True
if settings('askCinema') == "true":
resp = xbmcgui.Dialog().yesno("Emby for Kodi", utils.language(33016))
if not resp:
# User selected to not play trailers
getTrailers = False
log("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()
log("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 seektime and not sizePlaylist:
# Extend our current playlist with the actual item to play
# only if there's no playlist first
log("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(url)
for part in parts['Items']:
additionalListItem = xbmcgui.ListItem()
additionalPlayurl = putils.PlayUtils(part).getPlayUrl()
log("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.
log("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:
log("Resetting properties playback flag.", 2)
window('emby_playbackProps', clear=True)
#self.pl.verifyPlaylist()
########## SETUP MAIN ITEM ##########
# For transcoding only, ask for audio/subs pref
if window('emby_%s.playmethod' % playurl) == "Transcode":
playurl = playutils.audioSubsPref(playurl, listitem)
window('emby_%s.playmethod' % playurl, value="Transcode")
listitem.setPath(playurl)
self.setProperties(playurl, listitem)
############### PLAYBACK ################
if homeScreen and seektime and window('emby_customPlaylist') != "true":
log("Play as a widget item.", 1)
self.setListItem(listitem)
xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, listitem)
elif ((introsPlaylist and window('emby_customPlaylist') == "true") or
(homeScreen and not sizePlaylist)):
# Playlist was created just now, play it.
log("Play playlist.", 1)
xbmc.Player().play(playlist, startpos=startPos)
else:
log("Play as a regular item.", 1)
xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, listitem)
def setProperties(self, playurl, listitem):
window = utils.window
# Set all properties necessary for plugin path playback
item = self.item
itemid = item['Id']
itemtype = item['Type']
embyitem = "emby_%s" % playurl
window('%s.runtime' % embyitem, value=str(item.get('RunTimeTicks')))
window('%s.type' % embyitem, value=itemtype)
window('%s.itemid' % embyitem, value=itemid)
if itemtype == "Episode":
window('%s.refreshid' % embyitem, value=item.get('SeriesId'))
else:
window('%s.refreshid' % embyitem, value=itemid)
# Append external subtitles to stream
playmethod = utils.window('%s.playmethod' % embyitem)
# Only for direct stream
if playmethod in ("DirectStream"):
# Direct play automatically appends external
subtitles = self.externalSubs(playurl)
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
itemtype = 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 itemtype:
# 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'])
# -*- 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().downloadUrl
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):
window = utils.window
settings = utils.settings
listitem = xbmcgui.ListItem()
playutils = putils.PlayUtils(self.item)
self.logMsg("Play called.", 1)
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 = window('emby_playbackProps') == "true"
introsPlaylist = False
dummyPlaylist = False
self.logMsg("Playlist start position: %s" % startPos, 2)
self.logMsg("Playlist plugin position: %s" % currentPosition, 2)
self.logMsg("Playlist size: %s" % sizePlaylist, 2)
############### RESUME POINT ################
userdata = self.API.getUserData()
seektime = self.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:
window('emby_playbackProps', value="true")
self.logMsg("Setting up properties in playlist.", 1)
if (not homeScreen and not seektime and
window('emby_customPlaylist') != "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, self.item['Type'].lower())
currentPosition += 1
############### -- CHECK FOR INTROS ################
if 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 = self.doUtils(url)
if intros['TotalRecordCount'] != 0:
getTrailers = True
if settings('askCinema') == "true":
resp = xbmcgui.Dialog().yesno("Emby for Kodi", utils.language(33016))
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 seektime 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, self.item['Type'].lower())
# Ensure that additional parts are played after the main item
currentPosition += 1
############### -- CHECK FOR ADDITIONAL PARTS ################
if self.item.get('PartCount'):
# Only add to the playlist after intros have played
partcount = self.item['PartCount']
url = "{server}/emby/Videos/%s/AdditionalParts?format=json" % itemid
parts = self.doUtils(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)
window('emby_playbackProps', clear=True)
#self.pl.verifyPlaylist()
########## SETUP MAIN ITEM ##########
# For transcoding only, ask for audio/subs pref
if window('emby_%s.playmethod' % playurl) == "Transcode":
playurl = playutils.audioSubsPref(playurl, listitem)
window('emby_%s.playmethod' % playurl, value="Transcode")
listitem.setPath(playurl)
self.setProperties(playurl, listitem)
############### PLAYBACK ################
if homeScreen and seektime and window('emby_customPlaylist') != "true":
self.logMsg("Play as a widget item.", 1)
self.setListItem(listitem)
xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, listitem)
elif ((introsPlaylist and window('emby_customPlaylist') == "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):
window = utils.window
# Set all properties necessary for plugin path playback
itemid = self.item['Id']
itemtype = self.item['Type']
embyitem = "emby_%s" % playurl
window('%s.runtime' % embyitem, value=str(self.item.get('RunTimeTicks')))
window('%s.type' % embyitem, value=itemtype)
window('%s.itemid' % embyitem, value=itemid)
if itemtype == "Episode":
window('%s.refreshid' % embyitem, value=self.item.get('SeriesId'))
else:
window('%s.refreshid' % embyitem, value=itemid)
# Append external subtitles to stream
playmethod = utils.window('%s.playmethod' % embyitem)
# Only for direct stream
if playmethod in ("DirectStream"):
# Direct play automatically appends external
subtitles = self.externalSubs(playurl)
listitem.setSubtitles(subtitles)
self.setArtwork(listitem)
def externalSubs(self, playurl):
externalsubs = []
mapping = {}
itemid = self.item['Id']
try:
mediastreams = self.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
allartwork = self.artwork.getAllArtwork(self.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):
people = self.API.getPeople()
studios = self.API.getStudios()
metadata = {
'title': self.item.get('Name', "Missing name"),
'year': self.item.get('ProductionYear'),
'plot': self.API.getOverview(),
'director': people.get('Director'),
'writer': people.get('Writer'),
'mpaa': self.API.getMpaa(),
'genre': " / ".join(self.item['Genres']),
'studio': " / ".join(studios),
'aired': self.API.getPremiereDate(),
'rating': self.item.get('CommunityRating'),
'votes': self.item.get('VoteCount')
}
if "Episode" in self.item['Type']:
# Only for tv shows
thumbId = self.item.get('SeriesId')
season = self.item.get('ParentIndexNumber', -1)
episode = self.item.get('IndexNumber', -1)
show = self.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)

File diff suppressed because it is too large Load diff

View file

@ -1,204 +1,196 @@
# -*- coding: utf-8 -*-
#################################################################################################
import json
import xbmc
import xbmcgui
import xbmcplugin
import clientinfo
import playutils
import playbackutils
import embydb_functions as embydb
import read_embyserver as embyserver
import utils
#################################################################################################
class Playlist():
def __init__(self):
self.clientInfo = clientinfo.ClientInfo()
self.addonName = self.clientInfo.getAddonName()
self.userid = utils.window('emby_currUser')
self.server = utils.window('emby_server%s' % self.userid)
self.emby = embyserver.Read_EmbyServer()
def logMsg(self, msg, lvl=1):
self.className = self.__class__.__name__
utils.logMsg("%s %s" % (self.addonName, self.className), msg, lvl)
def playAll(self, itemids, startat):
log = self.logMsg
window = utils.window
embyconn = utils.kodiSQL('emby')
embycursor = embyconn.cursor()
emby_db = embydb.Embydb_Functions(embycursor)
player = xbmc.Player()
playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
playlist.clear()
log("---*** PLAY ALL ***---", 1)
log("Items: %s and start at: %s" % (itemids, startat), 1)
started = False
window('emby_customplaylist', value="true")
if startat != 0:
# Seek to the starting position
window('emby_customplaylist.seektime', str(startat))
for itemid in itemids:
embydb_item = emby_db.getItem_byId(itemid)
try:
dbid = embydb_item[0]
mediatype = embydb_item[4]
except TypeError:
# Item is not found in our database, add item manually
log("Item was not found in the database, manually adding item.", 1)
item = self.emby.getItem(itemid)
self.addtoPlaylist_xbmc(playlist, item)
else:
# Add to playlist
self.addtoPlaylist(dbid, mediatype)
log("Adding %s to playlist." % itemid, 1)
if not started:
started = True
player.play(playlist)
self.verifyPlaylist()
embycursor.close()
def modifyPlaylist(self, itemids):
log = self.logMsg
embyconn = utils.kodiSQL('emby')
embycursor = embyconn.cursor()
emby_db = embydb.Embydb_Functions(embycursor)
log("---*** ADD TO PLAYLIST ***---", 1)
log("Items: %s" % itemids, 1)
player = xbmc.Player()
playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
for itemid in itemids:
embydb_item = emby_db.getItem_byId(itemid)
try:
dbid = embydb_item[0]
mediatype = embydb_item[4]
except TypeError:
# Item is not found in our database, add item manually
item = self.emby.getItem(itemid)
self.addtoPlaylist_xbmc(playlist, item)
else:
# Add to playlist
self.addtoPlaylist(dbid, mediatype)
log("Adding %s to playlist." % itemid, 1)
self.verifyPlaylist()
embycursor.close()
return playlist
def addtoPlaylist(self, dbid=None, mediatype=None, url=None):
pl = {
'jsonrpc': "2.0",
'id': 1,
'method': "Playlist.Add",
'params': {
'playlistid': 1
}
}
if dbid is not None:
pl['params']['item'] = {'%sid' % mediatype: int(dbid)}
else:
pl['params']['item'] = {'file': url}
result = xbmc.executeJSONRPC(json.dumps(pl))
self.logMsg(result, 2)
def addtoPlaylist_xbmc(self, playlist, item):
itemid = item['Id']
playurl = playutils.PlayUtils(item).getPlayUrl()
if not playurl:
# Playurl failed
self.logMsg("Failed to retrieve playurl.", 1)
return
self.logMsg("Playurl: %s" % playurl)
listitem = xbmcgui.ListItem()
playbackutils.PlaybackUtils(item).setProperties(playurl, listitem)
playlist.add(playurl, listitem)
def insertintoPlaylist(self, position, dbid=None, mediatype=None, url=None):
pl = {
'jsonrpc': "2.0",
'id': 1,
'method': "Playlist.Insert",
'params': {
'playlistid': 1,
'position': position
}
}
if dbid is not None:
pl['params']['item'] = {'%sid' % mediatype: int(dbid)}
else:
pl['params']['item'] = {'file': url}
result = xbmc.executeJSONRPC(json.dumps(pl))
self.logMsg(result, 2)
def verifyPlaylist(self):
pl = {
'jsonrpc': "2.0",
'id': 1,
'method': "Playlist.GetItems",
'params': {
'playlistid': 1
}
}
result = xbmc.executeJSONRPC(json.dumps(pl))
self.logMsg(result, 2)
def removefromPlaylist(self, position):
pl = {
'jsonrpc': "2.0",
'id': 1,
'method': "Playlist.Remove",
'params': {
'playlistid': 1,
'position': position
}
}
result = xbmc.executeJSONRPC(json.dumps(pl))
self.logMsg(result, 2)
# -*- coding: utf-8 -*-
#################################################################################################
import json
import xbmc
import xbmcgui
import xbmcplugin
import clientinfo
import playutils
import playbackutils
import embydb_functions as embydb
import read_embyserver as embyserver
import utils
#################################################################################################
class Playlist():
def __init__(self):
self.clientInfo = clientinfo.ClientInfo()
self.addonName = self.clientInfo.getAddonName()
self.userid = utils.window('emby_currUser')
self.server = utils.window('emby_server%s' % self.userid)
self.emby = embyserver.Read_EmbyServer()
def logMsg(self, msg, lvl=1):
self.className = self.__class__.__name__
utils.logMsg("%s %s" % (self.addonName, self.className), msg, lvl)
def playAll(self, itemids, startat):
window = utils.window
embyconn = utils.kodiSQL('emby')
embycursor = embyconn.cursor()
emby_db = embydb.Embydb_Functions(embycursor)
player = xbmc.Player()
playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
playlist.clear()
self.logMsg("---*** PLAY ALL ***---", 1)
self.logMsg("Items: %s and start at: %s" % (itemids, startat), 1)
started = False
window('emby_customplaylist', value="true")
if startat != 0:
# Seek to the starting position
window('emby_customplaylist.seektime', str(startat))
for itemid in itemids:
embydb_item = emby_db.getItem_byId(itemid)
try:
dbid = embydb_item[0]
mediatype = embydb_item[4]
except TypeError:
# Item is not found in our database, add item manually
self.logMsg("Item was not found in the database, manually adding item.", 1)
item = self.emby.getItem(itemid)
self.addtoPlaylist_xbmc(playlist, item)
else:
# Add to playlist
self.addtoPlaylist(dbid, mediatype)
self.logMsg("Adding %s to playlist." % itemid, 1)
if not started:
started = True
player.play(playlist)
self.verifyPlaylist()
embycursor.close()
def modifyPlaylist(self, itemids):
embyconn = utils.kodiSQL('emby')
embycursor = embyconn.cursor()
emby_db = embydb.Embydb_Functions(embycursor)
self.logMsg("---*** ADD TO PLAYLIST ***---", 1)
self.logMsg("Items: %s" % itemids, 1)
player = xbmc.Player()
playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
for itemid in itemids:
embydb_item = emby_db.getItem_byId(itemid)
try:
dbid = embydb_item[0]
mediatype = embydb_item[4]
except TypeError:
# Item is not found in our database, add item manually
item = self.emby.getItem(itemid)
self.addtoPlaylist_xbmc(playlist, item)
else:
# Add to playlist
self.addtoPlaylist(dbid, mediatype)
self.logMsg("Adding %s to playlist." % itemid, 1)
self.verifyPlaylist()
embycursor.close()
return playlist
def addtoPlaylist(self, dbid=None, mediatype=None, url=None):
pl = {
'jsonrpc': "2.0",
'id': 1,
'method': "Playlist.Add",
'params': {
'playlistid': 1
}
}
if dbid is not None:
pl['params']['item'] = {'%sid' % mediatype: int(dbid)}
else:
pl['params']['item'] = {'file': url}
self.logMsg(xbmc.executeJSONRPC(json.dumps(pl)), 2)
def addtoPlaylist_xbmc(self, playlist, item):
playurl = playutils.PlayUtils(item).getPlayUrl()
if not playurl:
# Playurl failed
self.logMsg("Failed to retrieve playurl.", 1)
return
self.logMsg("Playurl: %s" % playurl)
listitem = xbmcgui.ListItem()
playbackutils.PlaybackUtils(item).setProperties(playurl, listitem)
playlist.add(playurl, listitem)
def insertintoPlaylist(self, position, dbid=None, mediatype=None, url=None):
pl = {
'jsonrpc': "2.0",
'id': 1,
'method': "Playlist.Insert",
'params': {
'playlistid': 1,
'position': position
}
}
if dbid is not None:
pl['params']['item'] = {'%sid' % mediatype: int(dbid)}
else:
pl['params']['item'] = {'file': url}
self.logMsg(xbmc.executeJSONRPC(json.dumps(pl)), 2)
def verifyPlaylist(self):
pl = {
'jsonrpc': "2.0",
'id': 1,
'method': "Playlist.GetItems",
'params': {
'playlistid': 1
}
}
self.logMsg(xbmc.executeJSONRPC(json.dumps(pl)), 2)
def removefromPlaylist(self, position):
pl = {
'jsonrpc': "2.0",
'id': 1,
'method': "Playlist.Remove",
'params': {
'playlistid': 1,
'position': position
}
}
self.logMsg(xbmc.executeJSONRPC(json.dumps(pl)), 2)

View file

@ -1,458 +1,426 @@
# -*- 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):
log = self.logMsg
window = utils.window
item = self.item
playurl = None
if (item.get('Type') in ("Recording", "TvChannel") and
item.get('MediaSources') and item['MediaSources'][0]['Protocol'] == "Http"):
# Play LiveTV or recordings
log("File protocol is http (livetv).", 1)
playurl = "%s/emby/Videos/%s/live.m3u8?static=true" % (self.server, item['Id'])
window('emby_%s.playmethod' % playurl, value="Transcode")
elif item.get('MediaSources') and item['MediaSources'][0]['Protocol'] == "Http":
# Only play as http, used for channels, or online hosting of content
log("File protocol is http.", 1)
playurl = self.httpPlay()
window('emby_%s.playmethod' % playurl, value="DirectStream")
elif self.isDirectPlay():
log("File is direct playing.", 1)
playurl = self.directPlay()
playurl = playurl.encode('utf-8')
# Set playmethod property
window('emby_%s.playmethod' % playurl, value="DirectPlay")
elif self.isDirectStream():
log("File is direct streaming.", 1)
playurl = self.directStream()
# Set playmethod property
window('emby_%s.playmethod' % playurl, value="DirectStream")
elif self.isTranscoding():
log("File is transcoding.", 1)
playurl = self.transcoding()
# Set playmethod property
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 mediatype == "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):
log = self.logMsg
lang = utils.language
settings = utils.settings
dialog = xbmcgui.Dialog()
item = self.item
# Requirement: Filesystem, Accessible path
if settings('playFromStream') == "true":
# User forcing to play via HTTP
log("Can't direct play, play from HTTP enabled.", 1)
return False
videotrack = item['MediaSources'][0]['Name']
transcodeH265 = settings('transcodeH265')
if transcodeH265 in ("1", "2", "3") and ("HEVC" in videotrack or "H265" in videotrack):
# Avoid H265/HEVC depending on the resolution
resolution = int(videotrack.split("P", 1)[0])
res = {
'1': 480,
'2': 720,
'3': 1080
}
log("Resolution is: %sP, transcode for resolution: %sP+"
% (resolution, res[transcodeH265]), 1)
if res[transcodeH265] <= resolution:
return False
canDirectPlay = item['MediaSources'][0]['SupportsDirectPlay']
# Make sure direct play is supported by the server
if not canDirectPlay:
log("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():
log("Unable to direct play.")
try:
count = int(settings('failCount'))
except ValueError:
count = 0
log("Direct play failed: %s times." % count, 1)
if count < 2:
# Let the user know that direct play failed
settings('failCount', value=str(count+1))
dialog.notification(
heading="Emby for Kodi",
message=lang(33011),
icon="special://home/addons/plugin.video.emby/icon.png",
sound=False)
elif settings('playFromStream') != "true":
# Permanently set direct stream as true
settings('playFromStream', value="true")
settings('failCount', value="0")
dialog.notification(
heading="Emby for Kodi",
message=lang(33012),
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):
log = self.logMsg
if 'Path' not in self.item:
# File has no path defined in server
return False
# Convert path to direct play
path = self.directPlay()
log("Verifying path: %s" % path, 1)
if xbmcvfs.exists(path):
log("Path exists.", 1)
return True
elif ":" not in path:
log("Can't verify path, assumed linux. Still try to direct play.", 1)
return True
else:
log("Failed to find file.", 1)
return False
def isDirectStream(self):
log = self.logMsg
item = self.item
videotrack = item['MediaSources'][0]['Name']
transcodeH265 = utils.settings('transcodeH265')
if transcodeH265 in ("1", "2", "3") and ("HEVC" in videotrack or "H265" in videotrack):
# Avoid H265/HEVC depending on the resolution
resolution = int(videotrack.split("P", 1)[0])
res = {
'1': 480,
'2': 720,
'3': 1080
}
log("Resolution is: %sP, transcode for resolution: %sP+"
% (resolution, res[transcodeH265]), 1)
if res[transcodeH265] <= resolution:
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():
log("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']
itemtype = item['Type']
if 'Path' in item and item['Path'].endswith('.strm'):
# Allow strm loading when direct streaming
playurl = self.directPlay()
elif itemtype == "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):
log = self.logMsg
settings = self.getBitrate()*1000
try:
sourceBitrate = int(self.item['MediaSources'][0]['Bitrate'])
except (KeyError, TypeError):
log("Bitrate value is missing.", 1)
else:
log("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, listitem):
log = self.logMsg
lang = utils.language
dialog = xbmcgui.Dialog()
# For transcoding only
# Present the list of audio to select from
audioStreamsList = {}
audioStreams = []
audioStreamsChannelsList = {}
subtitleStreamsList = {}
subtitleStreams = ['No subtitles']
downloadableStreams = []
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:
try:
track = "%s - %s" % (index, stream['Language'])
except:
track = "%s - %s" % (index, stream['Codec'])
default = stream['IsDefault']
forced = stream['IsForced']
downloadable = stream['IsTextSubtitleStream']
if default:
track = "%s - Default" % track
if forced:
track = "%s - Forced" % track
if downloadable:
downloadableStreams.append(index)
subtitleStreamsList[track] = index
subtitleStreams.append(track)
if len(audioStreams) > 1:
resp = dialog.select(lang(33013), 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 = dialog.select(lang(33014), subtitleStreams)
if resp == 0:
# User selected no subtitles
pass
elif resp > -1:
# User selected subtitles
selected = subtitleStreams[resp]
selectSubsIndex = subtitleStreamsList[selected]
# Load subtitles in the listitem if downloadable
if selectSubsIndex in downloadableStreams:
itemid = item['Id']
url = [("%s/Videos/%s/%s/Subtitles/%s/Stream.srt"
% (self.server, itemid, itemid, selectSubsIndex))]
log("Set up subtitles: %s %s" % (selectSubsIndex, url), 1)
listitem.setSubtitles(url)
else:
# Burn subtitles
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"
# -*- 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):
window = utils.window
playurl = None
if (self.item.get('Type') in ("Recording", "TvChannel") and
self.item.get('MediaSources') and self.item['MediaSources'][0]['Protocol'] == "Http"):
# Play LiveTV or recordings
self.logMsg("File protocol is http (livetv).", 1)
playurl = "%s/emby/Videos/%s/live.m3u8?static=true" % (self.server, self.item['Id'])
window('emby_%s.playmethod' % playurl, value="Transcode")
elif self.item.get('MediaSources') and self.item['MediaSources'][0]['Protocol'] == "Http":
# Only play as http, used for channels, or online hosting of content
self.logMsg("File protocol is http.", 1)
playurl = self.httpPlay()
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
window('emby_%s.playmethod' % playurl, value="DirectPlay")
elif self.isDirectStream():
self.logMsg("File is direct streaming.", 1)
playurl = self.directStream()
# Set playmethod property
window('emby_%s.playmethod' % playurl, value="DirectStream")
elif self.isTranscoding():
self.logMsg("File is transcoding.", 1)
playurl = self.transcoding()
# Set playmethod property
window('emby_%s.playmethod' % playurl, value="Transcode")
return playurl
def httpPlay(self):
# Audio, Video, Photo
itemid = self.item['Id']
mediatype = self.item['MediaType']
if mediatype == "Audio":
playurl = "%s/emby/Audio/%s/stream" % (self.server, itemid)
else:
playurl = "%s/emby/Videos/%s/stream?static=true" % (self.server, itemid)
return playurl
def isDirectPlay(self):
lang = utils.language
settings = utils.settings
dialog = xbmcgui.Dialog()
# Requirement: Filesystem, Accessible path
if settings('playFromStream') == "true":
# User forcing to play via HTTP
self.logMsg("Can't direct play, play from HTTP enabled.", 1)
return False
videotrack = self.item['MediaSources'][0]['Name']
transcodeH265 = settings('transcodeH265')
if transcodeH265 in ("1", "2", "3") and ("HEVC" in videotrack or "H265" in videotrack):
# Avoid H265/HEVC depending on the resolution
resolution = int(videotrack.split("P", 1)[0])
res = {
'1': 480,
'2': 720,
'3': 1080
}
self.logMsg("Resolution is: %sP, transcode for resolution: %sP+"
% (resolution, res[transcodeH265]), 1)
if res[transcodeH265] <= resolution:
return False
canDirectPlay = self.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 = self.item['LocationType']
if location == "FileSystem":
# Verify the path
if not self.fileExists():
self.logMsg("Unable to direct play.")
try:
count = int(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
settings('failCount', value=str(count+1))
dialog.notification(
heading="Emby for Kodi",
message=lang(33011),
icon="special://home/addons/plugin.video.emby/icon.png",
sound=False)
elif settings('playFromStream') != "true":
# Permanently set direct stream as true
settings('playFromStream', value="true")
settings('failCount', value="0")
dialog.notification(
heading="Emby for Kodi",
message=lang(33012),
icon="special://home/addons/plugin.video.emby/icon.png",
sound=False)
return False
return True
def directPlay(self):
try:
playurl = self.item['MediaSources'][0]['Path']
except (IndexError, KeyError):
playurl = self.item['Path']
if self.item.get('VideoType'):
# Specific format modification
if self.item['VideoType'] == "Dvd":
playurl = "%s/VIDEO_TS/VIDEO_TS.IFO" % playurl
elif self.item['VideoType'] == "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.", 1)
return False
def isDirectStream(self):
videotrack = self.item['MediaSources'][0]['Name']
transcodeH265 = utils.settings('transcodeH265')
if transcodeH265 in ("1", "2", "3") and ("HEVC" in videotrack or "H265" in videotrack):
# Avoid H265/HEVC depending on the resolution
resolution = int(videotrack.split("P", 1)[0])
res = {
'1': 480,
'2': 720,
'3': 1080
}
self.logMsg("Resolution is: %sP, transcode for resolution: %sP+"
% (resolution, res[transcodeH265]), 1)
if res[transcodeH265] <= resolution:
return False
# Requirement: BitRate, supported encoding
canDirectStream = self.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):
if 'Path' in self.item and self.item['Path'].endswith('.strm'):
# Allow strm loading when direct streaming
playurl = self.directPlay()
elif self.item['Type'] == "Audio":
playurl = "%s/emby/Audio/%s/stream.mp3" % (self.server, self.item['Id'])
else:
playurl = "%s/emby/Videos/%s/stream?static=true" % (self.server, self.item['Id'])
return playurl
def isNetworkSufficient(self):
settings = self.getBitrate()*1000
try:
sourceBitrate = int(self.item['MediaSources'][0]['Bitrate'])
except (KeyError, TypeError):
self.logMsg("Bitrate value is missing.", 1)
else:
self.logMsg("The add-on settings bitrate is: %s, the video bitrate required is: %s"
% (settings, sourceBitrate), 1)
if settings < sourceBitrate:
return False
return True
def isTranscoding(self):
# Make sure the server supports it
if not self.item['MediaSources'][0]['SupportsTranscoding']:
return False
return True
def transcoding(self):
if 'Path' in self.item and self.item['Path'].endswith('.strm'):
# Allow strm loading when transcoding
playurl = self.directPlay()
else:
itemid = self.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
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(utils.settings('videoBitrate'), 2147483)
def audioSubsPref(self, url, listitem):
lang = utils.language
dialog = xbmcgui.Dialog()
# For transcoding only
# Present the list of audio to select from
audioStreamsList = {}
audioStreams = []
audioStreamsChannelsList = {}
subtitleStreamsList = {}
subtitleStreams = ['No subtitles']
downloadableStreams = []
selectAudioIndex = ""
selectSubsIndex = ""
playurlprefs = "%s" % url
try:
mediasources = self.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']
if 'Audio' in stream['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 stream['Type']:
try:
track = "%s - %s" % (index, stream['Language'])
except:
track = "%s - %s" % (index, stream['Codec'])
default = stream['IsDefault']
forced = stream['IsForced']
downloadable = stream['IsTextSubtitleStream']
if default:
track = "%s - Default" % track
if forced:
track = "%s - Forced" % track
if downloadable:
downloadableStreams.append(index)
subtitleStreamsList[track] = index
subtitleStreams.append(track)
if len(audioStreams) > 1:
resp = dialog.select(lang(33013), 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 = dialog.select(lang(33014), subtitleStreams)
if resp == 0:
# User selected no subtitles
pass
elif resp > -1:
# User selected subtitles
selected = subtitleStreams[resp]
selectSubsIndex = subtitleStreamsList[selected]
# Load subtitles in the listitem if downloadable
if selectSubsIndex in downloadableStreams:
itemid = self.item['Id']
url = [("%s/Videos/%s/%s/Subtitles/%s/Stream.srt"
% (self.server, itemid, itemid, selectSubsIndex))]
self.logMsg("Set up subtitles: %s %s" % (selectSubsIndex, url), 1)
listitem.setSubtitles(url)
else:
# Burn subtitles
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

File diff suppressed because it is too large Load diff

View file

@ -1,487 +1,468 @@
# -*- 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()
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):
log = self.logMsg
window = utils.window
settings = utils.settings
username = self.getUsername()
w_userId = window('emby_currUser')
s_userId = settings('userId%s' % username)
# Verify the window property
if w_userId:
if not s_userId:
# Save access token if it's missing from settings
settings('userId%s' % username, value=w_userId)
log("Returning userId from WINDOW for username: %s UserId: %s"
% (username, w_userId), 2)
return w_userId
# Verify the settings
elif s_userId:
log("Returning userId from SETTINGS for username: %s userId: %s"
% (username, s_userId), 2)
return s_userId
# No userId found
else:
log("No userId saved for username: %s." % username, 1)
def getServer(self, prefix=True):
settings = utils.settings
alternate = settings('altip') == "true"
if alternate:
# Alternate host
HTTPS = settings('secondhttps') == "true"
host = settings('secondipaddress')
port = settings('secondport')
else:
# Original host
HTTPS = settings('https') == "true"
host = settings('ipaddress')
port = 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):
log = self.logMsg
window = utils.window
settings = utils.settings
username = self.getUsername()
userId = self.getUserId()
w_token = window('emby_accessToken%s' % userId)
s_token = settings('accessToken')
# Verify the window property
if w_token:
if not s_token:
# Save access token if it's missing from settings
settings('accessToken', value=w_token)
log("Returning accessToken from WINDOW for username: %s accessToken: %s"
% (username, w_token), 2)
return w_token
# Verify the settings
elif s_token:
log("Returning accessToken from SETTINGS for username: %s accessToken: %s"
% (username, s_token), 2)
window('emby_accessToken%s' % username, value=s_token)
return s_token
else:
log("No token found.", 1)
return ""
def getSSLverify(self):
# Verify host certificate
settings = utils.settings
s_sslverify = settings('sslverify')
if settings('altip') == "true":
s_sslverify = settings('secondsslverify')
if s_sslverify == "true":
return True
else:
return False
def getSSL(self):
# Client side certificate
settings = utils.settings
s_cert = settings('sslcert')
if settings('altip') == "true":
s_cert = settings('secondsslcert')
if s_cert == "None":
return None
else:
return s_cert
def setUserPref(self):
doUtils = self.doUtils.downloadUrl
art = artwork.Artwork()
url = "{server}/emby/Users/{UserId}?format=json"
result = doUtils(url)
self.userSettings = result
# Set user image for skin display
if result.get('PrimaryImageTag'):
utils.window('EmbyUserImage', value=art.getUserArtwork(result['Id'], 'Primary'))
# Set resume point max
url = "{server}/emby/System/Configuration?format=json"
result = doUtils(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
log = self.logMsg
window = utils.window
url = "{server}/emby/Users?format=json"
result = self.doUtils.downloadUrl(url)
if result == False:
# Access is restricted, set in downloadutils.py via exception
log("Access is restricted.", 1)
self.HasAccess = False
elif window('emby_online') != "true":
# Server connection failed
pass
elif window('emby_serverStatus') == "restricted":
log("Access is granted.", 1)
self.HasAccess = True
window('emby_serverStatus', clear=True)
xbmcgui.Dialog().notification("Emby for Kodi", utils.language(33007))
def loadCurrUser(self, authenticated=False):
window = utils.window
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)
window('emby_currUser', value=userId)
window('emby_accessToken%s' % userId, value=self.currToken)
result = doUtils.downloadUrl(url)
if result == 401:
# Token is no longer valid
self.resetClient()
return False
# Set to windows property
window('emby_currUser', value=userId)
window('emby_accessToken%s' % userId, value=self.currToken)
window('emby_server%s' % userId, value=self.currServer)
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):
log = self.logMsg
lang = utils.language
window = utils.window
settings = utils.settings
dialog = xbmcgui.Dialog()
# 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:
log("No settings.xml found.", 1)
self.auth = False
return
# If no user information
elif not server or not username:
log("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:
log("Current user: %s" % self.currUser, 1)
log("Current userId: %s" % self.currUserId, 1)
log("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 = dialog.input(
heading="%s %s" % (lang(33008), username.decode('utf-8')),
option=xbmcgui.ALPHANUM_HIDE_INPUT)
# If password dialog is cancelled
if not password:
log("No password entered.", 0)
window('emby_serverStatus', value="Stop")
self.auth = False
return
break
else:
# Manual login, user is hidden
password = dialog.input(
heading="%s %s" % (lang(33008), 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}
log(data, 2)
result = self.doUtils.downloadUrl(url, postBody=data, type="POST", authenticate=False)
try:
log("Auth response: %s" % result, 1)
accessToken = result['AccessToken']
except (KeyError, TypeError):
log("Failed to retrieve the api key.", 1)
accessToken = None
if accessToken is not None:
self.currUser = username
dialog.notification("Emby for Kodi",
"%s %s!" % (lang(33000), self.currUser.decode('utf-8')))
userId = result['User']['Id']
settings('accessToken', value=accessToken)
settings('userId%s' % username, value=userId)
log("User Authenticated: %s" % accessToken, 1)
self.loadCurrUser(authenticated=True)
window('emby_serverStatus', clear=True)
self.retry = 0
else:
log("User authentication failed.", 1)
settings('accessToken', value="")
settings('userId%s' % username, value="")
dialog.ok(lang(33001), lang(33009))
# Give two attempts at entering password
if self.retry == 2:
log("Too many retries. "
"You can retry by resetting attempts in the addon settings.", 1)
window('emby_serverStatus', value="Stop")
dialog.ok(lang(33001), lang(33010))
self.retry += 1
self.auth = False
def resetClient(self):
log = self.logMsg
log("Reset UserClient authentication.", 1)
userId = self.getUserId()
if self.currToken is not None:
# In case of 401, removed saved token
utils.settings('accessToken', value="")
utils.window('emby_accessToken%s' % userId, clear=True)
self.currToken = None
log("User token has been removed.", 1)
self.auth = True
self.currUser = None
def run(self):
log = self.logMsg
window = utils.window
monitor = xbmc.Monitor()
log("----===## Starting UserClient ##===----", 0)
while not monitor.abortRequested():
status = 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
window('emby_serverStatus', value="Auth")
self.resetClient()
if self.auth and (self.currUser is None):
# Try to authenticate user
status = 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 = 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
log("Server found: %s" % server, 2)
log("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()
log("##===---- UserClient Stopped ----===##", 0)
def stopClient(self):
# When emby for kodi terminates
# -*- 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()
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):
window = utils.window
settings = utils.settings
username = self.getUsername()
w_userId = window('emby_currUser')
s_userId = settings('userId%s' % username)
# Verify the window property
if w_userId:
if not s_userId:
# Save access token if it's missing from settings
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):
settings = utils.settings
alternate = settings('altip') == "true"
if alternate:
# Alternate host
HTTPS = settings('secondhttps') == "true"
host = settings('secondipaddress')
port = settings('secondport')
else:
# Original host
HTTPS = settings('https') == "true"
host = settings('ipaddress')
port = 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):
window = utils.window
settings = utils.settings
username = self.getUsername()
userId = self.getUserId()
w_token = window('emby_accessToken%s' % userId)
s_token = settings('accessToken')
# Verify the window property
if w_token:
if not s_token:
# Save access token if it's missing from settings
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)
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
settings = utils.settings
s_sslverify = settings('sslverify')
if settings('altip') == "true":
s_sslverify = settings('secondsslverify')
if s_sslverify == "true":
return True
else:
return False
def getSSL(self):
# Client side certificate
settings = utils.settings
s_cert = settings('sslcert')
if settings('altip') == "true":
s_cert = settings('secondsslcert')
if s_cert == "None":
return None
else:
return s_cert
def setUserPref(self):
doUtils = self.doUtils.downloadUrl
result = doUtils("{server}/emby/Users/{UserId}?format=json")
self.userSettings = result
# Set user image for skin display
if result.get('PrimaryImageTag'):
utils.window('EmbyUserImage', value=artwork.Artwork().getUserArtwork(result['Id'], 'Primary'))
# Set resume point max
result = doUtils("{server}/emby/System/Configuration?format=json")
utils.settings('markPlayed', value=str(result['MaxResumePct']))
def getPublicUsers(self):
# Get public Users
result = self.doUtils.downloadUrl("%s/emby/Users/Public?format=json" % self.getServer(), authenticate=False)
if result != "":
return result
else:
# Server connection failed
return False
def hasAccess(self):
# hasAccess is verified in service.py
window = utils.window
result = self.doUtils.downloadUrl("{server}/emby/Users?format=json")
if result == False:
# Access is restricted, set in downloadutils.py via exception
self.logMsg("Access is restricted.", 1)
self.HasAccess = False
elif window('emby_online') != "true":
# Server connection failed
pass
elif window('emby_serverStatus') == "restricted":
self.logMsg("Access is granted.", 1)
self.HasAccess = True
window('emby_serverStatus', clear=True)
xbmcgui.Dialog().notification("Emby for Kodi", utils.language(33007))
def loadCurrUser(self, authenticated=False):
window = utils.window
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)
window('emby_currUser', value=userId)
window('emby_accessToken%s' % userId, value=self.currToken)
result = doUtils.downloadUrl(url)
if result == 401:
# Token is no longer valid
self.resetClient()
return False
# Set to windows property
window('emby_currUser', value=userId)
window('emby_accessToken%s' % userId, value=self.currToken)
window('emby_server%s' % userId, value=self.currServer)
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):
lang = utils.language
window = utils.window
settings = utils.settings
dialog = xbmcgui.Dialog()
# 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 is 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 = dialog.input(
heading="%s %s" % (lang(33008), username.decode('utf-8')),
option=xbmcgui.ALPHANUM_HIDE_INPUT)
# If password dialog is cancelled
if not password:
self.logMsg("No password entered.", 0)
window('emby_serverStatus', value="Stop")
self.auth = False
return
break
else:
# Manual login, user is hidden
password = dialog.input(
heading="%s %s" % (lang(33008), username),
option=xbmcgui.ALPHANUM_HIDE_INPUT)
sha1 = hashlib.sha1(password)
sha1 = sha1.hexdigest()
# Authenticate username and password
data = {'username': username, 'password': sha1}
self.logMsg(data, 2)
result = self.doUtils.downloadUrl("%s/emby/Users/AuthenticateByName?format=json" % server, postBody=data, action_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
dialog.notification("Emby for Kodi",
"%s %s!" % (lang(33000), self.currUser.decode('utf-8')))
settings('accessToken', value=accessToken)
settings('userId%s' % username, value=result['User']['Id'])
self.logMsg("User Authenticated: %s" % accessToken, 1)
self.loadCurrUser(authenticated=True)
window('emby_serverStatus', clear=True)
self.retry = 0
else:
self.logMsg("User authentication failed.", 1)
settings('accessToken', value="")
settings('userId%s' % username, value="")
dialog.ok(lang(33001), lang(33009))
# 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)
window('emby_serverStatus', value="Stop")
dialog.ok(lang(33001), lang(33010))
self.retry += 1
self.auth = False
def resetClient(self):
self.logMsg("Reset UserClient authentication.", 1)
if self.currToken is not None:
# In case of 401, removed saved token
utils.settings('accessToken', value="")
utils.window('emby_accessToken%s' % self.getUserId(), clear=True)
self.currToken = None
self.logMsg("User token has been removed.", 1)
self.auth = True
self.currUser = None
def run(self):
window = utils.window
monitor = xbmc.Monitor()
self.logMsg("----===## Starting UserClient ##===----", 0)
while not monitor.abortRequested():
status = 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
window('emby_serverStatus', value="Auth")
self.resetClient()
if self.auth and (self.currUser is None):
# Try to authenticate user
status = 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 = 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

File diff suppressed because it is too large Load diff

View file

@ -1,395 +1,394 @@
# -*- 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, viewid, delete=False):
window = utils.window
kodiversion = self.kodiversion
if viewtype == "mixed":
dirname = "%s - %s" % (viewid, mediatype)
else:
dirname = viewid
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) and not mediatype == "photos":
# 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 window('Emby.nodes.%s.index' % i) == path:
return
if mediatype == "photos":
path = "plugin://plugin.video.emby/?id=%s&mode=getsubfolders" % indexnumber
window('Emby.nodes.%s.index' % indexnumber, value=path)
# Root
if not mediatype == "photos":
if viewtype == "mixed":
specialtag = "%s - %s" % (tagname, mediatype)
root = self.commonRoot(order=0, label=specialtag, tagname=tagname, roottype=0)
else:
root = self.commonRoot(order=0, label=tagname, 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': "nextepisodes",
'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
},
'homevideos':
{
'1': tagname,
'2': 30251,
'11': 30253
},
'photos':
{
'1': tagname,
'2': 30252,
'8': 30255,
'11': 30254
},
'musicvideos':
{
'1': tagname,
'2': 30256,
'4': 30257,
'6': 30258
}
}
nodes = mediatypes[mediatype]
for node in nodes:
nodetype = nodetypes[node]
nodeXML = "%s%s_%s.xml" % (nodepath, viewid, 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 (mediatype == "homevideos" or mediatype == "photos") and nodetype == "all":
# Custom query
path = ("plugin://plugin.video.emby/?id=%s&mode=browsecontent&type=%s"
% (tagname, mediatype))
elif (mediatype == "homevideos" or mediatype == "photos"):
# Custom query
path = ("plugin://plugin.video.emby/?id=%s&mode=browsecontent&type=%s&folderid=%s"
% (tagname, mediatype, nodetype))
elif nodetype == "nextepisodes":
# 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, viewid, nodetype)
if mediatype == "photos":
windowpath = "ActivateWindow(Pictures,%s,return)" % path
else:
windowpath = "ActivateWindow(Video,%s,return)" % path
if nodetype == "all":
if viewtype == "mixed":
templabel = "%s - %s" % (tagname, mediatype)
else:
templabel = label
embynode = "Emby.nodes.%s" % indexnumber
window('%s.title' % embynode, value=templabel)
window('%s.path' % embynode, value=windowpath)
window('%s.content' % embynode, value=path)
window('%s.type' % embynode, value=mediatype)
else:
embynode = "Emby.nodes.%s.%s" % (indexnumber, nodetype)
window('%s.title' % embynode, value=label)
window('%s.path' % embynode, value=windowpath)
window('%s.content' % embynode, value=path)
if mediatype == "photos":
# For photos, we do not create a node in videos but we do want the window props
# to be created.
# To do: add our photos nodes to kodi picture sources somehow
continue
if xbmcvfs.exists(nodeXML):
# Don't recreate xml if already exists
continue
# Create the root
if (nodetype == "nextepisodes" or mediatype == "homevideos" 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):
window = utils.window
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
window('%s.title' % embynode, value=label)
window('%s.path' % embynode, value=windowpath)
window('%s.content' % embynode, value=path)
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):
window = utils.window
self.logMsg("Clearing nodes properties.", 1)
embyprops = 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:
# -*- 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, viewid, delete=False):
window = utils.window
if viewtype == "mixed":
dirname = "%s - %s" % (viewid, mediatype)
else:
dirname = viewid
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) and not mediatype == "photos":
# 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 window('Emby.nodes.%s.index' % i) == path:
return
if mediatype == "photos":
path = "plugin://plugin.video.emby/?id=%s&mode=getsubfolders" % indexnumber
window('Emby.nodes.%s.index' % indexnumber, value=path)
# Root
if not mediatype == "photos":
if viewtype == "mixed":
specialtag = "%s - %s" % (tagname, mediatype)
root = self.commonRoot(order=0, label=specialtag, tagname=tagname, roottype=0)
else:
root = self.commonRoot(order=0, label=tagname, 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': "nextepisodes",
'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
},
'homevideos':
{
'1': tagname,
'2': 30251,
'11': 30253
},
'photos':
{
'1': tagname,
'2': 30252,
'8': 30255,
'11': 30254
},
'musicvideos':
{
'1': tagname,
'2': 30256,
'4': 30257,
'6': 30258
}
}
nodes = mediatypes[mediatype]
for node in nodes:
nodetype = nodetypes[node]
nodeXML = "%s%s_%s.xml" % (nodepath, viewid, 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 (mediatype == "homevideos" or mediatype == "photos") and nodetype == "all":
# Custom query
path = ("plugin://plugin.video.emby/?id=%s&mode=browsecontent&type=%s"
% (tagname, mediatype))
elif (mediatype == "homevideos" or mediatype == "photos"):
# Custom query
path = ("plugin://plugin.video.emby/?id=%s&mode=browsecontent&type=%s&folderid=%s"
% (tagname, mediatype, nodetype))
elif nodetype == "nextepisodes":
# Custom query
path = "plugin://plugin.video.emby/?id=%s&mode=nextup&limit=25" % tagname
elif self.kodiversion == 14 and nodetype == "recentepisodes":
# Custom query
path = "plugin://plugin.video.emby/?id=%s&mode=recentepisodes&limit=25" % tagname
elif self.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, viewid, nodetype)
if mediatype == "photos":
windowpath = "ActivateWindow(Pictures,%s,return)" % path
else:
windowpath = "ActivateWindow(Video,%s,return)" % path
if nodetype == "all":
if viewtype == "mixed":
templabel = "%s - %s" % (tagname, mediatype)
else:
templabel = label
embynode = "Emby.nodes.%s" % indexnumber
window('%s.title' % embynode, value=templabel)
window('%s.path' % embynode, value=windowpath)
window('%s.content' % embynode, value=path)
window('%s.type' % embynode, value=mediatype)
else:
embynode = "Emby.nodes.%s.%s" % (indexnumber, nodetype)
window('%s.title' % embynode, value=label)
window('%s.path' % embynode, value=windowpath)
window('%s.content' % embynode, value=path)
if mediatype == "photos":
# For photos, we do not create a node in videos but we do want the window props
# to be created.
# To do: add our photos nodes to kodi picture sources somehow
continue
if xbmcvfs.exists(nodeXML):
# Don't recreate xml if already exists
continue
# Create the root
if (nodetype == "nextepisodes" or mediatype == "homevideos" or
(self.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):
window = utils.window
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
window('%s.title' % embynode, value=label)
window('%s.path' % embynode, value=windowpath)
window('%s.content' % embynode, value=path)
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):
window = utils.window
self.logMsg("Clearing nodes properties.", 1)
embyprops = 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:
window('Emby.nodes.%s.%s' % (str(i), prop), clear=True)

File diff suppressed because it is too large Load diff

View file

@ -1,327 +1,319 @@
# -*- coding: utf-8 -*-
#################################################################################################
import json
import threading
import websocket
import xbmc
import xbmcgui
import clientinfo
import downloadutils
import librarysync
import playlist
import userclient
import utils
import logging
logging.basicConfig()
#################################################################################################
class WebSocket_Client(threading.Thread):
_shared_state = {}
client = None
stopWebsocket = False
def __init__(self):
self.__dict__ = self._shared_state
self.monitor = xbmc.Monitor()
self.doUtils = downloadutils.DownloadUtils()
self.clientInfo = clientinfo.ClientInfo()
self.addonName = self.clientInfo.getAddonName()
self.deviceId = self.clientInfo.getDeviceId()
self.librarySync = librarysync.LibrarySync()
threading.Thread.__init__(self)
def logMsg(self, msg, lvl=1):
self.className = self.__class__.__name__
utils.logMsg("%s %s" % (self.addonName, self.className), msg, lvl)
def sendProgressUpdate(self, data):
log = self.logMsg
log("sendProgressUpdate", 2)
try:
messageData = {
'MessageType': "ReportPlaybackProgress",
'Data': data
}
messageString = json.dumps(messageData)
self.client.send(messageString)
log("Message data: %s" % messageString, 2)
except Exception as e:
log("Exception: %s" % e, 1)
def on_message(self, ws, message):
log = self.logMsg
window = utils.window
lang = utils.language
result = json.loads(message)
messageType = result['MessageType']
data = result['Data']
if messageType not in ('SessionEnded'):
# Mute certain events
log("Message: %s" % message, 1)
if messageType == "Play":
# A remote control play command has been sent from the server.
itemIds = data['ItemIds']
command = data['PlayCommand']
pl = playlist.Playlist()
dialog = xbmcgui.Dialog()
if command == "PlayNow":
dialog.notification(
heading="Emby for Kodi",
message="%s %s" % (len(itemIds), lang(33004)),
icon="special://home/addons/plugin.video.emby/icon.png",
sound=False)
startat = data.get('StartPositionTicks', 0)
pl.playAll(itemIds, startat)
elif command == "PlayNext":
dialog.notification(
heading="Emby for Kodi",
message="%s %s" % (len(itemIds), lang(33005)),
icon="special://home/addons/plugin.video.emby/icon.png",
sound=False)
newplaylist = pl.modifyPlaylist(itemIds)
player = xbmc.Player()
if not player.isPlaying():
# Only start the playlist if nothing is playing
player.play(newplaylist)
elif messageType == "Playstate":
# A remote control update playstate command has been sent from the server.
command = data['Command']
player = xbmc.Player()
actions = {
'Stop': player.stop,
'Unpause': player.pause,
'Pause': player.pause,
'NextTrack': player.playnext,
'PreviousTrack': player.playprevious,
'Seek': player.seekTime
}
action = actions[command]
if command == "Seek":
seekto = data['SeekPositionTicks']
seektime = seekto / 10000000.0
action(seektime)
log("Seek to %s." % seektime, 1)
else:
action()
log("Command: %s completed." % command, 1)
window('emby_command', value="true")
elif messageType == "UserDataChanged":
# A user changed their personal rating for an item, or their playstate was updated
userdata_list = data['UserDataList']
self.librarySync.triage_items("userdata", userdata_list)
elif messageType == "LibraryChanged":
librarySync = self.librarySync
processlist = {
'added': data['ItemsAdded'],
'update': data['ItemsUpdated'],
'remove': data['ItemsRemoved']
}
for action in processlist:
librarySync.triage_items(action, processlist[action])
elif messageType == "GeneralCommand":
command = data['Name']
arguments = data['Arguments']
if command in ('Mute', 'Unmute', 'SetVolume',
'SetSubtitleStreamIndex', 'SetAudioStreamIndex'):
player = xbmc.Player()
# These commands need to be reported back
if command == "Mute":
xbmc.executebuiltin('Mute')
elif command == "Unmute":
xbmc.executebuiltin('Mute')
elif command == "SetVolume":
volume = arguments['Volume']
xbmc.executebuiltin('SetVolume(%s[,showvolumebar])' % volume)
elif command == "SetAudioStreamIndex":
index = int(arguments['Index'])
player.setAudioStream(index - 1)
elif command == "SetSubtitleStreamIndex":
embyindex = int(arguments['Index'])
currentFile = player.getPlayingFile()
mapping = window('emby_%s.indexMapping' % currentFile)
if mapping:
externalIndex = json.loads(mapping)
# If there's external subtitles added via playbackutils
for index in externalIndex:
if externalIndex[index] == embyindex:
player.setSubtitleStream(int(index))
break
else:
# User selected internal subtitles
external = len(externalIndex)
audioTracks = len(player.getAvailableAudioStreams())
player.setSubtitleStream(external + embyindex - audioTracks - 1)
else:
# Emby merges audio and subtitle index together
audioTracks = len(player.getAvailableAudioStreams())
player.setSubtitleStream(index - audioTracks - 1)
# Let service know
window('emby_command', value="true")
elif command == "DisplayMessage":
header = arguments['Header']
text = arguments['Text']
xbmcgui.Dialog().notification(
heading=header,
message=text,
icon="special://home/addons/plugin.video.emby/icon.png",
time=4000)
elif command == "SendString":
string = arguments['String']
text = {
'jsonrpc': "2.0",
'id': 0,
'method': "Input.SendText",
'params': {
'text': "%s" % string,
'done': False
}
}
result = xbmc.executeJSONRPC(json.dumps(text))
else:
builtin = {
'ToggleFullscreen': 'Action(FullScreen)',
'ToggleOsdMenu': 'Action(OSD)',
'ToggleContextMenu': 'Action(ContextMenu)',
'MoveUp': 'Action(Up)',
'MoveDown': 'Action(Down)',
'MoveLeft': 'Action(Left)',
'MoveRight': 'Action(Right)',
'Select': 'Action(Select)',
'Back': 'Action(back)',
'GoHome': 'ActivateWindow(Home)',
'PageUp': 'Action(PageUp)',
'NextLetter': 'Action(NextLetter)',
'GoToSearch': 'VideoLibrary.Search',
'GoToSettings': 'ActivateWindow(Settings)',
'PageDown': 'Action(PageDown)',
'PreviousLetter': 'Action(PrevLetter)',
'TakeScreenshot': 'TakeScreenshot',
'ToggleMute': 'Mute',
'VolumeUp': 'Action(VolumeUp)',
'VolumeDown': 'Action(VolumeDown)',
}
action = builtin.get(command)
if action:
xbmc.executebuiltin(action)
elif messageType == "ServerRestarting":
if utils.settings('supressRestartMsg') == "true":
xbmcgui.Dialog().notification(
heading="Emby for Kodi",
message=lang(33006),
icon="special://home/addons/plugin.video.emby/icon.png")
elif messageType == "UserConfigurationUpdated":
# Update user data set in userclient
userclient.UserClient().userSettings = data
self.librarySync.refresh_views = True
def on_close(self, ws):
self.logMsg("Closed.", 2)
def on_open(self, ws):
self.doUtils.postCapabilities(self.deviceId)
def on_error(self, ws, error):
if "10061" in str(error):
# Server is offline
pass
else:
self.logMsg("Error: %s" % error, 2)
def run(self):
log = self.logMsg
window = utils.window
monitor = self.monitor
loglevel = int(window('emby_logLevel'))
# websocket.enableTrace(True)
userId = window('emby_currUser')
server = window('emby_server%s' % userId)
token = window('emby_accessToken%s' % userId)
deviceId = self.deviceId
# Get the appropriate prefix for the websocket
if "https" in server:
server = server.replace('https', "wss")
else:
server = server.replace('http', "ws")
websocket_url = "%s?api_key=%s&deviceId=%s" % (server, token, deviceId)
log("websocket url: %s" % websocket_url, 1)
self.client = websocket.WebSocketApp(websocket_url,
on_message=self.on_message,
on_error=self.on_error,
on_close=self.on_close)
self.client.on_open = self.on_open
log("----===## Starting WebSocketClient ##===----", 0)
while not monitor.abortRequested():
self.client.run_forever(ping_interval=10)
if self.stopWebsocket:
break
if monitor.waitForAbort(5):
# Abort was requested, exit
break
log("##===---- WebSocketClient Stopped ----===##", 0)
def stopClient(self):
self.stopWebsocket = True
self.client.close()
# -*- coding: utf-8 -*-
#################################################################################################
import json
import threading
import websocket
import xbmc
import xbmcgui
import clientinfo
import downloadutils
import librarysync
import playlist
import userclient
import utils
import logging
logging.basicConfig()
#################################################################################################
class WebSocket_Client(threading.Thread):
_shared_state = {}
client = None
stopWebsocket = False
def __init__(self):
self.__dict__ = self._shared_state
self.monitor = xbmc.Monitor()
self.doUtils = downloadutils.DownloadUtils()
self.clientInfo = clientinfo.ClientInfo()
self.addonName = self.clientInfo.getAddonName()
self.deviceId = self.clientInfo.getDeviceId()
self.librarySync = librarysync.LibrarySync()
threading.Thread.__init__(self)
def logMsg(self, msg, lvl=1):
self.className = self.__class__.__name__
utils.logMsg("%s %s" % (self.addonName, self.className), msg, lvl)
def sendProgressUpdate(self, data):
self.logMsg("sendProgressUpdate", 2)
try:
messageData = {
'MessageType': "ReportPlaybackProgress",
'Data': data
}
messageString = json.dumps(messageData)
self.client.send(messageString)
self.logMsg("Message data: %s" % messageString, 2)
except Exception as e:
self.logMsg("Exception: %s" % e, 1)
def on_message(self, ws, message):
window = utils.window
lang = utils.language
result = json.loads(message)
messageType = result['MessageType']
data = result['Data']
if messageType not in ('SessionEnded'):
# Mute certain events
self.logMsg("Message: %s" % message, 1)
if messageType == "Play":
# A remote control play command has been sent from the server.
itemIds = data['ItemIds']
command = data['PlayCommand']
pl = playlist.Playlist()
dialog = xbmcgui.Dialog()
if command == "PlayNow":
dialog.notification(
heading="Emby for Kodi",
message="%s %s" % (len(itemIds), lang(33004)),
icon="special://home/addons/plugin.video.emby/icon.png",
sound=False)
startat = data.get('StartPositionTicks', 0)
pl.playAll(itemIds, startat)
elif command == "PlayNext":
dialog.notification(
heading="Emby for Kodi",
message="%s %s" % (len(itemIds), lang(33005)),
icon="special://home/addons/plugin.video.emby/icon.png",
sound=False)
newplaylist = pl.modifyPlaylist(itemIds)
player = xbmc.Player()
if not player.isPlaying():
# Only start the playlist if nothing is playing
player.play(newplaylist)
elif messageType == "Playstate":
# A remote control update playstate command has been sent from the server.
command = data['Command']
player = xbmc.Player()
actions = {
'Stop': player.stop,
'Unpause': player.pause,
'Pause': player.pause,
'NextTrack': player.playnext,
'PreviousTrack': player.playprevious,
'Seek': player.seekTime
}
action = actions[command]
if command == "Seek":
seekto = data['SeekPositionTicks']
seektime = seekto / 10000000.0
action(seektime)
self.logMsg("Seek to %s." % seektime, 1)
else:
action()
self.logMsg("Command: %s completed." % command, 1)
window('emby_command', value="true")
elif messageType == "UserDataChanged":
# A user changed their personal rating for an item, or their playstate was updated
userdata_list = data['UserDataList']
self.librarySync.triage_items("userdata", userdata_list)
elif messageType == "LibraryChanged":
librarySync = self.librarySync
processlist = {
'added': data['ItemsAdded'],
'update': data['ItemsUpdated'],
'remove': data['ItemsRemoved']
}
for action in processlist:
librarySync.triage_items(action, processlist[action])
elif messageType == "GeneralCommand":
command = data['Name']
arguments = data['Arguments']
if command in ('Mute', 'Unmute', 'SetVolume',
'SetSubtitleStreamIndex', 'SetAudioStreamIndex'):
player = xbmc.Player()
# These commands need to be reported back
if command == "Mute":
xbmc.executebuiltin('Mute')
elif command == "Unmute":
xbmc.executebuiltin('Mute')
elif command == "SetVolume":
volume = arguments['Volume']
xbmc.executebuiltin('SetVolume(%s[,showvolumebar])' % volume)
elif command == "SetAudioStreamIndex":
index = int(arguments['Index'])
player.setAudioStream(index - 1)
elif command == "SetSubtitleStreamIndex":
embyindex = int(arguments['Index'])
currentFile = player.getPlayingFile()
mapping = window('emby_%s.indexMapping' % currentFile)
if mapping:
externalIndex = json.loads(mapping)
# If there's external subtitles added via playbackutils
for index in externalIndex:
if externalIndex[index] == embyindex:
player.setSubtitleStream(int(index))
break
else:
# User selected internal subtitles
external = len(externalIndex)
audioTracks = len(player.getAvailableAudioStreams())
player.setSubtitleStream(external + embyindex - audioTracks - 1)
else:
# Emby merges audio and subtitle index together
audioTracks = len(player.getAvailableAudioStreams())
player.setSubtitleStream(index - audioTracks - 1)
# Let service know
window('emby_command', value="true")
elif command == "DisplayMessage":
header = arguments['Header']
text = arguments['Text']
xbmcgui.Dialog().notification(
heading=header,
message=text,
icon="special://home/addons/plugin.video.emby/icon.png",
time=4000)
elif command == "SendString":
string = arguments['String']
text = {
'jsonrpc': "2.0",
'id': 0,
'method': "Input.SendText",
'params': {
'text': "%s" % string,
'done': False
}
}
result = xbmc.executeJSONRPC(json.dumps(text))
else:
builtin = {
'ToggleFullscreen': 'Action(FullScreen)',
'ToggleOsdMenu': 'Action(OSD)',
'ToggleContextMenu': 'Action(ContextMenu)',
'MoveUp': 'Action(Up)',
'MoveDown': 'Action(Down)',
'MoveLeft': 'Action(Left)',
'MoveRight': 'Action(Right)',
'Select': 'Action(Select)',
'Back': 'Action(back)',
'GoHome': 'ActivateWindow(Home)',
'PageUp': 'Action(PageUp)',
'NextLetter': 'Action(NextLetter)',
'GoToSearch': 'VideoLibrary.Search',
'GoToSettings': 'ActivateWindow(Settings)',
'PageDown': 'Action(PageDown)',
'PreviousLetter': 'Action(PrevLetter)',
'TakeScreenshot': 'TakeScreenshot',
'ToggleMute': 'Mute',
'VolumeUp': 'Action(VolumeUp)',
'VolumeDown': 'Action(VolumeDown)',
}
action = builtin.get(command)
if action:
xbmc.executebuiltin(action)
elif messageType == "ServerRestarting":
if utils.settings('supressRestartMsg') == "true":
xbmcgui.Dialog().notification(
heading="Emby for Kodi",
message=lang(33006),
icon="special://home/addons/plugin.video.emby/icon.png")
elif messageType == "UserConfigurationUpdated":
# Update user data set in userclient
userclient.UserClient().userSettings = data
self.librarySync.refresh_views = True
def on_close(self, ws):
self.logMsg("Closed.", 2)
def on_open(self, ws):
self.doUtils.postCapabilities(self.deviceId)
def on_error(self, ws, error):
if "10061" in str(error):
# Server is offline
pass
else:
self.logMsg("Error: %s" % error, 2)
def run(self):
window = utils.window
loglevel = int(window('emby_logLevel'))
# websocket.enableTrace(True)
userId = window('emby_currUser')
server = window('emby_server%s' % userId)
token = window('emby_accessToken%s' % userId)
# Get the appropriate prefix for the websocket
if "https" in server:
server = server.replace('https', "wss")
else:
server = server.replace('http', "ws")
websocket_url = "%s?api_key=%s&deviceId=%s" % (server, token, self.deviceId)
self.logMsg("websocket url: %s" % websocket_url, 1)
self.client = websocket.WebSocketApp(websocket_url,
on_message=self.on_message,
on_error=self.on_error,
on_close=self.on_close)
self.client.on_open = self.on_open
self.logMsg("----===## Starting WebSocketClient ##===----", 0)
while not self.monitor.abortRequested():
self.client.run_forever(ping_interval=10)
if self.stopWebsocket:
break
if self.monitor.waitForAbort(5):
# Abort was requested, exit
break
self.logMsg("##===---- WebSocketClient Stopped ----===##", 0)
def stopClient(self):
self.stopWebsocket = True
self.client.close()
self.logMsg("Stopping thread.", 1)