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

View file

@ -323,7 +323,7 @@
<string id="33020">Gathering tv shows from:</string> <string id="33020">Gathering tv shows from:</string>
<string id="33021">Gathering:</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="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="33024">Cancelling the database syncing process. The current Kodi version is unsupported.</string>
<string id="33025">completed in:</string> <string id="33025">completed in:</string>
<string id="33026">Comparing movies from:</string> <string id="33026">Comparing movies from:</string>

View file

@ -37,7 +37,7 @@ class API():
try: try:
userdata = self.item['UserData'] userdata = self.item['UserData']
except KeyError: # No userdata found. except KeyError: # No userdata found.
pass pass
@ -57,7 +57,7 @@ class API():
lastPlayedDate = userdata.get('LastPlayedDate') lastPlayedDate = userdata.get('LastPlayedDate')
if lastPlayedDate: if lastPlayedDate:
lastPlayedDate = lastPlayedDate.split('.')[0].replace('T', " ") lastPlayedDate = lastPlayedDate.split('.')[0].replace('T', " ")
if userdata['Played']: if userdata['Played']:
# Playcount is tied to the watch status # Playcount is tied to the watch status
played = True played = True
@ -91,10 +91,10 @@ class API():
try: try:
people = self.item['People'] people = self.item['People']
except KeyError: except KeyError:
pass pass
else: else:
for person in people: for person in people:
@ -116,17 +116,16 @@ class API():
} }
def getMediaStreams(self): def getMediaStreams(self):
item = self.item
videotracks = [] videotracks = []
audiotracks = [] audiotracks = []
subtitlelanguages = [] subtitlelanguages = []
try: try:
media_streams = item['MediaSources'][0]['MediaStreams'] media_streams = self.item['MediaSources'][0]['MediaStreams']
except KeyError: except KeyError:
if not item.get("MediaStreams"): return None if not self.item.get("MediaStreams"): return None
media_streams = item['MediaStreams'] media_streams = self.item['MediaStreams']
for media_stream in media_streams: for media_stream in media_streams:
# Sort through Video, Audio, Subtitle # Sort through Video, Audio, Subtitle
@ -141,12 +140,12 @@ class API():
'codec': codec, 'codec': codec,
'height': media_stream.get('Height'), 'height': media_stream.get('Height'),
'width': media_stream.get('Width'), 'width': media_stream.get('Width'),
'video3DFormat': item.get('Video3DFormat'), 'video3DFormat': self.item.get('Video3DFormat'),
'aspect': 1.85 'aspect': 1.85
} }
try: try:
container = item['MediaSources'][0]['Container'].lower() container = self.item['MediaSources'][0]['Container'].lower()
except: except:
container = "" container = ""
@ -161,16 +160,16 @@ class API():
track['codec'] = "avc1" track['codec'] = "avc1"
# Aspect ratio # Aspect ratio
if item.get('AspectRatio'): if self.item.get('AspectRatio'):
# Metadata AR # Metadata AR
aspect = item['AspectRatio'] aspect = self.item['AspectRatio']
else: # File AR else: # File AR
aspect = media_stream.get('AspectRatio', "0") aspect = media_stream.get('AspectRatio', "0")
try: try:
aspectwidth, aspectheight = aspect.split(':') aspectwidth, aspectheight = aspect.split(':')
track['aspect'] = round(float(aspectwidth) / float(aspectheight), 6) track['aspect'] = round(float(aspectwidth) / float(aspectheight), 6)
except (ValueError, ZeroDivisionError): except (ValueError, ZeroDivisionError):
width = track.get('width') width = track.get('width')
height = track.get('height') height = track.get('height')
@ -179,16 +178,16 @@ class API():
track['aspect'] = round(float(width / height), 6) track['aspect'] = round(float(width / height), 6)
else: else:
track['aspect'] = 1.85 track['aspect'] = 1.85
if item.get("RunTimeTicks"): if self.item.get("RunTimeTicks"):
track['duration'] = item.get("RunTimeTicks") / 10000000.0 track['duration'] = self.item.get("RunTimeTicks") / 10000000.0
videotracks.append(track) videotracks.append(track)
elif stream_type == "Audio": elif stream_type == "Audio":
# Codec, Channels, language # Codec, Channels, language
track = { track = {
'codec': codec, 'codec': codec,
'channels': media_stream.get('Channels'), 'channels': media_stream.get('Channels'),
'language': media_stream.get('Language') 'language': media_stream.get('Language')
@ -205,18 +204,17 @@ class API():
return { return {
'video': videotracks, 'video': videotracks,
'audio': audiotracks, 'audio': audiotracks,
'subtitle': subtitlelanguages 'subtitle': subtitlelanguages
} }
def getRuntime(self): def getRuntime(self):
item = self.item
try: try:
runtime = item['RunTimeTicks'] / 10000000.0 runtime = self.item['RunTimeTicks'] / 10000000.0
except KeyError: except KeyError:
runtime = item.get('CumulativeRunTimeTicks', 0) / 10000000.0 runtime = self.item.get('CumulativeRunTimeTicks', 0) / 10000000.0
return runtime return runtime
@ -234,20 +232,19 @@ class API():
def getStudios(self): def getStudios(self):
# Process Studios # Process Studios
item = self.item
studios = [] studios = []
try: try:
studio = item['SeriesStudio'] studio = self.item['SeriesStudio']
studios.append(self.verifyStudio(studio)) studios.append(self.verifyStudio(studio))
except KeyError: except KeyError:
studioList = item['Studios'] studioList = self.item['Studios']
for studio in studioList: for studio in studioList:
name = studio['Name'] name = studio['Name']
studios.append(self.verifyStudio(name)) studios.append(self.verifyStudio(name))
return studios return studios
def verifyStudio(self, studioName): def verifyStudio(self, studioName):
@ -265,12 +262,11 @@ class API():
def getChecksum(self): def getChecksum(self):
# Use the etags checksum and userdata # Use the etags checksum and userdata
item = self.item userdata = self.item['UserData']
userdata = item['UserData']
checksum = "%s%s%s%s%s%s%s" % ( checksum = "%s%s%s%s%s%s%s" % (
item['Etag'], self.item['Etag'],
userdata['Played'], userdata['Played'],
userdata['IsFavorite'], userdata['IsFavorite'],
userdata.get('Likes',''), userdata.get('Likes',''),
@ -282,9 +278,8 @@ class API():
return checksum return checksum
def getGenres(self): def getGenres(self):
item = self.item
all_genres = "" all_genres = ""
genres = item.get('Genres', item.get('SeriesGenres')) genres = self.item.get('Genres', self.item.get('SeriesGenres'))
if genres: if genres:
all_genres = " / ".join(genres) all_genres = " / ".join(genres)
@ -344,7 +339,7 @@ class API():
def getMpaa(self): def getMpaa(self):
# Convert more complex cases # Convert more complex cases
mpaa = self.item.get('OfficialRating', "") mpaa = self.item.get('OfficialRating', "")
if mpaa in ("NR", "UR"): if mpaa in ("NR", "UR"):
# Kodi seems to not like NR, but will accept Not Rated # Kodi seems to not like NR, but will accept Not Rated
mpaa = "Not Rated" mpaa = "Not Rated"
@ -362,9 +357,8 @@ class API():
def getFilePath(self): def getFilePath(self):
item = self.item
try: try:
filepath = item['Path'] filepath = self.item['Path']
except KeyError: except KeyError:
filepath = "" filepath = ""
@ -375,17 +369,16 @@ class API():
filepath = filepath.replace("\\\\", "smb://") filepath = filepath.replace("\\\\", "smb://")
filepath = filepath.replace("\\", "/") filepath = filepath.replace("\\", "/")
if item.get('VideoType'): if self.item.get('VideoType'):
videotype = item['VideoType'] videotype = self.item['VideoType']
# Specific format modification # Specific format modification
if 'Dvd'in videotype: if 'Dvd'in videotype:
filepath = "%s/VIDEO_TS/VIDEO_TS.IFO" % filepath filepath = "%s/VIDEO_TS/VIDEO_TS.IFO" % filepath
elif 'BluRay' in videotype: elif 'BluRay' in videotype:
filepath = "%s/BDMV/index.bdmv" % filepath filepath = "%s/BDMV/index.bdmv" % filepath
if "\\" in filepath: if "\\" in filepath:
# Local path scenario, with special videotype # Local path scenario, with special videotype
filepath = filepath.replace("/", "\\") filepath = filepath.replace("/", "\\")
return filepath 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(): class ConnectUtils():
# Borg - multiple instances, shared state # Borg - multiple instances, shared state
_shared_state = {} _shared_state = {}
clientInfo = clientinfo.ClientInfo() clientInfo = clientinfo.ClientInfo()
@ -60,8 +60,6 @@ class ConnectUtils():
def startSession(self): def startSession(self):
log = self.logMsg
self.deviceId = self.clientInfo.getDeviceId() self.deviceId = self.clientInfo.getDeviceId()
# User is identified from this point # User is identified from this point
@ -75,8 +73,8 @@ class ConnectUtils():
if self.sslclient is not None: if self.sslclient is not None:
verify = self.sslclient verify = self.sslclient
except: except:
log("Could not load SSL settings.", 1) self.logMsg("Could not load SSL settings.", 1)
# Start session # Start session
self.c = requests.Session() self.c = requests.Session()
self.c.headers = header self.c.headers = header
@ -85,7 +83,7 @@ class ConnectUtils():
self.c.mount("http://", requests.adapters.HTTPAdapter(max_retries=1)) self.c.mount("http://", requests.adapters.HTTPAdapter(max_retries=1))
self.c.mount("https://", 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): def stopSession(self):
try: try:
@ -95,8 +93,7 @@ class ConnectUtils():
def getHeader(self, authenticate=True): def getHeader(self, authenticate=True):
clientInfo = self.clientInfo version = self.clientInfo.getVersion()
version = clientInfo.getVersion()
if not authenticate: if not authenticate:
# If user is not authenticated # If user is not authenticated
@ -105,9 +102,9 @@ class ConnectUtils():
'X-Application': "Kodi/%s" % version, 'X-Application': "Kodi/%s" % version,
'Content-type': 'application/x-www-form-urlencoded; charset=UTF-8', 'Content-type': 'application/x-www-form-urlencoded; charset=UTF-8',
'Accept': "application/json" 'Accept': "application/json"
} }
self.logMsg("Header: %s" % header, 1) self.logMsg("Header: %s" % header, 1)
else: else:
token = self.token token = self.token
# Attached to the requests session # Attached to the requests session
@ -117,18 +114,17 @@ class ConnectUtils():
'Accept': "application/json", 'Accept': "application/json",
'X-Application': "Kodi/%s" % version, 'X-Application': "Kodi/%s" % version,
'X-Connect-UserToken': token 'X-Connect-UserToken': token
} }
self.logMsg("Header: %s" % header, 1) self.logMsg("Header: %s" % header, 1)
return header return header
def doUrl(self, url, data=None, postBody=None, rtype="GET", def doUrl(self, url, data=None, postBody=None, rtype="GET",
parameters=None, authenticate=True, timeout=None): parameters=None, authenticate=True, timeout=None):
log = self.logMsg
window = utils.window window = utils.window
log("=== ENTER connectUrl ===", 2) self.logMsg("=== ENTER connectUrl ===", 2)
default_link = "" default_link = ""
if timeout is None: if timeout is None:
timeout = self.timeout timeout = self.timeout
@ -137,7 +133,7 @@ class ConnectUtils():
try: try:
# If connect user is authenticated # If connect user is authenticated
if authenticate: if authenticate:
try: try:
c = self.c c = self.c
# Replace for the real values # Replace for the real values
url = url.replace("{server}", self.server) url = url.replace("{server}", self.server)
@ -167,7 +163,7 @@ class ConnectUtils():
verifyssl = self.sslclient verifyssl = self.sslclient
except AttributeError: except AttributeError:
pass pass
# Prepare request # Prepare request
if rtype == "GET": if rtype == "GET":
r = requests.get(url, r = requests.get(url,
@ -195,7 +191,7 @@ class ConnectUtils():
verifyssl = self.sslclient verifyssl = self.sslclient
except AttributeError: except AttributeError:
pass pass
# Prepare request # Prepare request
if rtype == "GET": if rtype == "GET":
r = requests.get(url, r = requests.get(url,
@ -213,28 +209,28 @@ class ConnectUtils():
verify=verifyssl) verify=verifyssl)
##### THE RESPONSE ##### ##### THE RESPONSE #####
log(r.url, 1) self.logMsg(r.url, 1)
log(r, 1) self.logMsg(r, 1)
if r.status_code == 204: if r.status_code == 204:
# No body in the response # No body in the response
log("====== 204 Success ======", 1) self.logMsg("====== 204 Success ======", 1)
elif r.status_code == requests.codes.ok: elif r.status_code == requests.codes.ok:
try: try:
# UNICODE - JSON object # UNICODE - JSON object
r = r.json() r = r.json()
log("====== 200 Success ======", 1) self.logMsg("====== 200 Success ======", 1)
log("Response: %s" % r, 1) self.logMsg("Response: %s" % r, 1)
return r return r
except: except:
if r.headers.get('content-type') != "text/html": 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: else:
r.raise_for_status() r.raise_for_status()
##### EXCEPTIONS ##### ##### EXCEPTIONS #####
except requests.exceptions.ConnectionError as e: except requests.exceptions.ConnectionError as e:
@ -242,8 +238,8 @@ class ConnectUtils():
pass pass
except requests.exceptions.ConnectTimeout as e: except requests.exceptions.ConnectTimeout as e:
log("Server timeout at: %s" % url, 0) self.logMsg("Server timeout at: %s" % url, 0)
log(e, 1) self.logMsg(e, 1)
except requests.exceptions.HTTPError as e: except requests.exceptions.HTTPError as e:
@ -259,11 +255,11 @@ class ConnectUtils():
pass pass
except requests.exceptions.SSLError as e: except requests.exceptions.SSLError as e:
log("Invalid SSL certificate for: %s" % url, 0) self.logMsg("Invalid SSL certificate for: %s" % url, 0)
log(e, 1) self.logMsg(e, 1)
except requests.exceptions.RequestException as e: except requests.exceptions.RequestException as e:
log("Unknown error connecting to: %s" % url, 0) self.logMsg("Unknown error connecting to: %s" % url, 0)
log(e, 1) self.logMsg(e, 1)
return default_link return default_link

View file

@ -23,7 +23,7 @@ requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
class DownloadUtils(): class DownloadUtils():
# Borg - multiple instances, shared state # Borg - multiple instances, shared state
_shared_state = {} _shared_state = {}
clientInfo = clientinfo.ClientInfo() clientInfo = clientinfo.ClientInfo()
@ -77,11 +77,11 @@ class DownloadUtils():
# Post settings to session # Post settings to session
url = "{server}/emby/Sessions/Capabilities/Full?format=json" url = "{server}/emby/Sessions/Capabilities/Full?format=json"
data = { data = {
'PlayableMediaTypes': "Audio,Video", 'PlayableMediaTypes': "Audio,Video",
'SupportsMediaControl': True, 'SupportsMediaControl': True,
'SupportedCommands': ( 'SupportedCommands': (
"MoveUp,MoveDown,MoveLeft,MoveRight,Select," "MoveUp,MoveDown,MoveLeft,MoveRight,Select,"
"Back,ToggleContextMenu,ToggleFullscreen,ToggleOsdMenu," "Back,ToggleContextMenu,ToggleFullscreen,ToggleOsdMenu,"
"GoHome,PageUp,NextLetter,GoToSearch," "GoHome,PageUp,NextLetter,GoToSearch,"
@ -97,7 +97,7 @@ class DownloadUtils():
self.logMsg("Capabilities URL: %s" % url, 2) self.logMsg("Capabilities URL: %s" % url, 2)
self.logMsg("Postdata: %s" % data, 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) self.logMsg("Posted capabilities to %s" % self.server, 2)
# Attempt at getting sessionId # Attempt at getting sessionId
@ -105,19 +105,19 @@ class DownloadUtils():
result = self.downloadUrl(url) result = self.downloadUrl(url)
try: try:
sessionId = result[0]['Id'] sessionId = result[0]['Id']
except (KeyError, TypeError): except (KeyError, TypeError):
self.logMsg("Failed to retrieve sessionId.", 1) self.logMsg("Failed to retrieve sessionId.", 1)
else: else:
self.logMsg("Session: %s" % result, 2) self.logMsg("Session: %s" % result, 2)
self.logMsg("SessionId: %s" % sessionId, 1) self.logMsg("SessionId: %s" % sessionId, 1)
utils.window('emby_sessionId', value=sessionId) utils.window('emby_sessionId', value=sessionId)
# Post any permanent additional users # Post any permanent additional users
additionalUsers = utils.settings('additionalUsers') additionalUsers = utils.settings('additionalUsers')
if additionalUsers: if additionalUsers:
additionalUsers = additionalUsers.split(',') additionalUsers = additionalUsers.split(',')
self.logMsg( self.logMsg(
"List of permanent users added to the session: %s" "List of permanent users added to the session: %s"
@ -140,13 +140,11 @@ class DownloadUtils():
"{server}/emby/Sessions/%s/Users/%s?format=json" "{server}/emby/Sessions/%s/Users/%s?format=json"
% (sessionId, userId) % (sessionId, userId)
) )
self.downloadUrl(url, postBody={}, type="POST") self.downloadUrl(url, postBody={}, action_type="POST")
def startSession(self): def startSession(self):
log = self.logMsg
self.deviceId = self.clientInfo.getDeviceId() self.deviceId = self.clientInfo.getDeviceId()
# User is identified from this point # User is identified from this point
@ -160,8 +158,8 @@ class DownloadUtils():
if self.sslclient is not None: if self.sslclient is not None:
verify = self.sslclient verify = self.sslclient
except: except:
log("Could not load SSL settings.", 1) self.logMsg("Could not load SSL settings.", 1)
# Start session # Start session
self.s = requests.Session() self.s = requests.Session()
self.s.headers = header self.s.headers = header
@ -170,7 +168,7 @@ class DownloadUtils():
self.s.mount("http://", requests.adapters.HTTPAdapter(max_retries=1)) self.s.mount("http://", requests.adapters.HTTPAdapter(max_retries=1))
self.s.mount("https://", 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): def stopSession(self):
try: try:
@ -180,12 +178,10 @@ class DownloadUtils():
def getHeader(self, authenticate=True): def getHeader(self, authenticate=True):
clientInfo = self.clientInfo deviceName = self.clientInfo.getDeviceName()
deviceName = clientInfo.getDeviceName()
deviceName = utils.normalize_string(deviceName.encode('utf-8')) deviceName = utils.normalize_string(deviceName.encode('utf-8'))
deviceId = clientInfo.getDeviceId() deviceId = self.clientInfo.getDeviceId()
version = clientInfo.getVersion() version = self.clientInfo.getVersion()
if not authenticate: if not authenticate:
# If user is not authenticated # If user is not authenticated
@ -198,9 +194,9 @@ class DownloadUtils():
'Accept-encoding': 'gzip', 'Accept-encoding': 'gzip',
'Accept-Charset': 'UTF-8,*', 'Accept-Charset': 'UTF-8,*',
'Authorization': auth 'Authorization': auth
} }
self.logMsg("Header: %s" % header, 2) self.logMsg("Header: %s" % header, 2)
else: else:
userId = self.userId userId = self.userId
token = self.token token = self.token
@ -215,36 +211,35 @@ class DownloadUtils():
'Accept-Charset': 'UTF-8,*', 'Accept-Charset': 'UTF-8,*',
'Authorization': auth, 'Authorization': auth,
'X-MediaBrowser-Token': token 'X-MediaBrowser-Token': token
} }
self.logMsg("Header: %s" % header, 2) self.logMsg("Header: %s" % header, 2)
return header 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) self.logMsg("=== ENTER downloadUrl ===", 2)
timeout = self.timeout
default_link = "" default_link = ""
try: try:
# If user is authenticated # If user is authenticated
if (authenticate): if (authenticate):
# Get requests session # Get requests session
try: try:
s = self.s s = self.s
# Replace for the real values # Replace for the real values
url = url.replace("{server}", self.server) url = url.replace("{server}", self.server)
url = url.replace("{UserId}", self.userId) url = url.replace("{UserId}", self.userId)
# Prepare request # Prepare request
if type == "GET": if action_type == "GET":
r = s.get(url, json=postBody, params=parameters, timeout=timeout) r = s.get(url, json=postBody, params=parameters, timeout=self.timeout)
elif type == "POST": elif action_type == "POST":
r = s.post(url, json=postBody, timeout=timeout) r = s.post(url, json=postBody, timeout=self.timeout)
elif type == "DELETE": elif action_type == "DELETE":
r = s.delete(url, json=postBody, timeout=timeout) r = s.delete(url, json=postBody, timeout=self.timeout)
except AttributeError: except AttributeError:
# request session does not exists # request session does not exists
# Get user information # Get user information
@ -266,26 +261,26 @@ class DownloadUtils():
url = url.replace("{UserId}", self.userId) url = url.replace("{UserId}", self.userId)
# Prepare request # Prepare request
if type == "GET": if action_type == "GET":
r = requests.get(url, r = requests.get(url,
json=postBody, json=postBody,
params=parameters, params=parameters,
headers=header, headers=header,
timeout=timeout, timeout=self.timeout,
verify=verifyssl) verify=verifyssl)
elif type == "POST": elif action_type == "POST":
r = requests.post(url, r = requests.post(url,
json=postBody, json=postBody,
headers=header, headers=header,
timeout=timeout, timeout=self.timeout,
verify=verifyssl) verify=verifyssl)
elif type == "DELETE": elif action_type == "DELETE":
r = requests.delete(url, r = requests.delete(url,
json=postBody, json=postBody,
headers=header, headers=header,
timeout=timeout, timeout=self.timeout,
verify=verifyssl) verify=verifyssl)
# If user is not authenticated # If user is not authenticated
@ -301,23 +296,23 @@ class DownloadUtils():
verifyssl = self.sslclient verifyssl = self.sslclient
except AttributeError: except AttributeError:
pass pass
# Prepare request # Prepare request
if type == "GET": if action_type == "GET":
r = requests.get(url, r = requests.get(url,
json=postBody, json=postBody,
params=parameters, params=parameters,
headers=header, headers=header,
timeout=timeout, timeout=self.timeout,
verify=verifyssl) verify=verifyssl)
elif type == "POST": elif action_type == "POST":
r = requests.post(url, r = requests.post(url,
json=postBody, json=postBody,
headers=header, headers=header,
timeout=timeout, timeout=self.timeout,
verify=verifyssl) verify=verifyssl)
##### THE RESPONSE ##### ##### THE RESPONSE #####
self.logMsg(r.url, 2) self.logMsg(r.url, 2)
if r.status_code == 204: if r.status_code == 204:
@ -325,8 +320,8 @@ class DownloadUtils():
self.logMsg("====== 204 Success ======", 2) self.logMsg("====== 204 Success ======", 2)
elif r.status_code == requests.codes.ok: elif r.status_code == requests.codes.ok:
try: try:
# UNICODE - JSON object # UNICODE - JSON object
r = r.json() r = r.json()
self.logMsg("====== 200 Success ======", 2) self.logMsg("====== 200 Success ======", 2)
@ -338,7 +333,7 @@ class DownloadUtils():
self.logMsg("Unable to convert the response for: %s" % url, 1) self.logMsg("Unable to convert the response for: %s" % url, 1)
else: else:
r.raise_for_status() r.raise_for_status()
##### EXCEPTIONS ##### ##### EXCEPTIONS #####
except requests.exceptions.ConnectionError as e: except requests.exceptions.ConnectionError as e:
@ -369,7 +364,7 @@ class DownloadUtils():
icon=xbmcgui.NOTIFICATION_ERROR, icon=xbmcgui.NOTIFICATION_ERROR,
time=5000) time=5000)
return False return False
elif r.headers['X-Application-Error-Code'] == "UnauthorizedAccessException": elif r.headers['X-Application-Error-Code'] == "UnauthorizedAccessException":
# User tried to do something his emby account doesn't allow # User tried to do something his emby account doesn't allow
pass pass

View file

@ -1,325 +1,292 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
################################################################################################# #################################################################################################
import utils import utils
import clientinfo import clientinfo
################################################################################################# #################################################################################################
class Embydb_Functions(): class Embydb_Functions():
def __init__(self, embycursor): def __init__(self, embycursor):
self.embycursor = embycursor self.embycursor = embycursor
self.clientInfo = clientinfo.ClientInfo() self.clientInfo = clientinfo.ClientInfo()
self.addonName = self.clientInfo.getAddonName() self.addonName = self.clientInfo.getAddonName()
def logMsg(self, msg, lvl=1): def logMsg(self, msg, lvl=1):
className = self.__class__.__name__ className = self.__class__.__name__
utils.logMsg("%s %s" % (self.addonName, className), msg, lvl) utils.logMsg("%s %s" % (self.addonName, className), msg, lvl)
def getViews(self): def getViews(self):
embycursor = self.embycursor views = []
views = []
query = ' '.join((
query = ' '.join((
"SELECT view_id",
"SELECT view_id", "FROM view"
"FROM view" ))
)) self.embycursor.execute(query)
embycursor.execute(query) rows = self.embycursor.fetchall()
rows = embycursor.fetchall() for row in rows:
for row in rows: views.append(row[0])
views.append(row[0])
return views
return views
def getView_byId(self, viewid):
def getView_byId(self, viewid):
embycursor = self.embycursor query = ' '.join((
query = ' '.join(( "SELECT view_name, media_type, kodi_tagid",
"FROM view",
"SELECT view_name, media_type, kodi_tagid", "WHERE view_id = ?"
"FROM view", ))
"WHERE view_id = ?" self.embycursor.execute(query, (viewid,))
)) view = self.embycursor.fetchone()
embycursor.execute(query, (viewid,))
view = embycursor.fetchone() return view
return view def getView_byType(self, mediatype):
def getView_byType(self, mediatype): views = []
embycursor = self.embycursor query = ' '.join((
views = []
"SELECT view_id, view_name",
query = ' '.join(( "FROM view",
"WHERE media_type = ?"
"SELECT view_id, view_name", ))
"FROM view", self.embycursor.execute(query, (mediatype,))
"WHERE media_type = ?" rows = self.embycursor.fetchall()
)) for row in rows:
embycursor.execute(query, (mediatype,)) views.append({
rows = embycursor.fetchall()
for row in rows: 'id': row[0],
views.append({ 'name': row[1]
})
'id': row[0],
'name': row[1] return views
})
def getView_byName(self, tagname):
return views
query = ' '.join((
def getView_byName(self, tagname):
"SELECT view_id",
embycursor = self.embycursor "FROM view",
"WHERE view_name = ?"
query = ' '.join(( ))
self.embycursor.execute(query, (tagname,))
"SELECT view_id", try:
"FROM view", view = self.embycursor.fetchone()[0]
"WHERE view_name = ?"
)) except TypeError:
embycursor.execute(query, (tagname,)) view = None
try:
view = embycursor.fetchone()[0] return view
except TypeError: def addView(self, embyid, name, mediatype, tagid):
view = None
query = (
return view '''
INSERT INTO view(
def addView(self, embyid, name, mediatype, tagid): view_id, view_name, media_type, kodi_tagid)
query = ( VALUES (?, ?, ?, ?)
''' '''
INSERT INTO view( )
view_id, view_name, media_type, kodi_tagid) self.embycursor.execute(query, (embyid, name, mediatype, tagid))
VALUES (?, ?, ?, ?) def updateView(self, name, tagid, mediafolderid):
'''
) query = ' '.join((
self.embycursor.execute(query, (embyid, name, mediatype, tagid))
"UPDATE view",
def updateView(self, name, tagid, mediafolderid): "SET view_name = ?, kodi_tagid = ?",
"WHERE view_id = ?"
query = ' '.join(( ))
self.embycursor.execute(query, (name, tagid, mediafolderid))
"UPDATE view",
"SET view_name = ?, kodi_tagid = ?", def removeView(self, viewid):
"WHERE view_id = ?"
)) query = ' '.join((
self.embycursor.execute(query, (name, tagid, mediafolderid))
"DELETE FROM view",
def removeView(self, viewid): "WHERE view_id = ?"
))
query = ' '.join(( self.embycursor.execute(query, (viewid,))
"DELETE FROM view", def getItem_byId(self, embyid):
"WHERE view_id = ?"
)) query = ' '.join((
self.embycursor.execute(query, (viewid,))
"SELECT kodi_id, kodi_fileid, kodi_pathid, parent_id, media_type, emby_type",
def getItem_byId(self, embyid): "FROM emby",
"WHERE emby_id = ?"
embycursor = self.embycursor ))
try:
query = ' '.join(( self.embycursor.execute(query, (embyid,))
item = self.embycursor.fetchone()
"SELECT kodi_id, kodi_fileid, kodi_pathid, parent_id, media_type, emby_type", return item
"FROM emby", except: return None
"WHERE emby_id = ?"
)) def getItem_byWildId(self, embyid):
try:
embycursor.execute(query, (embyid,)) query = ' '.join((
item = embycursor.fetchone()
return item "SELECT kodi_id, media_type",
except: return None "FROM emby",
"WHERE emby_id LIKE ?"
def getItem_byWildId(self, embyid): ))
self.embycursor.execute(query, (embyid+"%",))
embycursor = self.embycursor return self.embycursor.fetchall()
query = ' '.join(( def getItem_byView(self, mediafolderid):
"SELECT kodi_id, media_type", query = ' '.join((
"FROM emby",
"WHERE emby_id LIKE ?" "SELECT kodi_id",
)) "FROM emby",
embycursor.execute(query, (embyid+"%",)) "WHERE media_folder = ?"
items = embycursor.fetchall() ))
self.embycursor.execute(query, (mediafolderid,))
return items return self.embycursor.fetchall()
def getItem_byView(self, mediafolderid): def getItem_byKodiId(self, kodiid, mediatype):
embycursor = self.embycursor query = ' '.join((
query = ' '.join(( "SELECT emby_id, parent_id",
"FROM emby",
"SELECT kodi_id", "WHERE kodi_id = ?",
"FROM emby", "AND media_type = ?"
"WHERE media_folder = ?" ))
)) self.embycursor.execute(query, (kodiid, mediatype,))
embycursor.execute(query, (mediafolderid,)) return self.embycursor.fetchone()
items = embycursor.fetchall()
def getItem_byParentId(self, parentid, mediatype):
return items
query = ' '.join((
def getItem_byKodiId(self, kodiid, mediatype):
"SELECT emby_id, kodi_id, kodi_fileid",
embycursor = self.embycursor "FROM emby",
"WHERE parent_id = ?",
query = ' '.join(( "AND media_type = ?"
))
"SELECT emby_id, parent_id", self.embycursor.execute(query, (parentid, mediatype,))
"FROM emby", return self.embycursor.fetchall()
"WHERE kodi_id = ?",
"AND media_type = ?" def getItemId_byParentId(self, parentid, mediatype):
))
embycursor.execute(query, (kodiid, mediatype,)) query = ' '.join((
item = embycursor.fetchone()
"SELECT emby_id, kodi_id",
return item "FROM emby",
"WHERE parent_id = ?",
def getItem_byParentId(self, parentid, mediatype): "AND media_type = ?"
))
embycursor = self.embycursor self.embycursor.execute(query, (parentid, mediatype,))
return self.embycursor.fetchall()
query = ' '.join((
def getChecksum(self, mediatype):
"SELECT emby_id, kodi_id, kodi_fileid",
"FROM emby", query = ' '.join((
"WHERE parent_id = ?",
"AND media_type = ?" "SELECT emby_id, checksum",
)) "FROM emby",
embycursor.execute(query, (parentid, mediatype,)) "WHERE emby_type = ?"
items = embycursor.fetchall() ))
self.embycursor.execute(query, (mediatype,))
return items return self.embycursor.fetchall()
def getItemId_byParentId(self, parentid, mediatype): def getMediaType_byId(self, embyid):
embycursor = self.embycursor query = ' '.join((
query = ' '.join(( "SELECT emby_type",
"FROM emby",
"SELECT emby_id, kodi_id", "WHERE emby_id = ?"
"FROM emby", ))
"WHERE parent_id = ?", self.embycursor.execute(query, (embyid,))
"AND media_type = ?" try:
)) itemtype = self.embycursor.fetchone()[0]
embycursor.execute(query, (parentid, mediatype,))
items = embycursor.fetchall() except TypeError:
itemtype = None
return items
return itemtype
def getChecksum(self, mediatype):
def sortby_mediaType(self, itemids, unsorted=True):
embycursor = self.embycursor
sorted_items = {}
query = ' '.join((
for itemid in itemids:
"SELECT emby_id, checksum",
"FROM emby", mediatype = self.getMediaType_byId(itemid)
"WHERE emby_type = ?" if mediatype:
)) sorted_items.setdefault(mediatype, []).append(itemid)
embycursor.execute(query, (mediatype,)) elif unsorted:
items = embycursor.fetchall() sorted_items.setdefault('Unsorted', []).append(itemid)
return items return sorted_items
def getMediaType_byId(self, embyid): def addReference(self, embyid, kodiid, embytype, mediatype, fileid=None, pathid=None,
parentid=None, checksum=None, mediafolderid=None):
embycursor = self.embycursor query = (
'''
query = ' '.join(( INSERT OR REPLACE INTO emby(
emby_id, kodi_id, kodi_fileid, kodi_pathid, emby_type, media_type, parent_id,
"SELECT emby_type", checksum, media_folder)
"FROM emby",
"WHERE emby_id = ?" VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
)) '''
embycursor.execute(query, (embyid,)) )
try: self.embycursor.execute(query, (embyid, kodiid, fileid, pathid, embytype, mediatype,
itemtype = embycursor.fetchone()[0] parentid, checksum, mediafolderid))
except TypeError: def updateReference(self, embyid, checksum):
itemtype = None
query = "UPDATE emby SET checksum = ? WHERE emby_id = ?"
return itemtype self.embycursor.execute(query, (checksum, embyid))
def sortby_mediaType(self, itemids, unsorted=True): def updateParentId(self, embyid, parent_kodiid):
sorted_items = {} query = "UPDATE emby SET parent_id = ? WHERE emby_id = ?"
self.embycursor.execute(query, (parent_kodiid, embyid))
for itemid in itemids:
def removeItems_byParentId(self, parent_kodiid, mediatype):
mediatype = self.getMediaType_byId(itemid)
if mediatype: query = ' '.join((
sorted_items.setdefault(mediatype, []).append(itemid)
elif unsorted: "DELETE FROM emby",
sorted_items.setdefault('Unsorted', []).append(itemid) "WHERE parent_id = ?",
"AND media_type = ?"
return sorted_items ))
self.embycursor.execute(query, (parent_kodiid, mediatype,))
def addReference(self, embyid, kodiid, embytype, mediatype, fileid=None, pathid=None,
parentid=None, checksum=None, mediafolderid=None): def removeItem_byKodiId(self, kodiid, mediatype):
query = (
''' query = ' '.join((
INSERT OR REPLACE INTO emby(
emby_id, kodi_id, kodi_fileid, kodi_pathid, emby_type, media_type, parent_id, "DELETE FROM emby",
checksum, media_folder) "WHERE kodi_id = ?",
"AND media_type = ?"
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) ))
''' self.embycursor.execute(query, (kodiid, mediatype,))
)
self.embycursor.execute(query, (embyid, kodiid, fileid, pathid, embytype, mediatype, def removeItem(self, embyid):
parentid, checksum, mediafolderid))
query = "DELETE FROM emby WHERE emby_id = ?"
def updateReference(self, embyid, checksum): self.embycursor.execute(query, (embyid,))
query = "UPDATE emby SET checksum = ? WHERE emby_id = ?" def removeWildItem(self, embyid):
self.embycursor.execute(query, (checksum, embyid))
query = "DELETE FROM emby WHERE emby_id LIKE ?"
def updateParentId(self, embyid, parent_kodiid): self.embycursor.execute(query, (embyid+"%",))
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.SOL_IP, socket.IP_MULTICAST_LOOP, 1)
sock.setsockopt(socket.IPPROTO_IP, socket.SO_REUSEADDR, 1) sock.setsockopt(socket.IPPROTO_IP, socket.SO_REUSEADDR, 1)
self.logMsg("MultiGroup : %s" % str(MULTI_GROUP), 2); self.logMsg("MultiGroup : %s" % str(MULTI_GROUP), 2)
self.logMsg("Sending UDP Data: %s" % MESSAGE, 2); self.logMsg("Sending UDP Data: %s" % MESSAGE, 2)
sock.sendto(MESSAGE, MULTI_GROUP) sock.sendto(MESSAGE, MULTI_GROUP)
try: 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 -*- # -*- coding: utf-8 -*-
################################################################################################# #################################################################################################
import json import json
import xbmc import xbmc
import xbmcgui import xbmcgui
import clientinfo import clientinfo
import downloadutils import downloadutils
import embydb_functions as embydb import embydb_functions as embydb
import playbackutils as pbutils import playbackutils as pbutils
import utils import utils
################################################################################################# #################################################################################################
class KodiMonitor(xbmc.Monitor): class KodiMonitor(xbmc.Monitor):
def __init__(self): def __init__(self):
self.clientInfo = clientinfo.ClientInfo() self.clientInfo = clientinfo.ClientInfo()
self.addonName = self.clientInfo.getAddonName() self.addonName = self.clientInfo.getAddonName()
self.doUtils = downloadutils.DownloadUtils() self.doUtils = downloadutils.DownloadUtils()
self.logMsg("Kodi monitor started.", 1) self.logMsg("Kodi monitor started.", 1)
def logMsg(self, msg, lvl=1): def logMsg(self, msg, lvl=1):
self.className = self.__class__.__name__ self.className = self.__class__.__name__
utils.logMsg("%s %s" % (self.addonName, self.className), msg, lvl) utils.logMsg("%s %s" % (self.addonName, self.className), msg, lvl)
def onScanStarted(self, library): def onScanStarted(self, library):
self.logMsg("Kodi library scan %s running." % library, 2) self.logMsg("Kodi library scan %s running." % library, 2)
if library == "video": if library == "video":
utils.window('emby_kodiScan', value="true") utils.window('emby_kodiScan', value="true")
def onScanFinished(self, library): def onScanFinished(self, library):
self.logMsg("Kodi library scan %s finished." % library, 2) self.logMsg("Kodi library scan %s finished." % library, 2)
if library == "video": if library == "video":
utils.window('emby_kodiScan', clear=True) utils.window('emby_kodiScan', clear=True)
def onSettingsChanged(self): def onSettingsChanged(self):
# Monitor emby settings # Monitor emby settings
# Review reset setting at a later time, need to be adjusted to account for initial setup # Review reset setting at a later time, need to be adjusted to account for initial setup
# changes. # changes.
'''currentPath = utils.settings('useDirectPaths') '''currentPath = utils.settings('useDirectPaths')
if utils.window('emby_pluginpath') != currentPath: if utils.window('emby_pluginpath') != currentPath:
# Plugin path value changed. Offer to reset # Plugin path value changed. Offer to reset
self.logMsg("Changed to playback mode detected", 1) self.logMsg("Changed to playback mode detected", 1)
utils.window('emby_pluginpath', value=currentPath) utils.window('emby_pluginpath', value=currentPath)
resp = xbmcgui.Dialog().yesno( resp = xbmcgui.Dialog().yesno(
heading="Playback mode change detected", heading="Playback mode change detected",
line1=( line1=(
"Detected the playback mode has changed. The database " "Detected the playback mode has changed. The database "
"needs to be recreated for the change to be applied. " "needs to be recreated for the change to be applied. "
"Proceed?")) "Proceed?"))
if resp: if resp:
utils.reset()''' utils.reset()'''
currentLog = utils.settings('logLevel') currentLog = utils.settings('logLevel')
if utils.window('emby_logLevel') != currentLog: if utils.window('emby_logLevel') != currentLog:
# The log level changed, set new prop # The log level changed, set new prop
self.logMsg("New log level: %s" % currentLog, 1) self.logMsg("New log level: %s" % currentLog, 1)
utils.window('emby_logLevel', value=currentLog) utils.window('emby_logLevel', value=currentLog)
def onNotification(self, sender, method, data): def onNotification(self, sender, method, data):
doUtils = self.doUtils doUtils = self.doUtils
if method not in ("Playlist.OnAdd"): if method not in ("Playlist.OnAdd"):
self.logMsg("Method: %s Data: %s" % (method, data), 1) self.logMsg("Method: %s Data: %s" % (method, data), 1)
if data: if data:
data = json.loads(data,'utf-8') data = json.loads(data,'utf-8')
if method == "Player.OnPlay": if method == "Player.OnPlay":
# Set up report progress for emby playback # Set up report progress for emby playback
item = data.get('item') item = data.get('item')
try: try:
kodiid = item['id'] kodiid = item['id']
type = item['type'] item_type = item['type']
except (KeyError, TypeError): except (KeyError, TypeError):
self.logMsg("Item is invalid for playstate update.", 1) self.logMsg("Item is invalid for playstate update.", 1)
else: else:
if ((utils.settings('useDirectPaths') == "1" and not type == "song") or if ((utils.settings('useDirectPaths') == "1" and not item_type == "song") or
(type == "song" and utils.settings('enableMusic') == "true")): (item_type == "song" and utils.settings('enableMusic') == "true")):
# Set up properties for player # Set up properties for player
embyconn = utils.kodiSQL('emby') embyconn = utils.kodiSQL('emby')
embycursor = embyconn.cursor() embycursor = embyconn.cursor()
emby_db = embydb.Embydb_Functions(embycursor) emby_db = embydb.Embydb_Functions(embycursor)
emby_dbitem = emby_db.getItem_byKodiId(kodiid, type) emby_dbitem = emby_db.getItem_byKodiId(kodiid, item_type)
try: try:
itemid = emby_dbitem[0] itemid = emby_dbitem[0]
except TypeError: except TypeError:
self.logMsg("No kodiid returned.", 1) self.logMsg("No kodiid returned.", 1)
else: else:
url = "{server}/emby/Users/{UserId}/Items/%s?format=json" % itemid url = "{server}/emby/Users/{UserId}/Items/%s?format=json" % itemid
result = doUtils.downloadUrl(url) result = doUtils.downloadUrl(url)
self.logMsg("Item: %s" % result, 2) self.logMsg("Item: %s" % result, 2)
playurl = None playurl = None
count = 0 count = 0
while not playurl and count < 2: while not playurl and count < 2:
try: try:
playurl = xbmc.Player().getPlayingFile() playurl = xbmc.Player().getPlayingFile()
except RuntimeError: except RuntimeError:
count += 1 count += 1
xbmc.sleep(200) xbmc.sleep(200)
else: else:
listItem = xbmcgui.ListItem() listItem = xbmcgui.ListItem()
playback = pbutils.PlaybackUtils(result) playback = pbutils.PlaybackUtils(result)
if type == "song" and utils.settings('streamMusic') == "true": if item_type == "song" and utils.settings('streamMusic') == "true":
utils.window('emby_%s.playmethod' % playurl, utils.window('emby_%s.playmethod' % playurl,
value="DirectStream") value="DirectStream")
else: else:
utils.window('emby_%s.playmethod' % playurl, utils.window('emby_%s.playmethod' % playurl,
value="DirectPlay") value="DirectPlay")
# Set properties for player.py # Set properties for player.py
playback.setProperties(playurl, listItem) playback.setProperties(playurl, listItem)
finally: finally:
embycursor.close() embycursor.close()
elif method == "VideoLibrary.OnUpdate": elif method == "VideoLibrary.OnUpdate":
# Manually marking as watched/unwatched # Manually marking as watched/unwatched
playcount = data.get('playcount') playcount = data.get('playcount')
item = data.get('item') item = data.get('item')
try: try:
kodiid = item['id'] kodiid = item['id']
type = item['type'] item_type = item['type']
except (KeyError, TypeError): except (KeyError, TypeError):
self.logMsg("Item is invalid for playstate update.", 1) self.logMsg("Item is invalid for playstate update.", 1)
else: else:
# Send notification to the server. # Send notification to the server.
embyconn = utils.kodiSQL('emby') embyconn = utils.kodiSQL('emby')
embycursor = embyconn.cursor() embycursor = embyconn.cursor()
emby_db = embydb.Embydb_Functions(embycursor) emby_db = embydb.Embydb_Functions(embycursor)
emby_dbitem = emby_db.getItem_byKodiId(kodiid, type) emby_dbitem = emby_db.getItem_byKodiId(kodiid, item_type)
try: try:
itemid = emby_dbitem[0] itemid = emby_dbitem[0]
except TypeError: except TypeError:
self.logMsg("Could not find itemid in emby database.", 1) self.logMsg("Could not find itemid in emby database.", 1)
else: else:
# Stop from manually marking as watched unwatched, with actual playback. # Stop from manually marking as watched unwatched, with actual playback.
if utils.window('emby_skipWatched%s' % itemid) == "true": if utils.window('emby_skipWatched%s' % itemid) == "true":
# property is set in player.py # property is set in player.py
utils.window('emby_skipWatched%s' % itemid, clear=True) utils.window('emby_skipWatched%s' % itemid, clear=True)
else: else:
# notify the server # notify the server
url = "{server}/emby/Users/{UserId}/PlayedItems/%s?format=json" % itemid url = "{server}/emby/Users/{UserId}/PlayedItems/%s?format=json" % itemid
if playcount != 0: if playcount != 0:
doUtils.downloadUrl(url, type="POST") doUtils.downloadUrl(url, action_type="POST")
self.logMsg("Mark as watched for itemid: %s" % itemid, 1) self.logMsg("Mark as watched for itemid: %s" % itemid, 1)
else: else:
doUtils.downloadUrl(url, type="DELETE") doUtils.downloadUrl(url, action_type="DELETE")
self.logMsg("Mark as unwatched for itemid: %s" % itemid, 1) self.logMsg("Mark as unwatched for itemid: %s" % itemid, 1)
finally: finally:
embycursor.close() embycursor.close()
elif method == "VideoLibrary.OnRemove": elif method == "VideoLibrary.OnRemove":
# Removed function, because with plugin paths + clean library, it will wipe # Removed function, because with plugin paths + clean library, it will wipe
# entire library if user has permissions. Instead, use the emby context menu available # entire library if user has permissions. Instead, use the emby context menu available
# in Isengard and higher version # in Isengard and higher version
pass pass
'''try: '''try:
kodiid = data['id'] kodiid = data['id']
type = data['type'] type = data['type']
except (KeyError, TypeError): except (KeyError, TypeError):
self.logMsg("Item is invalid for emby deletion.", 1) self.logMsg("Item is invalid for emby deletion.", 1)
else: else:
# Send the delete action to the server. # Send the delete action to the server.
embyconn = utils.kodiSQL('emby') embyconn = utils.kodiSQL('emby')
embycursor = embyconn.cursor() embycursor = embyconn.cursor()
emby_db = embydb.Embydb_Functions(embycursor) emby_db = embydb.Embydb_Functions(embycursor)
emby_dbitem = emby_db.getItem_byKodiId(kodiid, type) emby_dbitem = emby_db.getItem_byKodiId(kodiid, type)
try: try:
itemid = emby_dbitem[0] itemid = emby_dbitem[0]
except TypeError: except TypeError:
self.logMsg("Could not find itemid in emby database.", 1) self.logMsg("Could not find itemid in emby database.", 1)
else: else:
if utils.settings('skipContextMenu') != "true": if utils.settings('skipContextMenu') != "true":
resp = xbmcgui.Dialog().yesno( resp = xbmcgui.Dialog().yesno(
heading="Confirm delete", heading="Confirm delete",
line1="Delete file on Emby Server?") line1="Delete file on Emby Server?")
if not resp: if not resp:
self.logMsg("User skipped deletion.", 1) self.logMsg("User skipped deletion.", 1)
embycursor.close() embycursor.close()
return return
url = "{server}/emby/Items/%s?format=json" % itemid url = "{server}/emby/Items/%s?format=json" % itemid
self.logMsg("Deleting request: %s" % itemid) self.logMsg("Deleting request: %s" % itemid)
doUtils.downloadUrl(url, type="DELETE") doUtils.downloadUrl(url, action_type="DELETE")
finally: finally:
embycursor.close()''' embycursor.close()'''
elif method == "System.OnWake": elif method == "System.OnWake":
# Allow network to wake up # Allow network to wake up
xbmc.sleep(10000) xbmc.sleep(10000)
utils.window('emby_onWake', value="true") utils.window('emby_onWake', value="true")
elif method == "Playlist.OnClear": elif method == "Playlist.OnClear":
pass pass

File diff suppressed because it is too large Load diff

View file

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

View file

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

File diff suppressed because it is too large Load diff

View file

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

View file

@ -1,458 +1,426 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
################################################################################################# #################################################################################################
import xbmc import xbmc
import xbmcgui import xbmcgui
import xbmcvfs import xbmcvfs
import clientinfo import clientinfo
import utils import utils
################################################################################################# #################################################################################################
class PlayUtils(): class PlayUtils():
def __init__(self, item): def __init__(self, item):
self.item = item self.item = item
self.clientInfo = clientinfo.ClientInfo() self.clientInfo = clientinfo.ClientInfo()
self.addonName = self.clientInfo.getAddonName() self.addonName = self.clientInfo.getAddonName()
self.userid = utils.window('emby_currUser') self.userid = utils.window('emby_currUser')
self.server = utils.window('emby_server%s' % self.userid) self.server = utils.window('emby_server%s' % self.userid)
def logMsg(self, msg, lvl=1): def logMsg(self, msg, lvl=1):
self.className = self.__class__.__name__ self.className = self.__class__.__name__
utils.logMsg("%s %s" % (self.addonName, self.className), msg, lvl) utils.logMsg("%s %s" % (self.addonName, self.className), msg, lvl)
def getPlayUrl(self): def getPlayUrl(self):
log = self.logMsg window = utils.window
window = utils.window
playurl = None
item = self.item
playurl = None if (self.item.get('Type') in ("Recording", "TvChannel") and
self.item.get('MediaSources') and self.item['MediaSources'][0]['Protocol'] == "Http"):
if (item.get('Type') in ("Recording", "TvChannel") and # Play LiveTV or recordings
item.get('MediaSources') and item['MediaSources'][0]['Protocol'] == "Http"): self.logMsg("File protocol is http (livetv).", 1)
# Play LiveTV or recordings playurl = "%s/emby/Videos/%s/live.m3u8?static=true" % (self.server, self.item['Id'])
log("File protocol is http (livetv).", 1) window('emby_%s.playmethod' % playurl, value="Transcode")
playurl = "%s/emby/Videos/%s/live.m3u8?static=true" % (self.server, 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
elif item.get('MediaSources') and item['MediaSources'][0]['Protocol'] == "Http": self.logMsg("File protocol is http.", 1)
# Only play as http, used for channels, or online hosting of content playurl = self.httpPlay()
log("File protocol is http.", 1) window('emby_%s.playmethod' % playurl, value="DirectStream")
playurl = self.httpPlay()
window('emby_%s.playmethod' % playurl, value="DirectStream") elif self.isDirectPlay():
elif self.isDirectPlay(): self.logMsg("File is direct playing.", 1)
playurl = self.directPlay()
log("File is direct playing.", 1) playurl = playurl.encode('utf-8')
playurl = self.directPlay() # Set playmethod property
playurl = playurl.encode('utf-8') window('emby_%s.playmethod' % playurl, value="DirectPlay")
# Set playmethod property
window('emby_%s.playmethod' % playurl, value="DirectPlay") elif self.isDirectStream():
elif self.isDirectStream(): self.logMsg("File is direct streaming.", 1)
playurl = self.directStream()
log("File is direct streaming.", 1) # Set playmethod property
playurl = self.directStream() window('emby_%s.playmethod' % playurl, value="DirectStream")
# Set playmethod property
window('emby_%s.playmethod' % playurl, value="DirectStream") elif self.isTranscoding():
elif self.isTranscoding(): self.logMsg("File is transcoding.", 1)
playurl = self.transcoding()
log("File is transcoding.", 1) # Set playmethod property
playurl = self.transcoding() window('emby_%s.playmethod' % playurl, value="Transcode")
# Set playmethod property
window('emby_%s.playmethod' % playurl, value="Transcode") return playurl
return playurl def httpPlay(self):
# Audio, Video, Photo
def httpPlay(self):
# Audio, Video, Photo itemid = self.item['Id']
item = self.item mediatype = self.item['MediaType']
server = self.server
if mediatype == "Audio":
itemid = item['Id'] playurl = "%s/emby/Audio/%s/stream" % (self.server, itemid)
mediatype = item['MediaType'] else:
playurl = "%s/emby/Videos/%s/stream?static=true" % (self.server, itemid)
if mediatype == "Audio":
playurl = "%s/emby/Audio/%s/stream" % (server, itemid) return playurl
else:
playurl = "%s/emby/Videos/%s/stream?static=true" % (server, itemid) def isDirectPlay(self):
return playurl lang = utils.language
settings = utils.settings
def isDirectPlay(self): dialog = xbmcgui.Dialog()
log = self.logMsg
lang = utils.language # Requirement: Filesystem, Accessible path
settings = utils.settings if settings('playFromStream') == "true":
dialog = xbmcgui.Dialog() # User forcing to play via HTTP
self.logMsg("Can't direct play, play from HTTP enabled.", 1)
item = self.item return False
# Requirement: Filesystem, Accessible path videotrack = self.item['MediaSources'][0]['Name']
if settings('playFromStream') == "true": transcodeH265 = settings('transcodeH265')
# User forcing to play via HTTP
log("Can't direct play, play from HTTP enabled.", 1) if transcodeH265 in ("1", "2", "3") and ("HEVC" in videotrack or "H265" in videotrack):
return False # Avoid H265/HEVC depending on the resolution
resolution = int(videotrack.split("P", 1)[0])
videotrack = item['MediaSources'][0]['Name'] res = {
transcodeH265 = settings('transcodeH265')
'1': 480,
if transcodeH265 in ("1", "2", "3") and ("HEVC" in videotrack or "H265" in videotrack): '2': 720,
# Avoid H265/HEVC depending on the resolution '3': 1080
resolution = int(videotrack.split("P", 1)[0]) }
res = { self.logMsg("Resolution is: %sP, transcode for resolution: %sP+"
% (resolution, res[transcodeH265]), 1)
'1': 480, if res[transcodeH265] <= resolution:
'2': 720, return False
'3': 1080
} canDirectPlay = self.item['MediaSources'][0]['SupportsDirectPlay']
log("Resolution is: %sP, transcode for resolution: %sP+" # Make sure direct play is supported by the server
% (resolution, res[transcodeH265]), 1) if not canDirectPlay:
if res[transcodeH265] <= resolution: self.logMsg("Can't direct play, server doesn't allow/support it.", 1)
return False return False
canDirectPlay = item['MediaSources'][0]['SupportsDirectPlay'] location = self.item['LocationType']
# Make sure direct play is supported by the server if location == "FileSystem":
if not canDirectPlay: # Verify the path
log("Can't direct play, server doesn't allow/support it.", 1) if not self.fileExists():
return False self.logMsg("Unable to direct play.")
try:
location = item['LocationType'] count = int(settings('failCount'))
if location == "FileSystem": except ValueError:
# Verify the path count = 0
if not self.fileExists(): self.logMsg("Direct play failed: %s times." % count, 1)
log("Unable to direct play.")
try: if count < 2:
count = int(settings('failCount')) # Let the user know that direct play failed
except ValueError: settings('failCount', value=str(count+1))
count = 0 dialog.notification(
log("Direct play failed: %s times." % count, 1) heading="Emby for Kodi",
message=lang(33011),
if count < 2: icon="special://home/addons/plugin.video.emby/icon.png",
# Let the user know that direct play failed sound=False)
settings('failCount', value=str(count+1)) elif settings('playFromStream') != "true":
dialog.notification( # Permanently set direct stream as true
heading="Emby for Kodi", settings('playFromStream', value="true")
message=lang(33011), settings('failCount', value="0")
icon="special://home/addons/plugin.video.emby/icon.png", dialog.notification(
sound=False) heading="Emby for Kodi",
elif settings('playFromStream') != "true": message=lang(33012),
# Permanently set direct stream as true icon="special://home/addons/plugin.video.emby/icon.png",
settings('playFromStream', value="true") sound=False)
settings('failCount', value="0") return False
dialog.notification(
heading="Emby for Kodi", return True
message=lang(33012),
icon="special://home/addons/plugin.video.emby/icon.png", def directPlay(self):
sound=False)
return False try:
playurl = self.item['MediaSources'][0]['Path']
return True except (IndexError, KeyError):
playurl = self.item['Path']
def directPlay(self):
if self.item.get('VideoType'):
item = self.item # Specific format modification
if self.item['VideoType'] == "Dvd":
try: playurl = "%s/VIDEO_TS/VIDEO_TS.IFO" % playurl
playurl = item['MediaSources'][0]['Path'] elif self.item['VideoType'] == "BluRay":
except (IndexError, KeyError): playurl = "%s/BDMV/index.bdmv" % playurl
playurl = item['Path']
# Assign network protocol
if item.get('VideoType'): if playurl.startswith('\\\\'):
# Specific format modification playurl = playurl.replace("\\\\", "smb://")
type = item['VideoType'] playurl = playurl.replace("\\", "/")
if type == "Dvd": if "apple.com" in playurl:
playurl = "%s/VIDEO_TS/VIDEO_TS.IFO" % playurl USER_AGENT = "QuickTime/7.7.4"
elif type == "BluRay": playurl += "?|User-Agent=%s" % USER_AGENT
playurl = "%s/BDMV/index.bdmv" % playurl
return playurl
# Assign network protocol
if playurl.startswith('\\\\'): def fileExists(self):
playurl = playurl.replace("\\\\", "smb://")
playurl = playurl.replace("\\", "/") if 'Path' not in self.item:
# File has no path defined in server
if "apple.com" in playurl: return False
USER_AGENT = "QuickTime/7.7.4"
playurl += "?|User-Agent=%s" % USER_AGENT # Convert path to direct play
path = self.directPlay()
return playurl self.logMsg("Verifying path: %s" % path, 1)
def fileExists(self): if xbmcvfs.exists(path):
self.logMsg("Path exists.", 1)
log = self.logMsg return True
if 'Path' not in self.item: elif ":" not in path:
# File has no path defined in server self.logMsg("Can't verify path, assumed linux. Still try to direct play.", 1)
return False return True
# Convert path to direct play else:
path = self.directPlay() self.logMsg("Failed to find file.", 1)
log("Verifying path: %s" % path, 1) return False
if xbmcvfs.exists(path): def isDirectStream(self):
log("Path exists.", 1)
return True
videotrack = self.item['MediaSources'][0]['Name']
elif ":" not in path: transcodeH265 = utils.settings('transcodeH265')
log("Can't verify path, assumed linux. Still try to direct play.", 1)
return True if transcodeH265 in ("1", "2", "3") and ("HEVC" in videotrack or "H265" in videotrack):
# Avoid H265/HEVC depending on the resolution
else: resolution = int(videotrack.split("P", 1)[0])
log("Failed to find file.", 1) res = {
return False
'1': 480,
def isDirectStream(self): '2': 720,
'3': 1080
log = self.logMsg }
self.logMsg("Resolution is: %sP, transcode for resolution: %sP+"
item = self.item % (resolution, res[transcodeH265]), 1)
if res[transcodeH265] <= resolution:
videotrack = item['MediaSources'][0]['Name'] return False
transcodeH265 = utils.settings('transcodeH265')
# Requirement: BitRate, supported encoding
if transcodeH265 in ("1", "2", "3") and ("HEVC" in videotrack or "H265" in videotrack): canDirectStream = self.item['MediaSources'][0]['SupportsDirectStream']
# Avoid H265/HEVC depending on the resolution # Make sure the server supports it
resolution = int(videotrack.split("P", 1)[0]) if not canDirectStream:
res = { return False
'1': 480, # Verify the bitrate
'2': 720, if not self.isNetworkSufficient():
'3': 1080 self.logMsg("The network speed is insufficient to direct stream file.", 1)
} return False
log("Resolution is: %sP, transcode for resolution: %sP+"
% (resolution, res[transcodeH265]), 1) return True
if res[transcodeH265] <= resolution:
return False def directStream(self):
# Requirement: BitRate, supported encoding if 'Path' in self.item and self.item['Path'].endswith('.strm'):
canDirectStream = item['MediaSources'][0]['SupportsDirectStream'] # Allow strm loading when direct streaming
# Make sure the server supports it playurl = self.directPlay()
if not canDirectStream: elif self.item['Type'] == "Audio":
return False playurl = "%s/emby/Audio/%s/stream.mp3" % (self.server, self.item['Id'])
else:
# Verify the bitrate playurl = "%s/emby/Videos/%s/stream?static=true" % (self.server, self.item['Id'])
if not self.isNetworkSufficient():
log("The network speed is insufficient to direct stream file.", 1) return playurl
return False
def isNetworkSufficient(self):
return True
def directStream(self): settings = self.getBitrate()*1000
item = self.item try:
server = self.server sourceBitrate = int(self.item['MediaSources'][0]['Bitrate'])
except (KeyError, TypeError):
itemid = item['Id'] self.logMsg("Bitrate value is missing.", 1)
itemtype = item['Type'] else:
self.logMsg("The add-on settings bitrate is: %s, the video bitrate required is: %s"
if 'Path' in item and item['Path'].endswith('.strm'): % (settings, sourceBitrate), 1)
# Allow strm loading when direct streaming if settings < sourceBitrate:
playurl = self.directPlay() return False
elif itemtype == "Audio":
playurl = "%s/emby/Audio/%s/stream.mp3" % (server, itemid) return True
else:
playurl = "%s/emby/Videos/%s/stream?static=true" % (server, itemid) def isTranscoding(self):
# Make sure the server supports it
return playurl if not self.item['MediaSources'][0]['SupportsTranscoding']:
return False
def isNetworkSufficient(self):
return True
log = self.logMsg
def transcoding(self):
settings = self.getBitrate()*1000
if 'Path' in self.item and self.item['Path'].endswith('.strm'):
try: # Allow strm loading when transcoding
sourceBitrate = int(self.item['MediaSources'][0]['Bitrate']) playurl = self.directPlay()
except (KeyError, TypeError): else:
log("Bitrate value is missing.", 1) itemid = self.item['Id']
else: deviceId = self.clientInfo.getDeviceId()
log("The add-on settings bitrate is: %s, the video bitrate required is: %s" playurl = (
% (settings, sourceBitrate), 1) "%s/emby/Videos/%s/master.m3u8?MediaSourceId=%s"
if settings < sourceBitrate: % (self.server, itemid, itemid)
return False )
playurl = (
return True "%s&VideoCodec=h264&AudioCodec=ac3&MaxAudioChannels=6&deviceId=%s&VideoBitrate=%s"
% (playurl, deviceId, self.getBitrate()*1000))
def isTranscoding(self):
return playurl
item = self.item
def getBitrate(self):
canTranscode = item['MediaSources'][0]['SupportsTranscoding']
# Make sure the server supports it # get the addon video quality
if not canTranscode: bitrate = {
return False
'0': 664,
return True '1': 996,
'2': 1320,
def transcoding(self): '3': 2000,
'4': 3200,
item = self.item '5': 4700,
'6': 6200,
if 'Path' in item and item['Path'].endswith('.strm'): '7': 7700,
# Allow strm loading when transcoding '8': 9200,
playurl = self.directPlay() '9': 10700,
else: '10': 12200,
itemid = item['Id'] '11': 13700,
deviceId = self.clientInfo.getDeviceId() '12': 15200,
playurl = ( '13': 16700,
"%s/emby/Videos/%s/master.m3u8?MediaSourceId=%s" '14': 18200,
% (self.server, itemid, itemid) '15': 20000,
) '16': 40000,
playurl = ( '17': 100000,
"%s&VideoCodec=h264&AudioCodec=ac3&MaxAudioChannels=6&deviceId=%s&VideoBitrate=%s" '18': 1000000
% (playurl, deviceId, self.getBitrate()*1000)) }
return playurl # max bit rate supported by server (max signed 32bit integer)
return bitrate.get(utils.settings('videoBitrate'), 2147483)
def getBitrate(self):
def audioSubsPref(self, url, listitem):
# get the addon video quality
videoQuality = utils.settings('videoBitrate') lang = utils.language
bitrate = { dialog = xbmcgui.Dialog()
# For transcoding only
'0': 664, # Present the list of audio to select from
'1': 996, audioStreamsList = {}
'2': 1320, audioStreams = []
'3': 2000, audioStreamsChannelsList = {}
'4': 3200, subtitleStreamsList = {}
'5': 4700, subtitleStreams = ['No subtitles']
'6': 6200, downloadableStreams = []
'7': 7700, selectAudioIndex = ""
'8': 9200, selectSubsIndex = ""
'9': 10700, playurlprefs = "%s" % url
'10': 12200,
'11': 13700, try:
'12': 15200, mediasources = self.item['MediaSources'][0]
'13': 16700, mediastreams = mediasources['MediaStreams']
'14': 18200, except (TypeError, KeyError, IndexError):
'15': 20000, return
'16': 40000,
'17': 100000, for stream in mediastreams:
'18': 1000000 # Since Emby returns all possible tracks together, have to sort them.
} index = stream['Index']
# max bit rate supported by server (max signed 32bit integer) if 'Audio' in stream['Type']:
return bitrate.get(videoQuality, 2147483) codec = stream['Codec']
channelLayout = stream.get('ChannelLayout', "")
def audioSubsPref(self, url, listitem):
try:
log = self.logMsg track = "%s - %s - %s %s" % (index, stream['Language'], codec, channelLayout)
lang = utils.language except:
dialog = xbmcgui.Dialog() track = "%s - %s %s" % (index, codec, channelLayout)
# For transcoding only
# Present the list of audio to select from audioStreamsChannelsList[index] = stream['Channels']
audioStreamsList = {} audioStreamsList[track] = index
audioStreams = [] audioStreams.append(track)
audioStreamsChannelsList = {}
subtitleStreamsList = {} elif 'Subtitle' in stream['Type']:
subtitleStreams = ['No subtitles'] try:
downloadableStreams = [] track = "%s - %s" % (index, stream['Language'])
selectAudioIndex = "" except:
selectSubsIndex = "" track = "%s - %s" % (index, stream['Codec'])
playurlprefs = "%s" % url
default = stream['IsDefault']
item = self.item forced = stream['IsForced']
try: downloadable = stream['IsTextSubtitleStream']
mediasources = item['MediaSources'][0]
mediastreams = mediasources['MediaStreams'] if default:
except (TypeError, KeyError, IndexError): track = "%s - Default" % track
return if forced:
track = "%s - Forced" % track
for stream in mediastreams: if downloadable:
# Since Emby returns all possible tracks together, have to sort them. downloadableStreams.append(index)
index = stream['Index']
type = stream['Type'] subtitleStreamsList[track] = index
subtitleStreams.append(track)
if 'Audio' in type:
codec = stream['Codec']
channelLayout = stream.get('ChannelLayout', "") if len(audioStreams) > 1:
resp = dialog.select(lang(33013), audioStreams)
try: if resp > -1:
track = "%s - %s - %s %s" % (index, stream['Language'], codec, channelLayout) # User selected audio
except: selected = audioStreams[resp]
track = "%s - %s %s" % (index, codec, channelLayout) selectAudioIndex = audioStreamsList[selected]
playurlprefs += "&AudioStreamIndex=%s" % selectAudioIndex
audioStreamsChannelsList[index] = stream['Channels'] else: # User backed out of selection
audioStreamsList[track] = index playurlprefs += "&AudioStreamIndex=%s" % mediasources['DefaultAudioStreamIndex']
audioStreams.append(track) else: # There's only one audiotrack.
selectAudioIndex = audioStreamsList[audioStreams[0]]
elif 'Subtitle' in type: playurlprefs += "&AudioStreamIndex=%s" % selectAudioIndex
try:
track = "%s - %s" % (index, stream['Language']) if len(subtitleStreams) > 1:
except: resp = dialog.select(lang(33014), subtitleStreams)
track = "%s - %s" % (index, stream['Codec']) if resp == 0:
# User selected no subtitles
default = stream['IsDefault'] pass
forced = stream['IsForced'] elif resp > -1:
downloadable = stream['IsTextSubtitleStream'] # User selected subtitles
selected = subtitleStreams[resp]
if default: selectSubsIndex = subtitleStreamsList[selected]
track = "%s - Default" % track
if forced: # Load subtitles in the listitem if downloadable
track = "%s - Forced" % track if selectSubsIndex in downloadableStreams:
if downloadable:
downloadableStreams.append(index) itemid = self.item['Id']
url = [("%s/Videos/%s/%s/Subtitles/%s/Stream.srt"
subtitleStreamsList[track] = index % (self.server, itemid, itemid, selectSubsIndex))]
subtitleStreams.append(track) self.logMsg("Set up subtitles: %s %s" % (selectSubsIndex, url), 1)
listitem.setSubtitles(url)
else:
if len(audioStreams) > 1: # Burn subtitles
resp = dialog.select(lang(33013), audioStreams) playurlprefs += "&SubtitleStreamIndex=%s" % selectSubsIndex
if resp > -1:
# User selected audio else: # User backed out of selection
selected = audioStreams[resp] playurlprefs += "&SubtitleStreamIndex=%s" % mediasources.get('DefaultSubtitleStreamIndex', "")
selectAudioIndex = audioStreamsList[selected]
playurlprefs += "&AudioStreamIndex=%s" % selectAudioIndex # Get number of channels for selected audio track
else: # User backed out of selection audioChannels = audioStreamsChannelsList.get(selectAudioIndex, 0)
playurlprefs += "&AudioStreamIndex=%s" % mediasources['DefaultAudioStreamIndex'] if audioChannels > 2:
else: # There's only one audiotrack. playurlprefs += "&AudioBitrate=384000"
selectAudioIndex = audioStreamsList[audioStreams[0]] else:
playurlprefs += "&AudioStreamIndex=%s" % selectAudioIndex playurlprefs += "&AudioBitrate=192000"
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"
return playurlprefs return playurlprefs

File diff suppressed because it is too large Load diff

View file

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

File diff suppressed because it is too large Load diff

View file

@ -1,395 +1,394 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
################################################################################################# #################################################################################################
import shutil import shutil
import xml.etree.ElementTree as etree import xml.etree.ElementTree as etree
import xbmc import xbmc
import xbmcaddon import xbmcaddon
import xbmcvfs import xbmcvfs
import clientinfo import clientinfo
import utils import utils
################################################################################################# #################################################################################################
class VideoNodes(object): class VideoNodes(object):
def __init__(self): def __init__(self):
clientInfo = clientinfo.ClientInfo() clientInfo = clientinfo.ClientInfo()
self.addonName = clientInfo.getAddonName() self.addonName = clientInfo.getAddonName()
self.kodiversion = int(xbmc.getInfoLabel('System.BuildVersion')[:2]) self.kodiversion = int(xbmc.getInfoLabel('System.BuildVersion')[:2])
def logMsg(self, msg, lvl=1): def logMsg(self, msg, lvl=1):
className = self.__class__.__name__ className = self.__class__.__name__
utils.logMsg("%s %s" % (self.addonName, className), msg, lvl) utils.logMsg("%s %s" % (self.addonName, className), msg, lvl)
def commonRoot(self, order, label, tagname, roottype=1): def commonRoot(self, order, label, tagname, roottype=1):
if roottype == 0: if roottype == 0:
# Index # Index
root = etree.Element('node', attrib={'order': "%s" % order}) root = etree.Element('node', attrib={'order': "%s" % order})
elif roottype == 1: elif roottype == 1:
# Filter # Filter
root = etree.Element('node', attrib={'order': "%s" % order, 'type': "filter"}) root = etree.Element('node', attrib={'order': "%s" % order, 'type': "filter"})
etree.SubElement(root, 'match').text = "all" etree.SubElement(root, 'match').text = "all"
# Add tag rule # Add tag rule
rule = etree.SubElement(root, 'rule', attrib={'field': "tag", 'operator': "is"}) rule = etree.SubElement(root, 'rule', attrib={'field': "tag", 'operator': "is"})
etree.SubElement(rule, 'value').text = tagname etree.SubElement(rule, 'value').text = tagname
else: else:
# Folder # Folder
root = etree.Element('node', attrib={'order': "%s" % order, 'type': "folder"}) root = etree.Element('node', attrib={'order': "%s" % order, 'type': "folder"})
etree.SubElement(root, 'label').text = label etree.SubElement(root, 'label').text = label
etree.SubElement(root, 'icon').text = "special://home/addons/plugin.video.emby/icon.png" etree.SubElement(root, 'icon').text = "special://home/addons/plugin.video.emby/icon.png"
return root return root
def viewNode(self, indexnumber, tagname, mediatype, viewtype, viewid, delete=False): def viewNode(self, indexnumber, tagname, mediatype, viewtype, viewid, delete=False):
window = utils.window window = utils.window
kodiversion = self.kodiversion
if viewtype == "mixed":
if viewtype == "mixed": dirname = "%s - %s" % (viewid, mediatype)
dirname = "%s - %s" % (viewid, mediatype) else:
else: dirname = viewid
dirname = viewid
path = xbmc.translatePath("special://profile/library/video/").decode('utf-8')
path = xbmc.translatePath("special://profile/library/video/").decode('utf-8') nodepath = xbmc.translatePath(
nodepath = xbmc.translatePath( "special://profile/library/video/Emby - %s/" % dirname).decode('utf-8')
"special://profile/library/video/Emby - %s/" % dirname).decode('utf-8')
# Verify the video directory
# Verify the video directory if not xbmcvfs.exists(path):
if not xbmcvfs.exists(path): shutil.copytree(
shutil.copytree( src=xbmc.translatePath("special://xbmc/system/library/video").decode('utf-8'),
src=xbmc.translatePath("special://xbmc/system/library/video").decode('utf-8'), dst=xbmc.translatePath("special://profile/library/video").decode('utf-8'))
dst=xbmc.translatePath("special://profile/library/video").decode('utf-8')) xbmcvfs.exists(path)
xbmcvfs.exists(path)
# Create the node directory
# Create the node directory if not xbmcvfs.exists(nodepath) and not mediatype == "photos":
if not xbmcvfs.exists(nodepath) and not mediatype == "photos": # We need to copy over the default items
# We need to copy over the default items xbmcvfs.mkdirs(nodepath)
xbmcvfs.mkdirs(nodepath) else:
else: if delete:
if delete: dirs, files = xbmcvfs.listdir(nodepath)
dirs, files = xbmcvfs.listdir(nodepath) for file in files:
for file in files: xbmcvfs.delete(nodepath + file)
xbmcvfs.delete(nodepath + file)
self.logMsg("Sucessfully removed videonode: %s." % tagname, 1)
self.logMsg("Sucessfully removed videonode: %s." % tagname, 1) return
return
# Create index entry
# Create index entry nodeXML = "%sindex.xml" % nodepath
nodeXML = "%sindex.xml" % nodepath # Set windows property
# Set windows property path = "library://video/Emby - %s/" % dirname
path = "library://video/Emby - %s/" % dirname for i in range(1, indexnumber):
for i in range(1, indexnumber): # Verify to make sure we don't create duplicates
# Verify to make sure we don't create duplicates if window('Emby.nodes.%s.index' % i) == path:
if window('Emby.nodes.%s.index' % i) == path: return
return
if mediatype == "photos":
if mediatype == "photos": path = "plugin://plugin.video.emby/?id=%s&mode=getsubfolders" % indexnumber
path = "plugin://plugin.video.emby/?id=%s&mode=getsubfolders" % indexnumber
window('Emby.nodes.%s.index' % indexnumber, value=path)
window('Emby.nodes.%s.index' % indexnumber, value=path)
# Root
# Root if not mediatype == "photos":
if not mediatype == "photos": if viewtype == "mixed":
if viewtype == "mixed": specialtag = "%s - %s" % (tagname, mediatype)
specialtag = "%s - %s" % (tagname, mediatype) root = self.commonRoot(order=0, label=specialtag, tagname=tagname, roottype=0)
root = self.commonRoot(order=0, label=specialtag, tagname=tagname, roottype=0) else:
else: root = self.commonRoot(order=0, label=tagname, tagname=tagname, roottype=0)
root = self.commonRoot(order=0, label=tagname, tagname=tagname, roottype=0) try:
try: utils.indent(root)
utils.indent(root) except: pass
except: pass etree.ElementTree(root).write(nodeXML)
etree.ElementTree(root).write(nodeXML)
nodetypes = {
nodetypes = {
'1': "all",
'1': "all", '2': "recent",
'2': "recent", '3': "recentepisodes",
'3': "recentepisodes", '4': "inprogress",
'4': "inprogress", '5': "inprogressepisodes",
'5': "inprogressepisodes", '6': "unwatched",
'6': "unwatched", '7': "nextepisodes",
'7': "nextepisodes", '8': "sets",
'8': "sets", '9': "genres",
'9': "genres", '10': "random",
'10': "random", '11': "recommended",
'11': "recommended", }
} mediatypes = {
mediatypes = { # label according to nodetype per mediatype
# label according to nodetype per mediatype 'movies':
'movies': {
{ '1': tagname,
'1': tagname, '2': 30174,
'2': 30174, '4': 30177,
'4': 30177, '6': 30189,
'6': 30189, '8': 20434,
'8': 20434, '9': 135,
'9': 135, '10': 30229,
'10': 30229, '11': 30230
'11': 30230 },
},
'tvshows':
'tvshows': {
{ '1': tagname,
'1': tagname, '2': 30170,
'2': 30170, '3': 30175,
'3': 30175, '4': 30171,
'4': 30171, '5': 30178,
'5': 30178, '7': 30179,
'7': 30179, '9': 135,
'9': 135, '10': 30229,
'10': 30229, '11': 30230
'11': 30230 },
},
'homevideos':
'homevideos': {
{ '1': tagname,
'1': tagname, '2': 30251,
'2': 30251, '11': 30253
'11': 30253 },
},
'photos':
'photos': {
{ '1': tagname,
'1': tagname, '2': 30252,
'2': 30252, '8': 30255,
'8': 30255, '11': 30254
'11': 30254 },
},
'musicvideos':
'musicvideos': {
{ '1': tagname,
'1': tagname, '2': 30256,
'2': 30256, '4': 30257,
'4': 30257, '6': 30258
'6': 30258 }
} }
}
nodes = mediatypes[mediatype]
nodes = mediatypes[mediatype] for node in nodes:
for node in nodes:
nodetype = nodetypes[node]
nodetype = nodetypes[node] nodeXML = "%s%s_%s.xml" % (nodepath, viewid, nodetype)
nodeXML = "%s%s_%s.xml" % (nodepath, viewid, nodetype) # Get label
# Get label stringid = nodes[node]
stringid = nodes[node] if node != "1":
if node != "1": label = utils.language(stringid)
label = utils.language(stringid) if not label:
if not label: label = xbmc.getLocalizedString(stringid)
label = xbmc.getLocalizedString(stringid) else:
else: label = stringid
label = stringid
# Set window properties
# Set window properties if (mediatype == "homevideos" or mediatype == "photos") and nodetype == "all":
if (mediatype == "homevideos" or mediatype == "photos") and nodetype == "all": # Custom query
# Custom query path = ("plugin://plugin.video.emby/?id=%s&mode=browsecontent&type=%s"
path = ("plugin://plugin.video.emby/?id=%s&mode=browsecontent&type=%s" % (tagname, mediatype))
% (tagname, mediatype)) elif (mediatype == "homevideos" or mediatype == "photos"):
elif (mediatype == "homevideos" or mediatype == "photos"): # Custom query
# Custom query path = ("plugin://plugin.video.emby/?id=%s&mode=browsecontent&type=%s&folderid=%s"
path = ("plugin://plugin.video.emby/?id=%s&mode=browsecontent&type=%s&folderid=%s" % (tagname, mediatype, nodetype))
% (tagname, mediatype, nodetype)) elif nodetype == "nextepisodes":
elif nodetype == "nextepisodes": # Custom query
# Custom query path = "plugin://plugin.video.emby/?id=%s&mode=nextup&limit=25" % tagname
path = "plugin://plugin.video.emby/?id=%s&mode=nextup&limit=25" % tagname elif self.kodiversion == 14 and nodetype == "recentepisodes":
elif kodiversion == 14 and nodetype == "recentepisodes": # Custom query
# Custom query path = "plugin://plugin.video.emby/?id=%s&mode=recentepisodes&limit=25" % tagname
path = "plugin://plugin.video.emby/?id=%s&mode=recentepisodes&limit=25" % tagname elif self.kodiversion == 14 and nodetype == "inprogressepisodes":
elif kodiversion == 14 and nodetype == "inprogressepisodes": # Custom query
# Custom query path = "plugin://plugin.video.emby/?id=%s&mode=inprogressepisodes&limit=25"% tagname
path = "plugin://plugin.video.emby/?id=%s&mode=inprogressepisodes&limit=25"% tagname else:
else: path = "library://video/Emby - %s/%s_%s.xml" % (dirname, viewid, nodetype)
path = "library://video/Emby - %s/%s_%s.xml" % (dirname, viewid, nodetype)
if mediatype == "photos":
if mediatype == "photos": windowpath = "ActivateWindow(Pictures,%s,return)" % path
windowpath = "ActivateWindow(Pictures,%s,return)" % path else:
else: windowpath = "ActivateWindow(Video,%s,return)" % path
windowpath = "ActivateWindow(Video,%s,return)" % path
if nodetype == "all":
if nodetype == "all":
if viewtype == "mixed":
if viewtype == "mixed": templabel = "%s - %s" % (tagname, mediatype)
templabel = "%s - %s" % (tagname, mediatype) else:
else: templabel = label
templabel = label
embynode = "Emby.nodes.%s" % indexnumber
embynode = "Emby.nodes.%s" % indexnumber window('%s.title' % embynode, value=templabel)
window('%s.title' % embynode, value=templabel) window('%s.path' % embynode, value=windowpath)
window('%s.path' % embynode, value=windowpath) window('%s.content' % embynode, value=path)
window('%s.content' % embynode, value=path) window('%s.type' % embynode, value=mediatype)
window('%s.type' % embynode, value=mediatype) else:
else: embynode = "Emby.nodes.%s.%s" % (indexnumber, nodetype)
embynode = "Emby.nodes.%s.%s" % (indexnumber, nodetype) window('%s.title' % embynode, value=label)
window('%s.title' % embynode, value=label) window('%s.path' % embynode, value=windowpath)
window('%s.path' % embynode, value=windowpath) window('%s.content' % embynode, value=path)
window('%s.content' % embynode, value=path)
if mediatype == "photos":
if mediatype == "photos": # For photos, we do not create a node in videos but we do want the window props
# For photos, we do not create a node in videos but we do want the window props # to be created.
# to be created. # To do: add our photos nodes to kodi picture sources somehow
# To do: add our photos nodes to kodi picture sources somehow continue
continue
if xbmcvfs.exists(nodeXML):
if xbmcvfs.exists(nodeXML): # Don't recreate xml if already exists
# Don't recreate xml if already exists continue
continue
# Create the root
# Create the root if (nodetype == "nextepisodes" or mediatype == "homevideos" or
if (nodetype == "nextepisodes" or mediatype == "homevideos" or (self.kodiversion == 14 and nodetype in ('recentepisodes', 'inprogressepisodes'))):
(kodiversion == 14 and nodetype in ('recentepisodes', 'inprogressepisodes'))): # Folder type with plugin path
# Folder type with plugin path root = self.commonRoot(order=node, label=label, tagname=tagname, roottype=2)
root = self.commonRoot(order=node, label=label, tagname=tagname, roottype=2) etree.SubElement(root, 'path').text = path
etree.SubElement(root, 'path').text = path etree.SubElement(root, 'content').text = "episodes"
etree.SubElement(root, 'content').text = "episodes" else:
else: root = self.commonRoot(order=node, label=label, tagname=tagname)
root = self.commonRoot(order=node, label=label, tagname=tagname) if nodetype in ('recentepisodes', 'inprogressepisodes'):
if nodetype in ('recentepisodes', 'inprogressepisodes'): etree.SubElement(root, 'content').text = "episodes"
etree.SubElement(root, 'content').text = "episodes" else:
else: etree.SubElement(root, 'content').text = mediatype
etree.SubElement(root, 'content').text = mediatype
limit = "25"
limit = "25" # Elements per nodetype
# Elements per nodetype if nodetype == "all":
if nodetype == "all": etree.SubElement(root, 'order', {'direction': "ascending"}).text = "sorttitle"
etree.SubElement(root, 'order', {'direction': "ascending"}).text = "sorttitle"
elif nodetype == "recent":
elif nodetype == "recent": etree.SubElement(root, 'order', {'direction': "descending"}).text = "dateadded"
etree.SubElement(root, 'order', {'direction': "descending"}).text = "dateadded" etree.SubElement(root, 'limit').text = limit
etree.SubElement(root, 'limit').text = limit rule = etree.SubElement(root, 'rule', {'field': "playcount", 'operator': "is"})
rule = etree.SubElement(root, 'rule', {'field': "playcount", 'operator': "is"}) etree.SubElement(rule, 'value').text = "0"
etree.SubElement(rule, 'value').text = "0"
elif nodetype == "inprogress":
elif nodetype == "inprogress": etree.SubElement(root, 'rule', {'field': "inprogress", 'operator': "true"})
etree.SubElement(root, 'rule', {'field': "inprogress", 'operator': "true"}) etree.SubElement(root, 'limit').text = limit
etree.SubElement(root, 'limit').text = limit
elif nodetype == "genres":
elif nodetype == "genres": etree.SubElement(root, 'order', {'direction': "ascending"}).text = "sorttitle"
etree.SubElement(root, 'order', {'direction': "ascending"}).text = "sorttitle" etree.SubElement(root, 'group').text = "genres"
etree.SubElement(root, 'group').text = "genres"
elif nodetype == "unwatched":
elif nodetype == "unwatched": etree.SubElement(root, 'order', {'direction': "ascending"}).text = "sorttitle"
etree.SubElement(root, 'order', {'direction': "ascending"}).text = "sorttitle" rule = etree.SubElement(root, "rule", {'field': "playcount", 'operator': "is"})
rule = etree.SubElement(root, "rule", {'field': "playcount", 'operator': "is"}) etree.SubElement(rule, 'value').text = "0"
etree.SubElement(rule, 'value').text = "0"
elif nodetype == "sets":
elif nodetype == "sets": etree.SubElement(root, 'order', {'direction': "ascending"}).text = "sorttitle"
etree.SubElement(root, 'order', {'direction': "ascending"}).text = "sorttitle" etree.SubElement(root, 'group').text = "sets"
etree.SubElement(root, 'group').text = "sets"
elif nodetype == "random":
elif nodetype == "random": etree.SubElement(root, 'order', {'direction': "ascending"}).text = "random"
etree.SubElement(root, 'order', {'direction': "ascending"}).text = "random" etree.SubElement(root, 'limit').text = limit
etree.SubElement(root, 'limit').text = limit
elif nodetype == "recommended":
elif nodetype == "recommended": etree.SubElement(root, 'order', {'direction': "descending"}).text = "rating"
etree.SubElement(root, 'order', {'direction': "descending"}).text = "rating" etree.SubElement(root, 'limit').text = limit
etree.SubElement(root, 'limit').text = limit rule = etree.SubElement(root, 'rule', {'field': "playcount", 'operator': "is"})
rule = etree.SubElement(root, 'rule', {'field': "playcount", 'operator': "is"}) etree.SubElement(rule, 'value').text = "0"
etree.SubElement(rule, 'value').text = "0" rule2 = etree.SubElement(root, 'rule',
rule2 = etree.SubElement(root, 'rule', attrib={'field': "rating", 'operator': "greaterthan"})
attrib={'field': "rating", 'operator': "greaterthan"}) etree.SubElement(rule2, 'value').text = "7"
etree.SubElement(rule2, 'value').text = "7"
elif nodetype == "recentepisodes":
elif nodetype == "recentepisodes": # Kodi Isengard, Jarvis
# Kodi Isengard, Jarvis etree.SubElement(root, 'order', {'direction': "descending"}).text = "dateadded"
etree.SubElement(root, 'order', {'direction': "descending"}).text = "dateadded" etree.SubElement(root, 'limit').text = limit
etree.SubElement(root, 'limit').text = limit rule = etree.SubElement(root, 'rule', {'field': "playcount", 'operator': "is"})
rule = etree.SubElement(root, 'rule', {'field': "playcount", 'operator': "is"}) etree.SubElement(rule, 'value').text = "0"
etree.SubElement(rule, 'value').text = "0"
elif nodetype == "inprogressepisodes":
elif nodetype == "inprogressepisodes": # Kodi Isengard, Jarvis
# Kodi Isengard, Jarvis etree.SubElement(root, 'limit').text = "25"
etree.SubElement(root, 'limit').text = "25" rule = etree.SubElement(root, 'rule',
rule = etree.SubElement(root, 'rule', attrib={'field': "inprogress", 'operator':"true"})
attrib={'field': "inprogress", 'operator':"true"})
try:
try: utils.indent(root)
utils.indent(root) except: pass
except: pass etree.ElementTree(root).write(nodeXML)
etree.ElementTree(root).write(nodeXML)
def singleNode(self, indexnumber, tagname, mediatype, itemtype):
def singleNode(self, indexnumber, tagname, mediatype, itemtype):
window = utils.window
window = utils.window
tagname = tagname.encode('utf-8')
tagname = tagname.encode('utf-8') cleantagname = utils.normalize_nodes(tagname)
cleantagname = utils.normalize_nodes(tagname) nodepath = xbmc.translatePath("special://profile/library/video/").decode('utf-8')
nodepath = xbmc.translatePath("special://profile/library/video/").decode('utf-8') nodeXML = "%semby_%s.xml" % (nodepath, cleantagname)
nodeXML = "%semby_%s.xml" % (nodepath, cleantagname) path = "library://video/emby_%s.xml" % cleantagname
path = "library://video/emby_%s.xml" % cleantagname windowpath = "ActivateWindow(Video,%s,return)" % path
windowpath = "ActivateWindow(Video,%s,return)" % path
# Create the video node directory
# Create the video node directory if not xbmcvfs.exists(nodepath):
if not xbmcvfs.exists(nodepath): # We need to copy over the default items
# We need to copy over the default items shutil.copytree(
shutil.copytree( src=xbmc.translatePath("special://xbmc/system/library/video").decode('utf-8'),
src=xbmc.translatePath("special://xbmc/system/library/video").decode('utf-8'), dst=xbmc.translatePath("special://profile/library/video").decode('utf-8'))
dst=xbmc.translatePath("special://profile/library/video").decode('utf-8')) xbmcvfs.exists(path)
xbmcvfs.exists(path)
labels = {
labels = {
'Favorite movies': 30180,
'Favorite movies': 30180, 'Favorite tvshows': 30181,
'Favorite tvshows': 30181, 'channels': 30173
'channels': 30173 }
} label = utils.language(labels[tagname])
label = utils.language(labels[tagname]) embynode = "Emby.nodes.%s" % indexnumber
embynode = "Emby.nodes.%s" % indexnumber window('%s.title' % embynode, value=label)
window('%s.title' % embynode, value=label) window('%s.path' % embynode, value=windowpath)
window('%s.path' % embynode, value=windowpath) window('%s.content' % embynode, value=path)
window('%s.content' % embynode, value=path) window('%s.type' % embynode, value=itemtype)
window('%s.type' % embynode, value=itemtype)
if xbmcvfs.exists(nodeXML):
if xbmcvfs.exists(nodeXML): # Don't recreate xml if already exists
# Don't recreate xml if already exists return
return
if itemtype == "channels":
if itemtype == "channels": root = self.commonRoot(order=1, label=label, tagname=tagname, roottype=2)
root = self.commonRoot(order=1, label=label, tagname=tagname, roottype=2) etree.SubElement(root, 'path').text = "plugin://plugin.video.emby/?id=0&mode=channels"
etree.SubElement(root, 'path').text = "plugin://plugin.video.emby/?id=0&mode=channels" else:
else: root = self.commonRoot(order=1, label=label, tagname=tagname)
root = self.commonRoot(order=1, label=label, tagname=tagname) etree.SubElement(root, 'order', {'direction': "ascending"}).text = "sorttitle"
etree.SubElement(root, 'order', {'direction': "ascending"}).text = "sorttitle"
etree.SubElement(root, 'content').text = mediatype
etree.SubElement(root, 'content').text = mediatype
try:
try: utils.indent(root)
utils.indent(root) except: pass
except: pass etree.ElementTree(root).write(nodeXML)
etree.ElementTree(root).write(nodeXML)
def clearProperties(self):
def clearProperties(self):
window = utils.window
window = utils.window
self.logMsg("Clearing nodes properties.", 1)
self.logMsg("Clearing nodes properties.", 1) embyprops = window('Emby.nodes.total')
embyprops = window('Emby.nodes.total') propnames = [
propnames = [
"index","path","title","content",
"index","path","title","content", "inprogress.content","inprogress.title",
"inprogress.content","inprogress.title", "inprogress.content","inprogress.path",
"inprogress.content","inprogress.path", "nextepisodes.title","nextepisodes.content",
"nextepisodes.title","nextepisodes.content", "nextepisodes.path","unwatched.title",
"nextepisodes.path","unwatched.title", "unwatched.content","unwatched.path",
"unwatched.content","unwatched.path", "recent.title","recent.content","recent.path",
"recent.title","recent.content","recent.path", "recentepisodes.title","recentepisodes.content",
"recentepisodes.title","recentepisodes.content", "recentepisodes.path","inprogressepisodes.title",
"recentepisodes.path","inprogressepisodes.title", "inprogressepisodes.content","inprogressepisodes.path"
"inprogressepisodes.content","inprogressepisodes.path" ]
]
if embyprops:
if embyprops: totalnodes = int(embyprops)
totalnodes = int(embyprops) for i in range(totalnodes):
for i in range(totalnodes): for prop in propnames:
for prop in propnames:
window('Emby.nodes.%s.%s' % (str(i), prop), clear=True) 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 -*- # -*- coding: utf-8 -*-
################################################################################################# #################################################################################################
import json import json
import threading import threading
import websocket import websocket
import xbmc import xbmc
import xbmcgui import xbmcgui
import clientinfo import clientinfo
import downloadutils import downloadutils
import librarysync import librarysync
import playlist import playlist
import userclient import userclient
import utils import utils
import logging import logging
logging.basicConfig() logging.basicConfig()
################################################################################################# #################################################################################################
class WebSocket_Client(threading.Thread): class WebSocket_Client(threading.Thread):
_shared_state = {} _shared_state = {}
client = None client = None
stopWebsocket = False stopWebsocket = False
def __init__(self): def __init__(self):
self.__dict__ = self._shared_state self.__dict__ = self._shared_state
self.monitor = xbmc.Monitor() self.monitor = xbmc.Monitor()
self.doUtils = downloadutils.DownloadUtils() self.doUtils = downloadutils.DownloadUtils()
self.clientInfo = clientinfo.ClientInfo() self.clientInfo = clientinfo.ClientInfo()
self.addonName = self.clientInfo.getAddonName() self.addonName = self.clientInfo.getAddonName()
self.deviceId = self.clientInfo.getDeviceId() self.deviceId = self.clientInfo.getDeviceId()
self.librarySync = librarysync.LibrarySync() self.librarySync = librarysync.LibrarySync()
threading.Thread.__init__(self) threading.Thread.__init__(self)
def logMsg(self, msg, lvl=1): def logMsg(self, msg, lvl=1):
self.className = self.__class__.__name__ self.className = self.__class__.__name__
utils.logMsg("%s %s" % (self.addonName, self.className), msg, lvl) utils.logMsg("%s %s" % (self.addonName, self.className), msg, lvl)
def sendProgressUpdate(self, data): def sendProgressUpdate(self, data):
log = self.logMsg self.logMsg("sendProgressUpdate", 2)
try:
log("sendProgressUpdate", 2) messageData = {
try:
messageData = { 'MessageType': "ReportPlaybackProgress",
'Data': data
'MessageType': "ReportPlaybackProgress", }
'Data': data messageString = json.dumps(messageData)
} self.client.send(messageString)
messageString = json.dumps(messageData) self.logMsg("Message data: %s" % messageString, 2)
self.client.send(messageString)
log("Message data: %s" % messageString, 2) except Exception as e:
self.logMsg("Exception: %s" % e, 1)
except Exception as e:
log("Exception: %s" % e, 1) def on_message(self, ws, message):
def on_message(self, ws, message): window = utils.window
lang = utils.language
log = self.logMsg
window = utils.window result = json.loads(message)
lang = utils.language messageType = result['MessageType']
data = result['Data']
result = json.loads(message)
messageType = result['MessageType'] if messageType not in ('SessionEnded'):
data = result['Data'] # Mute certain events
self.logMsg("Message: %s" % message, 1)
if messageType not in ('SessionEnded'):
# Mute certain events if messageType == "Play":
log("Message: %s" % message, 1) # A remote control play command has been sent from the server.
itemIds = data['ItemIds']
if messageType == "Play": command = data['PlayCommand']
# A remote control play command has been sent from the server.
itemIds = data['ItemIds'] pl = playlist.Playlist()
command = data['PlayCommand'] dialog = xbmcgui.Dialog()
pl = playlist.Playlist() if command == "PlayNow":
dialog = xbmcgui.Dialog() dialog.notification(
heading="Emby for Kodi",
if command == "PlayNow": message="%s %s" % (len(itemIds), lang(33004)),
dialog.notification( icon="special://home/addons/plugin.video.emby/icon.png",
heading="Emby for Kodi", sound=False)
message="%s %s" % (len(itemIds), lang(33004)), startat = data.get('StartPositionTicks', 0)
icon="special://home/addons/plugin.video.emby/icon.png", pl.playAll(itemIds, startat)
sound=False)
startat = data.get('StartPositionTicks', 0) elif command == "PlayNext":
pl.playAll(itemIds, startat) dialog.notification(
heading="Emby for Kodi",
elif command == "PlayNext": message="%s %s" % (len(itemIds), lang(33005)),
dialog.notification( icon="special://home/addons/plugin.video.emby/icon.png",
heading="Emby for Kodi", sound=False)
message="%s %s" % (len(itemIds), lang(33005)), newplaylist = pl.modifyPlaylist(itemIds)
icon="special://home/addons/plugin.video.emby/icon.png", player = xbmc.Player()
sound=False) if not player.isPlaying():
newplaylist = pl.modifyPlaylist(itemIds) # Only start the playlist if nothing is playing
player = xbmc.Player() player.play(newplaylist)
if not player.isPlaying():
# Only start the playlist if nothing is playing elif messageType == "Playstate":
player.play(newplaylist) # A remote control update playstate command has been sent from the server.
command = data['Command']
elif messageType == "Playstate": player = xbmc.Player()
# A remote control update playstate command has been sent from the server.
command = data['Command'] actions = {
player = xbmc.Player()
'Stop': player.stop,
actions = { 'Unpause': player.pause,
'Pause': player.pause,
'Stop': player.stop, 'NextTrack': player.playnext,
'Unpause': player.pause, 'PreviousTrack': player.playprevious,
'Pause': player.pause, 'Seek': player.seekTime
'NextTrack': player.playnext, }
'PreviousTrack': player.playprevious, action = actions[command]
'Seek': player.seekTime if command == "Seek":
} seekto = data['SeekPositionTicks']
action = actions[command] seektime = seekto / 10000000.0
if command == "Seek": action(seektime)
seekto = data['SeekPositionTicks'] self.logMsg("Seek to %s." % seektime, 1)
seektime = seekto / 10000000.0 else:
action(seektime) action()
log("Seek to %s." % seektime, 1) self.logMsg("Command: %s completed." % command, 1)
else:
action() window('emby_command', value="true")
log("Command: %s completed." % command, 1)
elif messageType == "UserDataChanged":
window('emby_command', value="true") # A user changed their personal rating for an item, or their playstate was updated
userdata_list = data['UserDataList']
elif messageType == "UserDataChanged": self.librarySync.triage_items("userdata", userdata_list)
# A user changed their personal rating for an item, or their playstate was updated
userdata_list = data['UserDataList'] elif messageType == "LibraryChanged":
self.librarySync.triage_items("userdata", userdata_list)
librarySync = self.librarySync
elif messageType == "LibraryChanged": processlist = {
librarySync = self.librarySync 'added': data['ItemsAdded'],
processlist = { 'update': data['ItemsUpdated'],
'remove': data['ItemsRemoved']
'added': data['ItemsAdded'], }
'update': data['ItemsUpdated'], for action in processlist:
'remove': data['ItemsRemoved'] librarySync.triage_items(action, processlist[action])
}
for action in processlist: elif messageType == "GeneralCommand":
librarySync.triage_items(action, processlist[action])
command = data['Name']
elif messageType == "GeneralCommand": arguments = data['Arguments']
command = data['Name'] if command in ('Mute', 'Unmute', 'SetVolume',
arguments = data['Arguments'] 'SetSubtitleStreamIndex', 'SetAudioStreamIndex'):
if command in ('Mute', 'Unmute', 'SetVolume', player = xbmc.Player()
'SetSubtitleStreamIndex', 'SetAudioStreamIndex'): # These commands need to be reported back
if command == "Mute":
player = xbmc.Player() xbmc.executebuiltin('Mute')
# These commands need to be reported back elif command == "Unmute":
if command == "Mute": xbmc.executebuiltin('Mute')
xbmc.executebuiltin('Mute') elif command == "SetVolume":
elif command == "Unmute": volume = arguments['Volume']
xbmc.executebuiltin('Mute') xbmc.executebuiltin('SetVolume(%s[,showvolumebar])' % volume)
elif command == "SetVolume": elif command == "SetAudioStreamIndex":
volume = arguments['Volume'] index = int(arguments['Index'])
xbmc.executebuiltin('SetVolume(%s[,showvolumebar])' % volume) player.setAudioStream(index - 1)
elif command == "SetAudioStreamIndex": elif command == "SetSubtitleStreamIndex":
index = int(arguments['Index']) embyindex = int(arguments['Index'])
player.setAudioStream(index - 1) currentFile = player.getPlayingFile()
elif command == "SetSubtitleStreamIndex":
embyindex = int(arguments['Index']) mapping = window('emby_%s.indexMapping' % currentFile)
currentFile = player.getPlayingFile() if mapping:
externalIndex = json.loads(mapping)
mapping = window('emby_%s.indexMapping' % currentFile) # If there's external subtitles added via playbackutils
if mapping: for index in externalIndex:
externalIndex = json.loads(mapping) if externalIndex[index] == embyindex:
# If there's external subtitles added via playbackutils player.setSubtitleStream(int(index))
for index in externalIndex: break
if externalIndex[index] == embyindex: else:
player.setSubtitleStream(int(index)) # User selected internal subtitles
break external = len(externalIndex)
else: audioTracks = len(player.getAvailableAudioStreams())
# User selected internal subtitles player.setSubtitleStream(external + embyindex - audioTracks - 1)
external = len(externalIndex) else:
audioTracks = len(player.getAvailableAudioStreams()) # Emby merges audio and subtitle index together
player.setSubtitleStream(external + embyindex - audioTracks - 1) audioTracks = len(player.getAvailableAudioStreams())
else: player.setSubtitleStream(index - audioTracks - 1)
# Emby merges audio and subtitle index together
audioTracks = len(player.getAvailableAudioStreams()) # Let service know
player.setSubtitleStream(index - audioTracks - 1) window('emby_command', value="true")
# Let service know elif command == "DisplayMessage":
window('emby_command', value="true")
header = arguments['Header']
elif command == "DisplayMessage": text = arguments['Text']
xbmcgui.Dialog().notification(
header = arguments['Header'] heading=header,
text = arguments['Text'] message=text,
xbmcgui.Dialog().notification( icon="special://home/addons/plugin.video.emby/icon.png",
heading=header, time=4000)
message=text,
icon="special://home/addons/plugin.video.emby/icon.png", elif command == "SendString":
time=4000)
string = arguments['String']
elif command == "SendString": text = {
string = arguments['String'] 'jsonrpc': "2.0",
text = { 'id': 0,
'method': "Input.SendText",
'jsonrpc': "2.0", 'params': {
'id': 0,
'method': "Input.SendText", 'text': "%s" % string,
'params': { 'done': False
}
'text': "%s" % string, }
'done': False result = xbmc.executeJSONRPC(json.dumps(text))
}
} else:
result = xbmc.executeJSONRPC(json.dumps(text)) builtin = {
else: 'ToggleFullscreen': 'Action(FullScreen)',
builtin = { 'ToggleOsdMenu': 'Action(OSD)',
'ToggleContextMenu': 'Action(ContextMenu)',
'ToggleFullscreen': 'Action(FullScreen)', 'MoveUp': 'Action(Up)',
'ToggleOsdMenu': 'Action(OSD)', 'MoveDown': 'Action(Down)',
'ToggleContextMenu': 'Action(ContextMenu)', 'MoveLeft': 'Action(Left)',
'MoveUp': 'Action(Up)', 'MoveRight': 'Action(Right)',
'MoveDown': 'Action(Down)', 'Select': 'Action(Select)',
'MoveLeft': 'Action(Left)', 'Back': 'Action(back)',
'MoveRight': 'Action(Right)', 'GoHome': 'ActivateWindow(Home)',
'Select': 'Action(Select)', 'PageUp': 'Action(PageUp)',
'Back': 'Action(back)', 'NextLetter': 'Action(NextLetter)',
'GoHome': 'ActivateWindow(Home)', 'GoToSearch': 'VideoLibrary.Search',
'PageUp': 'Action(PageUp)', 'GoToSettings': 'ActivateWindow(Settings)',
'NextLetter': 'Action(NextLetter)', 'PageDown': 'Action(PageDown)',
'GoToSearch': 'VideoLibrary.Search', 'PreviousLetter': 'Action(PrevLetter)',
'GoToSettings': 'ActivateWindow(Settings)', 'TakeScreenshot': 'TakeScreenshot',
'PageDown': 'Action(PageDown)', 'ToggleMute': 'Mute',
'PreviousLetter': 'Action(PrevLetter)', 'VolumeUp': 'Action(VolumeUp)',
'TakeScreenshot': 'TakeScreenshot', 'VolumeDown': 'Action(VolumeDown)',
'ToggleMute': 'Mute', }
'VolumeUp': 'Action(VolumeUp)', action = builtin.get(command)
'VolumeDown': 'Action(VolumeDown)', if action:
} xbmc.executebuiltin(action)
action = builtin.get(command)
if action: elif messageType == "ServerRestarting":
xbmc.executebuiltin(action) if utils.settings('supressRestartMsg') == "true":
xbmcgui.Dialog().notification(
elif messageType == "ServerRestarting": heading="Emby for Kodi",
if utils.settings('supressRestartMsg') == "true": message=lang(33006),
xbmcgui.Dialog().notification( icon="special://home/addons/plugin.video.emby/icon.png")
heading="Emby for Kodi",
message=lang(33006), elif messageType == "UserConfigurationUpdated":
icon="special://home/addons/plugin.video.emby/icon.png") # Update user data set in userclient
userclient.UserClient().userSettings = data
elif messageType == "UserConfigurationUpdated": self.librarySync.refresh_views = True
# Update user data set in userclient
userclient.UserClient().userSettings = data def on_close(self, ws):
self.librarySync.refresh_views = True self.logMsg("Closed.", 2)
def on_close(self, ws): def on_open(self, ws):
self.logMsg("Closed.", 2) self.doUtils.postCapabilities(self.deviceId)
def on_open(self, ws): def on_error(self, ws, error):
self.doUtils.postCapabilities(self.deviceId) if "10061" in str(error):
# Server is offline
def on_error(self, ws, error): pass
if "10061" in str(error): else:
# Server is offline self.logMsg("Error: %s" % error, 2)
pass
else: def run(self):
self.logMsg("Error: %s" % error, 2)
window = utils.window
def run(self): loglevel = int(window('emby_logLevel'))
# websocket.enableTrace(True)
log = self.logMsg
window = utils.window userId = window('emby_currUser')
monitor = self.monitor server = window('emby_server%s' % userId)
token = window('emby_accessToken%s' % userId)
loglevel = int(window('emby_logLevel')) # Get the appropriate prefix for the websocket
# websocket.enableTrace(True) if "https" in server:
server = server.replace('https', "wss")
userId = window('emby_currUser') else:
server = window('emby_server%s' % userId) server = server.replace('http', "ws")
token = window('emby_accessToken%s' % userId)
deviceId = self.deviceId websocket_url = "%s?api_key=%s&deviceId=%s" % (server, token, self.deviceId)
self.logMsg("websocket url: %s" % websocket_url, 1)
# Get the appropriate prefix for the websocket
if "https" in server: self.client = websocket.WebSocketApp(websocket_url,
server = server.replace('https', "wss") on_message=self.on_message,
else: on_error=self.on_error,
server = server.replace('http', "ws") on_close=self.on_close)
websocket_url = "%s?api_key=%s&deviceId=%s" % (server, token, deviceId) self.client.on_open = self.on_open
log("websocket url: %s" % websocket_url, 1) self.logMsg("----===## Starting WebSocketClient ##===----", 0)
self.client = websocket.WebSocketApp(websocket_url, while not self.monitor.abortRequested():
on_message=self.on_message,
on_error=self.on_error, self.client.run_forever(ping_interval=10)
on_close=self.on_close) if self.stopWebsocket:
break
self.client.on_open = self.on_open
log("----===## Starting WebSocketClient ##===----", 0) if self.monitor.waitForAbort(5):
# Abort was requested, exit
while not monitor.abortRequested(): break
self.client.run_forever(ping_interval=10) self.logMsg("##===---- WebSocketClient Stopped ----===##", 0)
if self.stopWebsocket:
break def stopClient(self):
if monitor.waitForAbort(5): self.stopWebsocket = True
# Abort was requested, exit self.client.close()
break
log("##===---- WebSocketClient Stopped ----===##", 0)
def stopClient(self):
self.stopWebsocket = True
self.client.close()
self.logMsg("Stopping thread.", 1) self.logMsg("Stopping thread.", 1)