commit
bec455e8f7
24 changed files with 12471 additions and 12835 deletions
315
contextmenu.py
315
contextmenu.py
|
@ -1,159 +1,158 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
#################################################################################################
|
||||
|
||||
import os
|
||||
import sys
|
||||
import urlparse
|
||||
|
||||
import xbmc
|
||||
import xbmcaddon
|
||||
import xbmcgui
|
||||
|
||||
addon_ = xbmcaddon.Addon(id='plugin.video.emby')
|
||||
addon_path = addon_.getAddonInfo('path').decode('utf-8')
|
||||
base_resource = xbmc.translatePath(os.path.join(addon_path, 'resources', 'lib')).decode('utf-8')
|
||||
sys.path.append(base_resource)
|
||||
|
||||
import artwork
|
||||
import utils
|
||||
import clientinfo
|
||||
import downloadutils
|
||||
import librarysync
|
||||
import read_embyserver as embyserver
|
||||
import embydb_functions as embydb
|
||||
import kodidb_functions as kodidb
|
||||
import musicutils as musicutils
|
||||
import api
|
||||
|
||||
def logMsg(msg, lvl=1):
|
||||
utils.logMsg("%s %s" % ("EMBY", "Contextmenu"), msg, lvl)
|
||||
|
||||
|
||||
#Kodi contextmenu item to configure the emby settings
|
||||
#for now used to set ratings but can later be used to sync individual items etc.
|
||||
if __name__ == '__main__':
|
||||
itemid = xbmc.getInfoLabel("ListItem.DBID").decode("utf-8")
|
||||
itemtype = xbmc.getInfoLabel("ListItem.DBTYPE").decode("utf-8")
|
||||
|
||||
emby = embyserver.Read_EmbyServer()
|
||||
|
||||
embyid = ""
|
||||
if not itemtype and xbmc.getCondVisibility("Container.Content(albums)"): itemtype = "album"
|
||||
if not itemtype and xbmc.getCondVisibility("Container.Content(artists)"): itemtype = "artist"
|
||||
if not itemtype and xbmc.getCondVisibility("Container.Content(songs)"): itemtype = "song"
|
||||
if not itemtype and xbmc.getCondVisibility("Container.Content(pictures)"): itemtype = "picture"
|
||||
|
||||
if (not itemid or itemid == "-1") and xbmc.getInfoLabel("ListItem.Property(embyid)"):
|
||||
embyid = xbmc.getInfoLabel("ListItem.Property(embyid)")
|
||||
else:
|
||||
embyconn = utils.kodiSQL('emby')
|
||||
embycursor = embyconn.cursor()
|
||||
emby_db = embydb.Embydb_Functions(embycursor)
|
||||
item = emby_db.getItem_byKodiId(itemid, itemtype)
|
||||
embycursor.close()
|
||||
if item: embyid = item[0]
|
||||
|
||||
logMsg("Contextmenu opened for embyid: %s - itemtype: %s" %(embyid,itemtype))
|
||||
|
||||
if embyid:
|
||||
item = emby.getItem(embyid)
|
||||
API = api.API(item)
|
||||
userdata = API.getUserData()
|
||||
likes = userdata['Likes']
|
||||
favourite = userdata['Favorite']
|
||||
|
||||
options=[]
|
||||
if likes == True:
|
||||
#clear like for the item
|
||||
options.append(utils.language(30402))
|
||||
if likes == False or likes == None:
|
||||
#Like the item
|
||||
options.append(utils.language(30403))
|
||||
if likes == True or likes == None:
|
||||
#Dislike the item
|
||||
options.append(utils.language(30404))
|
||||
if favourite == False:
|
||||
#Add to emby favourites
|
||||
options.append(utils.language(30405))
|
||||
if favourite == True:
|
||||
#Remove from emby favourites
|
||||
options.append(utils.language(30406))
|
||||
if itemtype == "song":
|
||||
#Set custom song rating
|
||||
options.append(utils.language(30407))
|
||||
|
||||
#delete item
|
||||
options.append(utils.language(30409))
|
||||
|
||||
#addon settings
|
||||
options.append(utils.language(30408))
|
||||
|
||||
#display select dialog and process results
|
||||
header = utils.language(30401)
|
||||
ret = xbmcgui.Dialog().select(header, options)
|
||||
if ret != -1:
|
||||
if options[ret] == utils.language(30402):
|
||||
emby.updateUserRating(embyid, deletelike=True)
|
||||
if options[ret] == utils.language(30403):
|
||||
emby.updateUserRating(embyid, like=True)
|
||||
if options[ret] == utils.language(30404):
|
||||
emby.updateUserRating(embyid, like=False)
|
||||
if options[ret] == utils.language(30405):
|
||||
emby.updateUserRating(embyid, favourite=True)
|
||||
if options[ret] == utils.language(30406):
|
||||
emby.updateUserRating(embyid, favourite=False)
|
||||
if options[ret] == utils.language(30407):
|
||||
kodiconn = utils.kodiSQL('music')
|
||||
kodicursor = kodiconn.cursor()
|
||||
query = ' '.join(("SELECT rating", "FROM song", "WHERE idSong = ?" ))
|
||||
kodicursor.execute(query, (itemid,))
|
||||
currentvalue = int(round(float(kodicursor.fetchone()[0]),0))
|
||||
newvalue = xbmcgui.Dialog().numeric(0, "Set custom song rating (0-5)", str(currentvalue))
|
||||
if newvalue:
|
||||
newvalue = int(newvalue)
|
||||
if newvalue > 5: newvalue = "5"
|
||||
if utils.settings('enableUpdateSongRating') == "true":
|
||||
musicutils.updateRatingToFile(newvalue, API.getFilePath())
|
||||
if utils.settings('enableExportSongRating') == "true":
|
||||
like, favourite, deletelike = musicutils.getEmbyRatingFromKodiRating(newvalue)
|
||||
emby.updateUserRating(embyid, like, favourite, deletelike)
|
||||
query = ' '.join(( "UPDATE song","SET rating = ?", "WHERE idSong = ?" ))
|
||||
kodicursor.execute(query, (newvalue,itemid,))
|
||||
kodiconn.commit()
|
||||
|
||||
if options[ret] == utils.language(30408):
|
||||
#Open addon settings
|
||||
xbmc.executebuiltin("Addon.OpenSettings(plugin.video.emby)")
|
||||
|
||||
if options[ret] == utils.language(30409):
|
||||
#delete item from the server
|
||||
delete = True
|
||||
if utils.settings('skipContextMenu') != "true":
|
||||
resp = xbmcgui.Dialog().yesno(
|
||||
heading="Confirm delete",
|
||||
line1=("Delete file from Emby Server? This will "
|
||||
"also delete the file(s) from disk!"))
|
||||
if not resp:
|
||||
logMsg("User skipped deletion for: %s." % embyid, 1)
|
||||
delete = False
|
||||
|
||||
if delete:
|
||||
import downloadutils
|
||||
doUtils = downloadutils.DownloadUtils()
|
||||
url = "{server}/emby/Items/%s?format=json" % embyid
|
||||
logMsg("Deleting request: %s" % embyid, 0)
|
||||
doUtils.downloadUrl(url, type="DELETE")
|
||||
|
||||
'''if utils.settings('skipContextMenu') != "true":
|
||||
if xbmcgui.Dialog().yesno(
|
||||
heading="Confirm delete",
|
||||
line1=("Delete file on Emby Server? This will "
|
||||
"also delete the file(s) from disk!")):
|
||||
import downloadutils
|
||||
doUtils = downloadutils.DownloadUtils()
|
||||
url = "{server}/emby/Items/%s?format=json" % embyid
|
||||
doUtils.downloadUrl(url, type="DELETE")'''
|
||||
|
||||
xbmc.sleep(500)
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#################################################################################################
|
||||
|
||||
import os
|
||||
import sys
|
||||
import urlparse
|
||||
|
||||
import xbmc
|
||||
import xbmcaddon
|
||||
import xbmcgui
|
||||
|
||||
addon_ = xbmcaddon.Addon(id='plugin.video.emby')
|
||||
addon_path = addon_.getAddonInfo('path').decode('utf-8')
|
||||
base_resource = xbmc.translatePath(os.path.join(addon_path, 'resources', 'lib')).decode('utf-8')
|
||||
sys.path.append(base_resource)
|
||||
|
||||
import artwork
|
||||
import utils
|
||||
import clientinfo
|
||||
import downloadutils
|
||||
import librarysync
|
||||
import read_embyserver as embyserver
|
||||
import embydb_functions as embydb
|
||||
import kodidb_functions as kodidb
|
||||
import musicutils as musicutils
|
||||
import api
|
||||
|
||||
def logMsg(msg, lvl=1):
|
||||
utils.logMsg("%s %s" % ("EMBY", "Contextmenu"), msg, lvl)
|
||||
|
||||
|
||||
#Kodi contextmenu item to configure the emby settings
|
||||
#for now used to set ratings but can later be used to sync individual items etc.
|
||||
if __name__ == '__main__':
|
||||
itemid = xbmc.getInfoLabel("ListItem.DBID").decode("utf-8")
|
||||
itemtype = xbmc.getInfoLabel("ListItem.DBTYPE").decode("utf-8")
|
||||
|
||||
emby = embyserver.Read_EmbyServer()
|
||||
|
||||
embyid = ""
|
||||
if not itemtype and xbmc.getCondVisibility("Container.Content(albums)"): itemtype = "album"
|
||||
if not itemtype and xbmc.getCondVisibility("Container.Content(artists)"): itemtype = "artist"
|
||||
if not itemtype and xbmc.getCondVisibility("Container.Content(songs)"): itemtype = "song"
|
||||
if not itemtype and xbmc.getCondVisibility("Container.Content(pictures)"): itemtype = "picture"
|
||||
|
||||
if (not itemid or itemid == "-1") and xbmc.getInfoLabel("ListItem.Property(embyid)"):
|
||||
embyid = xbmc.getInfoLabel("ListItem.Property(embyid)")
|
||||
else:
|
||||
embyconn = utils.kodiSQL('emby')
|
||||
embycursor = embyconn.cursor()
|
||||
emby_db = embydb.Embydb_Functions(embycursor)
|
||||
item = emby_db.getItem_byKodiId(itemid, itemtype)
|
||||
embycursor.close()
|
||||
if item: embyid = item[0]
|
||||
|
||||
logMsg("Contextmenu opened for embyid: %s - itemtype: %s" %(embyid,itemtype))
|
||||
|
||||
if embyid:
|
||||
item = emby.getItem(embyid)
|
||||
API = api.API(item)
|
||||
userdata = API.getUserData()
|
||||
likes = userdata['Likes']
|
||||
favourite = userdata['Favorite']
|
||||
|
||||
options=[]
|
||||
if likes == True:
|
||||
#clear like for the item
|
||||
options.append(utils.language(30402))
|
||||
if likes == False or likes == None:
|
||||
#Like the item
|
||||
options.append(utils.language(30403))
|
||||
if likes == True or likes == None:
|
||||
#Dislike the item
|
||||
options.append(utils.language(30404))
|
||||
if favourite == False:
|
||||
#Add to emby favourites
|
||||
options.append(utils.language(30405))
|
||||
if favourite == True:
|
||||
#Remove from emby favourites
|
||||
options.append(utils.language(30406))
|
||||
if itemtype == "song":
|
||||
#Set custom song rating
|
||||
options.append(utils.language(30407))
|
||||
|
||||
#delete item
|
||||
options.append(utils.language(30409))
|
||||
|
||||
#addon settings
|
||||
options.append(utils.language(30408))
|
||||
|
||||
#display select dialog and process results
|
||||
header = utils.language(30401)
|
||||
ret = xbmcgui.Dialog().select(header, options)
|
||||
if ret != -1:
|
||||
if options[ret] == utils.language(30402):
|
||||
emby.updateUserRating(embyid, deletelike=True)
|
||||
if options[ret] == utils.language(30403):
|
||||
emby.updateUserRating(embyid, like=True)
|
||||
if options[ret] == utils.language(30404):
|
||||
emby.updateUserRating(embyid, like=False)
|
||||
if options[ret] == utils.language(30405):
|
||||
emby.updateUserRating(embyid, favourite=True)
|
||||
if options[ret] == utils.language(30406):
|
||||
emby.updateUserRating(embyid, favourite=False)
|
||||
if options[ret] == utils.language(30407):
|
||||
kodiconn = utils.kodiSQL('music')
|
||||
kodicursor = kodiconn.cursor()
|
||||
query = ' '.join(("SELECT rating", "FROM song", "WHERE idSong = ?" ))
|
||||
kodicursor.execute(query, (itemid,))
|
||||
currentvalue = int(round(float(kodicursor.fetchone()[0]),0))
|
||||
newvalue = xbmcgui.Dialog().numeric(0, "Set custom song rating (0-5)", str(currentvalue))
|
||||
if newvalue:
|
||||
newvalue = int(newvalue)
|
||||
if newvalue > 5: newvalue = "5"
|
||||
if utils.settings('enableUpdateSongRating') == "true":
|
||||
musicutils.updateRatingToFile(newvalue, API.getFilePath())
|
||||
if utils.settings('enableExportSongRating') == "true":
|
||||
like, favourite, deletelike = musicutils.getEmbyRatingFromKodiRating(newvalue)
|
||||
emby.updateUserRating(embyid, like, favourite, deletelike)
|
||||
query = ' '.join(( "UPDATE song","SET rating = ?", "WHERE idSong = ?" ))
|
||||
kodicursor.execute(query, (newvalue,itemid,))
|
||||
kodiconn.commit()
|
||||
|
||||
if options[ret] == utils.language(30408):
|
||||
#Open addon settings
|
||||
xbmc.executebuiltin("Addon.OpenSettings(plugin.video.emby)")
|
||||
|
||||
if options[ret] == utils.language(30409):
|
||||
#delete item from the server
|
||||
delete = True
|
||||
if utils.settings('skipContextMenu') != "true":
|
||||
resp = xbmcgui.Dialog().yesno(
|
||||
heading="Confirm delete",
|
||||
line1=("Delete file from Emby Server? This will "
|
||||
"also delete the file(s) from disk!"))
|
||||
if not resp:
|
||||
logMsg("User skipped deletion for: %s." % embyid, 1)
|
||||
delete = False
|
||||
|
||||
if delete:
|
||||
import downloadutils
|
||||
doUtils = downloadutils.DownloadUtils()
|
||||
url = "{server}/emby/Items/%s?format=json" % embyid
|
||||
logMsg("Deleting request: %s" % embyid, 0)
|
||||
doUtils.downloadUrl(url, action_type="DELETE")
|
||||
|
||||
'''if utils.settings('skipContextMenu') != "true":
|
||||
if xbmcgui.Dialog().yesno(
|
||||
heading="Confirm delete",
|
||||
line1=("Delete file on Emby Server? This will "
|
||||
"also delete the file(s) from disk!")):
|
||||
import downloadutils
|
||||
doUtils = downloadutils.DownloadUtils()
|
||||
doUtils.downloadUrl("{server}/emby/Items/%s?format=json" % embyid, action_type="DELETE")'''
|
||||
|
||||
xbmc.sleep(500)
|
||||
xbmc.executebuiltin("Container.Update")
|
|
@ -323,7 +323,7 @@
|
|||
<string id="33020">Gathering tv shows from:</string>
|
||||
<string id="33021">Gathering:</string>
|
||||
<string id="33022">Detected the database needs to be recreated for this version of Emby for Kodi. Proceed?</string>
|
||||
<string id="33023">Emby for Kod may not work correctly until the database is reset.</string>
|
||||
<string id="33023">Emby for Kodi may not work correctly until the database is reset.</string>
|
||||
<string id="33024">Cancelling the database syncing process. The current Kodi version is unsupported.</string>
|
||||
<string id="33025">completed in:</string>
|
||||
<string id="33026">Comparing movies from:</string>
|
||||
|
|
|
@ -37,7 +37,7 @@ class API():
|
|||
|
||||
try:
|
||||
userdata = self.item['UserData']
|
||||
|
||||
|
||||
except KeyError: # No userdata found.
|
||||
pass
|
||||
|
||||
|
@ -57,7 +57,7 @@ class API():
|
|||
lastPlayedDate = userdata.get('LastPlayedDate')
|
||||
if lastPlayedDate:
|
||||
lastPlayedDate = lastPlayedDate.split('.')[0].replace('T', " ")
|
||||
|
||||
|
||||
if userdata['Played']:
|
||||
# Playcount is tied to the watch status
|
||||
played = True
|
||||
|
@ -91,10 +91,10 @@ class API():
|
|||
|
||||
try:
|
||||
people = self.item['People']
|
||||
|
||||
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
|
||||
else:
|
||||
for person in people:
|
||||
|
||||
|
@ -116,17 +116,16 @@ class API():
|
|||
}
|
||||
|
||||
def getMediaStreams(self):
|
||||
item = self.item
|
||||
videotracks = []
|
||||
audiotracks = []
|
||||
subtitlelanguages = []
|
||||
|
||||
try:
|
||||
media_streams = item['MediaSources'][0]['MediaStreams']
|
||||
media_streams = self.item['MediaSources'][0]['MediaStreams']
|
||||
|
||||
except KeyError:
|
||||
if not item.get("MediaStreams"): return None
|
||||
media_streams = item['MediaStreams']
|
||||
if not self.item.get("MediaStreams"): return None
|
||||
media_streams = self.item['MediaStreams']
|
||||
|
||||
for media_stream in media_streams:
|
||||
# Sort through Video, Audio, Subtitle
|
||||
|
@ -141,12 +140,12 @@ class API():
|
|||
'codec': codec,
|
||||
'height': media_stream.get('Height'),
|
||||
'width': media_stream.get('Width'),
|
||||
'video3DFormat': item.get('Video3DFormat'),
|
||||
'video3DFormat': self.item.get('Video3DFormat'),
|
||||
'aspect': 1.85
|
||||
}
|
||||
|
||||
try:
|
||||
container = item['MediaSources'][0]['Container'].lower()
|
||||
container = self.item['MediaSources'][0]['Container'].lower()
|
||||
except:
|
||||
container = ""
|
||||
|
||||
|
@ -161,16 +160,16 @@ class API():
|
|||
track['codec'] = "avc1"
|
||||
|
||||
# Aspect ratio
|
||||
if item.get('AspectRatio'):
|
||||
if self.item.get('AspectRatio'):
|
||||
# Metadata AR
|
||||
aspect = item['AspectRatio']
|
||||
aspect = self.item['AspectRatio']
|
||||
else: # File AR
|
||||
aspect = media_stream.get('AspectRatio', "0")
|
||||
|
||||
try:
|
||||
aspectwidth, aspectheight = aspect.split(':')
|
||||
track['aspect'] = round(float(aspectwidth) / float(aspectheight), 6)
|
||||
|
||||
|
||||
except (ValueError, ZeroDivisionError):
|
||||
width = track.get('width')
|
||||
height = track.get('height')
|
||||
|
@ -179,16 +178,16 @@ class API():
|
|||
track['aspect'] = round(float(width / height), 6)
|
||||
else:
|
||||
track['aspect'] = 1.85
|
||||
|
||||
if item.get("RunTimeTicks"):
|
||||
track['duration'] = item.get("RunTimeTicks") / 10000000.0
|
||||
|
||||
|
||||
if self.item.get("RunTimeTicks"):
|
||||
track['duration'] = self.item.get("RunTimeTicks") / 10000000.0
|
||||
|
||||
videotracks.append(track)
|
||||
|
||||
elif stream_type == "Audio":
|
||||
# Codec, Channels, language
|
||||
track = {
|
||||
|
||||
|
||||
'codec': codec,
|
||||
'channels': media_stream.get('Channels'),
|
||||
'language': media_stream.get('Language')
|
||||
|
@ -205,18 +204,17 @@ class API():
|
|||
|
||||
return {
|
||||
|
||||
'video': videotracks,
|
||||
'video': videotracks,
|
||||
'audio': audiotracks,
|
||||
'subtitle': subtitlelanguages
|
||||
}
|
||||
|
||||
def getRuntime(self):
|
||||
item = self.item
|
||||
try:
|
||||
runtime = item['RunTimeTicks'] / 10000000.0
|
||||
|
||||
runtime = self.item['RunTimeTicks'] / 10000000.0
|
||||
|
||||
except KeyError:
|
||||
runtime = item.get('CumulativeRunTimeTicks', 0) / 10000000.0
|
||||
runtime = self.item.get('CumulativeRunTimeTicks', 0) / 10000000.0
|
||||
|
||||
return runtime
|
||||
|
||||
|
@ -234,20 +232,19 @@ class API():
|
|||
|
||||
def getStudios(self):
|
||||
# Process Studios
|
||||
item = self.item
|
||||
studios = []
|
||||
|
||||
try:
|
||||
studio = item['SeriesStudio']
|
||||
studio = self.item['SeriesStudio']
|
||||
studios.append(self.verifyStudio(studio))
|
||||
|
||||
|
||||
except KeyError:
|
||||
studioList = item['Studios']
|
||||
studioList = self.item['Studios']
|
||||
for studio in studioList:
|
||||
|
||||
name = studio['Name']
|
||||
studios.append(self.verifyStudio(name))
|
||||
|
||||
|
||||
return studios
|
||||
|
||||
def verifyStudio(self, studioName):
|
||||
|
@ -265,12 +262,11 @@ class API():
|
|||
|
||||
def getChecksum(self):
|
||||
# Use the etags checksum and userdata
|
||||
item = self.item
|
||||
userdata = item['UserData']
|
||||
userdata = self.item['UserData']
|
||||
|
||||
checksum = "%s%s%s%s%s%s%s" % (
|
||||
|
||||
item['Etag'],
|
||||
|
||||
self.item['Etag'],
|
||||
userdata['Played'],
|
||||
userdata['IsFavorite'],
|
||||
userdata.get('Likes',''),
|
||||
|
@ -282,9 +278,8 @@ class API():
|
|||
return checksum
|
||||
|
||||
def getGenres(self):
|
||||
item = self.item
|
||||
all_genres = ""
|
||||
genres = item.get('Genres', item.get('SeriesGenres'))
|
||||
genres = self.item.get('Genres', self.item.get('SeriesGenres'))
|
||||
|
||||
if genres:
|
||||
all_genres = " / ".join(genres)
|
||||
|
@ -344,7 +339,7 @@ class API():
|
|||
def getMpaa(self):
|
||||
# Convert more complex cases
|
||||
mpaa = self.item.get('OfficialRating', "")
|
||||
|
||||
|
||||
if mpaa in ("NR", "UR"):
|
||||
# Kodi seems to not like NR, but will accept Not Rated
|
||||
mpaa = "Not Rated"
|
||||
|
@ -362,9 +357,8 @@ class API():
|
|||
|
||||
def getFilePath(self):
|
||||
|
||||
item = self.item
|
||||
try:
|
||||
filepath = item['Path']
|
||||
filepath = self.item['Path']
|
||||
|
||||
except KeyError:
|
||||
filepath = ""
|
||||
|
@ -375,17 +369,16 @@ class API():
|
|||
filepath = filepath.replace("\\\\", "smb://")
|
||||
filepath = filepath.replace("\\", "/")
|
||||
|
||||
if item.get('VideoType'):
|
||||
videotype = item['VideoType']
|
||||
if self.item.get('VideoType'):
|
||||
videotype = self.item['VideoType']
|
||||
# Specific format modification
|
||||
if 'Dvd'in videotype:
|
||||
filepath = "%s/VIDEO_TS/VIDEO_TS.IFO" % filepath
|
||||
elif 'BluRay' in videotype:
|
||||
filepath = "%s/BDMV/index.bdmv" % filepath
|
||||
|
||||
|
||||
if "\\" in filepath:
|
||||
# Local path scenario, with special videotype
|
||||
filepath = filepath.replace("/", "\\")
|
||||
|
||||
return filepath
|
||||
|
File diff suppressed because it is too large
Load diff
|
@ -21,7 +21,7 @@ requests.packages.urllib3.disable_warnings(InsecurePlatformWarning)
|
|||
|
||||
|
||||
class ConnectUtils():
|
||||
|
||||
|
||||
# Borg - multiple instances, shared state
|
||||
_shared_state = {}
|
||||
clientInfo = clientinfo.ClientInfo()
|
||||
|
@ -60,8 +60,6 @@ class ConnectUtils():
|
|||
|
||||
def startSession(self):
|
||||
|
||||
log = self.logMsg
|
||||
|
||||
self.deviceId = self.clientInfo.getDeviceId()
|
||||
|
||||
# User is identified from this point
|
||||
|
@ -75,8 +73,8 @@ class ConnectUtils():
|
|||
if self.sslclient is not None:
|
||||
verify = self.sslclient
|
||||
except:
|
||||
log("Could not load SSL settings.", 1)
|
||||
|
||||
self.logMsg("Could not load SSL settings.", 1)
|
||||
|
||||
# Start session
|
||||
self.c = requests.Session()
|
||||
self.c.headers = header
|
||||
|
@ -85,7 +83,7 @@ class ConnectUtils():
|
|||
self.c.mount("http://", requests.adapters.HTTPAdapter(max_retries=1))
|
||||
self.c.mount("https://", requests.adapters.HTTPAdapter(max_retries=1))
|
||||
|
||||
log("Requests session started on: %s" % self.server, 1)
|
||||
self.logMsg("Requests session started on: %s" % self.server, 1)
|
||||
|
||||
def stopSession(self):
|
||||
try:
|
||||
|
@ -95,8 +93,7 @@ class ConnectUtils():
|
|||
|
||||
def getHeader(self, authenticate=True):
|
||||
|
||||
clientInfo = self.clientInfo
|
||||
version = clientInfo.getVersion()
|
||||
version = self.clientInfo.getVersion()
|
||||
|
||||
if not authenticate:
|
||||
# If user is not authenticated
|
||||
|
@ -105,9 +102,9 @@ class ConnectUtils():
|
|||
'X-Application': "Kodi/%s" % version,
|
||||
'Content-type': 'application/x-www-form-urlencoded; charset=UTF-8',
|
||||
'Accept': "application/json"
|
||||
}
|
||||
}
|
||||
self.logMsg("Header: %s" % header, 1)
|
||||
|
||||
|
||||
else:
|
||||
token = self.token
|
||||
# Attached to the requests session
|
||||
|
@ -117,18 +114,17 @@ class ConnectUtils():
|
|||
'Accept': "application/json",
|
||||
'X-Application': "Kodi/%s" % version,
|
||||
'X-Connect-UserToken': token
|
||||
}
|
||||
}
|
||||
self.logMsg("Header: %s" % header, 1)
|
||||
|
||||
|
||||
return header
|
||||
|
||||
def doUrl(self, url, data=None, postBody=None, rtype="GET",
|
||||
parameters=None, authenticate=True, timeout=None):
|
||||
|
||||
log = self.logMsg
|
||||
window = utils.window
|
||||
|
||||
log("=== ENTER connectUrl ===", 2)
|
||||
self.logMsg("=== ENTER connectUrl ===", 2)
|
||||
default_link = ""
|
||||
if timeout is None:
|
||||
timeout = self.timeout
|
||||
|
@ -137,7 +133,7 @@ class ConnectUtils():
|
|||
try:
|
||||
# If connect user is authenticated
|
||||
if authenticate:
|
||||
try:
|
||||
try:
|
||||
c = self.c
|
||||
# Replace for the real values
|
||||
url = url.replace("{server}", self.server)
|
||||
|
@ -167,7 +163,7 @@ class ConnectUtils():
|
|||
verifyssl = self.sslclient
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
|
||||
# Prepare request
|
||||
if rtype == "GET":
|
||||
r = requests.get(url,
|
||||
|
@ -195,7 +191,7 @@ class ConnectUtils():
|
|||
verifyssl = self.sslclient
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
|
||||
# Prepare request
|
||||
if rtype == "GET":
|
||||
r = requests.get(url,
|
||||
|
@ -213,28 +209,28 @@ class ConnectUtils():
|
|||
verify=verifyssl)
|
||||
|
||||
##### THE RESPONSE #####
|
||||
log(r.url, 1)
|
||||
log(r, 1)
|
||||
self.logMsg(r.url, 1)
|
||||
self.logMsg(r, 1)
|
||||
|
||||
if r.status_code == 204:
|
||||
# No body in the response
|
||||
log("====== 204 Success ======", 1)
|
||||
self.logMsg("====== 204 Success ======", 1)
|
||||
|
||||
elif r.status_code == requests.codes.ok:
|
||||
|
||||
try:
|
||||
|
||||
try:
|
||||
# UNICODE - JSON object
|
||||
r = r.json()
|
||||
log("====== 200 Success ======", 1)
|
||||
log("Response: %s" % r, 1)
|
||||
self.logMsg("====== 200 Success ======", 1)
|
||||
self.logMsg("Response: %s" % r, 1)
|
||||
return r
|
||||
|
||||
except:
|
||||
if r.headers.get('content-type') != "text/html":
|
||||
log("Unable to convert the response for: %s" % url, 1)
|
||||
self.logMsg("Unable to convert the response for: %s" % url, 1)
|
||||
else:
|
||||
r.raise_for_status()
|
||||
|
||||
|
||||
##### EXCEPTIONS #####
|
||||
|
||||
except requests.exceptions.ConnectionError as e:
|
||||
|
@ -242,8 +238,8 @@ class ConnectUtils():
|
|||
pass
|
||||
|
||||
except requests.exceptions.ConnectTimeout as e:
|
||||
log("Server timeout at: %s" % url, 0)
|
||||
log(e, 1)
|
||||
self.logMsg("Server timeout at: %s" % url, 0)
|
||||
self.logMsg(e, 1)
|
||||
|
||||
except requests.exceptions.HTTPError as e:
|
||||
|
||||
|
@ -259,11 +255,11 @@ class ConnectUtils():
|
|||
pass
|
||||
|
||||
except requests.exceptions.SSLError as e:
|
||||
log("Invalid SSL certificate for: %s" % url, 0)
|
||||
log(e, 1)
|
||||
self.logMsg("Invalid SSL certificate for: %s" % url, 0)
|
||||
self.logMsg(e, 1)
|
||||
|
||||
except requests.exceptions.RequestException as e:
|
||||
log("Unknown error connecting to: %s" % url, 0)
|
||||
log(e, 1)
|
||||
self.logMsg("Unknown error connecting to: %s" % url, 0)
|
||||
self.logMsg(e, 1)
|
||||
|
||||
return default_link
|
||||
|
|
|
@ -23,7 +23,7 @@ requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
|
|||
|
||||
|
||||
class DownloadUtils():
|
||||
|
||||
|
||||
# Borg - multiple instances, shared state
|
||||
_shared_state = {}
|
||||
clientInfo = clientinfo.ClientInfo()
|
||||
|
@ -77,11 +77,11 @@ class DownloadUtils():
|
|||
# Post settings to session
|
||||
url = "{server}/emby/Sessions/Capabilities/Full?format=json"
|
||||
data = {
|
||||
|
||||
|
||||
'PlayableMediaTypes': "Audio,Video",
|
||||
'SupportsMediaControl': True,
|
||||
'SupportedCommands': (
|
||||
|
||||
|
||||
"MoveUp,MoveDown,MoveLeft,MoveRight,Select,"
|
||||
"Back,ToggleContextMenu,ToggleFullscreen,ToggleOsdMenu,"
|
||||
"GoHome,PageUp,NextLetter,GoToSearch,"
|
||||
|
@ -97,7 +97,7 @@ class DownloadUtils():
|
|||
self.logMsg("Capabilities URL: %s" % url, 2)
|
||||
self.logMsg("Postdata: %s" % data, 2)
|
||||
|
||||
self.downloadUrl(url, postBody=data, type="POST")
|
||||
self.downloadUrl(url, postBody=data, action_type="POST")
|
||||
self.logMsg("Posted capabilities to %s" % self.server, 2)
|
||||
|
||||
# Attempt at getting sessionId
|
||||
|
@ -105,19 +105,19 @@ class DownloadUtils():
|
|||
result = self.downloadUrl(url)
|
||||
try:
|
||||
sessionId = result[0]['Id']
|
||||
|
||||
|
||||
except (KeyError, TypeError):
|
||||
self.logMsg("Failed to retrieve sessionId.", 1)
|
||||
|
||||
|
||||
else:
|
||||
self.logMsg("Session: %s" % result, 2)
|
||||
self.logMsg("SessionId: %s" % sessionId, 1)
|
||||
utils.window('emby_sessionId', value=sessionId)
|
||||
|
||||
|
||||
# Post any permanent additional users
|
||||
additionalUsers = utils.settings('additionalUsers')
|
||||
if additionalUsers:
|
||||
|
||||
|
||||
additionalUsers = additionalUsers.split(',')
|
||||
self.logMsg(
|
||||
"List of permanent users added to the session: %s"
|
||||
|
@ -140,13 +140,11 @@ class DownloadUtils():
|
|||
"{server}/emby/Sessions/%s/Users/%s?format=json"
|
||||
% (sessionId, userId)
|
||||
)
|
||||
self.downloadUrl(url, postBody={}, type="POST")
|
||||
self.downloadUrl(url, postBody={}, action_type="POST")
|
||||
|
||||
|
||||
def startSession(self):
|
||||
|
||||
log = self.logMsg
|
||||
|
||||
self.deviceId = self.clientInfo.getDeviceId()
|
||||
|
||||
# User is identified from this point
|
||||
|
@ -160,8 +158,8 @@ class DownloadUtils():
|
|||
if self.sslclient is not None:
|
||||
verify = self.sslclient
|
||||
except:
|
||||
log("Could not load SSL settings.", 1)
|
||||
|
||||
self.logMsg("Could not load SSL settings.", 1)
|
||||
|
||||
# Start session
|
||||
self.s = requests.Session()
|
||||
self.s.headers = header
|
||||
|
@ -170,7 +168,7 @@ class DownloadUtils():
|
|||
self.s.mount("http://", requests.adapters.HTTPAdapter(max_retries=1))
|
||||
self.s.mount("https://", requests.adapters.HTTPAdapter(max_retries=1))
|
||||
|
||||
log("Requests session started on: %s" % self.server, 1)
|
||||
self.logMsg("Requests session started on: %s" % self.server, 1)
|
||||
|
||||
def stopSession(self):
|
||||
try:
|
||||
|
@ -180,12 +178,10 @@ class DownloadUtils():
|
|||
|
||||
def getHeader(self, authenticate=True):
|
||||
|
||||
clientInfo = self.clientInfo
|
||||
|
||||
deviceName = clientInfo.getDeviceName()
|
||||
deviceName = self.clientInfo.getDeviceName()
|
||||
deviceName = utils.normalize_string(deviceName.encode('utf-8'))
|
||||
deviceId = clientInfo.getDeviceId()
|
||||
version = clientInfo.getVersion()
|
||||
deviceId = self.clientInfo.getDeviceId()
|
||||
version = self.clientInfo.getVersion()
|
||||
|
||||
if not authenticate:
|
||||
# If user is not authenticated
|
||||
|
@ -198,9 +194,9 @@ class DownloadUtils():
|
|||
'Accept-encoding': 'gzip',
|
||||
'Accept-Charset': 'UTF-8,*',
|
||||
'Authorization': auth
|
||||
}
|
||||
}
|
||||
self.logMsg("Header: %s" % header, 2)
|
||||
|
||||
|
||||
else:
|
||||
userId = self.userId
|
||||
token = self.token
|
||||
|
@ -215,36 +211,35 @@ class DownloadUtils():
|
|||
'Accept-Charset': 'UTF-8,*',
|
||||
'Authorization': auth,
|
||||
'X-MediaBrowser-Token': token
|
||||
}
|
||||
}
|
||||
self.logMsg("Header: %s" % header, 2)
|
||||
|
||||
|
||||
return header
|
||||
|
||||
def downloadUrl(self, url, postBody=None, type="GET", parameters=None, authenticate=True):
|
||||
|
||||
def downloadUrl(self, url, postBody=None, action_type="GET", parameters=None, authenticate=True):
|
||||
|
||||
self.logMsg("=== ENTER downloadUrl ===", 2)
|
||||
|
||||
timeout = self.timeout
|
||||
default_link = ""
|
||||
|
||||
try:
|
||||
# If user is authenticated
|
||||
if (authenticate):
|
||||
# Get requests session
|
||||
try:
|
||||
try:
|
||||
s = self.s
|
||||
# Replace for the real values
|
||||
url = url.replace("{server}", self.server)
|
||||
url = url.replace("{UserId}", self.userId)
|
||||
|
||||
# Prepare request
|
||||
if type == "GET":
|
||||
r = s.get(url, json=postBody, params=parameters, timeout=timeout)
|
||||
elif type == "POST":
|
||||
r = s.post(url, json=postBody, timeout=timeout)
|
||||
elif type == "DELETE":
|
||||
r = s.delete(url, json=postBody, timeout=timeout)
|
||||
|
||||
if action_type == "GET":
|
||||
r = s.get(url, json=postBody, params=parameters, timeout=self.timeout)
|
||||
elif action_type == "POST":
|
||||
r = s.post(url, json=postBody, timeout=self.timeout)
|
||||
elif action_type == "DELETE":
|
||||
r = s.delete(url, json=postBody, timeout=self.timeout)
|
||||
|
||||
except AttributeError:
|
||||
# request session does not exists
|
||||
# Get user information
|
||||
|
@ -266,26 +261,26 @@ class DownloadUtils():
|
|||
url = url.replace("{UserId}", self.userId)
|
||||
|
||||
# Prepare request
|
||||
if type == "GET":
|
||||
if action_type == "GET":
|
||||
r = requests.get(url,
|
||||
json=postBody,
|
||||
params=parameters,
|
||||
headers=header,
|
||||
timeout=timeout,
|
||||
timeout=self.timeout,
|
||||
verify=verifyssl)
|
||||
|
||||
elif type == "POST":
|
||||
elif action_type == "POST":
|
||||
r = requests.post(url,
|
||||
json=postBody,
|
||||
headers=header,
|
||||
timeout=timeout,
|
||||
timeout=self.timeout,
|
||||
verify=verifyssl)
|
||||
|
||||
elif type == "DELETE":
|
||||
elif action_type == "DELETE":
|
||||
r = requests.delete(url,
|
||||
json=postBody,
|
||||
headers=header,
|
||||
timeout=timeout,
|
||||
timeout=self.timeout,
|
||||
verify=verifyssl)
|
||||
|
||||
# If user is not authenticated
|
||||
|
@ -301,23 +296,23 @@ class DownloadUtils():
|
|||
verifyssl = self.sslclient
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
|
||||
# Prepare request
|
||||
if type == "GET":
|
||||
if action_type == "GET":
|
||||
r = requests.get(url,
|
||||
json=postBody,
|
||||
params=parameters,
|
||||
headers=header,
|
||||
timeout=timeout,
|
||||
timeout=self.timeout,
|
||||
verify=verifyssl)
|
||||
|
||||
elif type == "POST":
|
||||
elif action_type == "POST":
|
||||
r = requests.post(url,
|
||||
json=postBody,
|
||||
headers=header,
|
||||
timeout=timeout,
|
||||
timeout=self.timeout,
|
||||
verify=verifyssl)
|
||||
|
||||
|
||||
##### THE RESPONSE #####
|
||||
self.logMsg(r.url, 2)
|
||||
if r.status_code == 204:
|
||||
|
@ -325,8 +320,8 @@ class DownloadUtils():
|
|||
self.logMsg("====== 204 Success ======", 2)
|
||||
|
||||
elif r.status_code == requests.codes.ok:
|
||||
|
||||
try:
|
||||
|
||||
try:
|
||||
# UNICODE - JSON object
|
||||
r = r.json()
|
||||
self.logMsg("====== 200 Success ======", 2)
|
||||
|
@ -338,7 +333,7 @@ class DownloadUtils():
|
|||
self.logMsg("Unable to convert the response for: %s" % url, 1)
|
||||
else:
|
||||
r.raise_for_status()
|
||||
|
||||
|
||||
##### EXCEPTIONS #####
|
||||
|
||||
except requests.exceptions.ConnectionError as e:
|
||||
|
@ -369,7 +364,7 @@ class DownloadUtils():
|
|||
icon=xbmcgui.NOTIFICATION_ERROR,
|
||||
time=5000)
|
||||
return False
|
||||
|
||||
|
||||
elif r.headers['X-Application-Error-Code'] == "UnauthorizedAccessException":
|
||||
# User tried to do something his emby account doesn't allow
|
||||
pass
|
||||
|
|
|
@ -1,325 +1,292 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
#################################################################################################
|
||||
|
||||
import utils
|
||||
import clientinfo
|
||||
|
||||
#################################################################################################
|
||||
|
||||
|
||||
class Embydb_Functions():
|
||||
|
||||
|
||||
def __init__(self, embycursor):
|
||||
|
||||
self.embycursor = embycursor
|
||||
|
||||
self.clientInfo = clientinfo.ClientInfo()
|
||||
self.addonName = self.clientInfo.getAddonName()
|
||||
|
||||
def logMsg(self, msg, lvl=1):
|
||||
|
||||
className = self.__class__.__name__
|
||||
utils.logMsg("%s %s" % (self.addonName, className), msg, lvl)
|
||||
|
||||
def getViews(self):
|
||||
|
||||
embycursor = self.embycursor
|
||||
views = []
|
||||
|
||||
query = ' '.join((
|
||||
|
||||
"SELECT view_id",
|
||||
"FROM view"
|
||||
))
|
||||
embycursor.execute(query)
|
||||
rows = embycursor.fetchall()
|
||||
for row in rows:
|
||||
views.append(row[0])
|
||||
|
||||
return views
|
||||
|
||||
def getView_byId(self, viewid):
|
||||
|
||||
embycursor = self.embycursor
|
||||
|
||||
query = ' '.join((
|
||||
|
||||
"SELECT view_name, media_type, kodi_tagid",
|
||||
"FROM view",
|
||||
"WHERE view_id = ?"
|
||||
))
|
||||
embycursor.execute(query, (viewid,))
|
||||
view = embycursor.fetchone()
|
||||
|
||||
return view
|
||||
|
||||
def getView_byType(self, mediatype):
|
||||
|
||||
embycursor = self.embycursor
|
||||
views = []
|
||||
|
||||
query = ' '.join((
|
||||
|
||||
"SELECT view_id, view_name",
|
||||
"FROM view",
|
||||
"WHERE media_type = ?"
|
||||
))
|
||||
embycursor.execute(query, (mediatype,))
|
||||
rows = embycursor.fetchall()
|
||||
for row in rows:
|
||||
views.append({
|
||||
|
||||
'id': row[0],
|
||||
'name': row[1]
|
||||
})
|
||||
|
||||
return views
|
||||
|
||||
def getView_byName(self, tagname):
|
||||
|
||||
embycursor = self.embycursor
|
||||
|
||||
query = ' '.join((
|
||||
|
||||
"SELECT view_id",
|
||||
"FROM view",
|
||||
"WHERE view_name = ?"
|
||||
))
|
||||
embycursor.execute(query, (tagname,))
|
||||
try:
|
||||
view = embycursor.fetchone()[0]
|
||||
|
||||
except TypeError:
|
||||
view = None
|
||||
|
||||
return view
|
||||
|
||||
def addView(self, embyid, name, mediatype, tagid):
|
||||
|
||||
query = (
|
||||
'''
|
||||
INSERT INTO view(
|
||||
view_id, view_name, media_type, kodi_tagid)
|
||||
|
||||
VALUES (?, ?, ?, ?)
|
||||
'''
|
||||
)
|
||||
self.embycursor.execute(query, (embyid, name, mediatype, tagid))
|
||||
|
||||
def updateView(self, name, tagid, mediafolderid):
|
||||
|
||||
query = ' '.join((
|
||||
|
||||
"UPDATE view",
|
||||
"SET view_name = ?, kodi_tagid = ?",
|
||||
"WHERE view_id = ?"
|
||||
))
|
||||
self.embycursor.execute(query, (name, tagid, mediafolderid))
|
||||
|
||||
def removeView(self, viewid):
|
||||
|
||||
query = ' '.join((
|
||||
|
||||
"DELETE FROM view",
|
||||
"WHERE view_id = ?"
|
||||
))
|
||||
self.embycursor.execute(query, (viewid,))
|
||||
|
||||
def getItem_byId(self, embyid):
|
||||
|
||||
embycursor = self.embycursor
|
||||
|
||||
query = ' '.join((
|
||||
|
||||
"SELECT kodi_id, kodi_fileid, kodi_pathid, parent_id, media_type, emby_type",
|
||||
"FROM emby",
|
||||
"WHERE emby_id = ?"
|
||||
))
|
||||
try:
|
||||
embycursor.execute(query, (embyid,))
|
||||
item = embycursor.fetchone()
|
||||
return item
|
||||
except: return None
|
||||
|
||||
def getItem_byWildId(self, embyid):
|
||||
|
||||
embycursor = self.embycursor
|
||||
|
||||
query = ' '.join((
|
||||
|
||||
"SELECT kodi_id, media_type",
|
||||
"FROM emby",
|
||||
"WHERE emby_id LIKE ?"
|
||||
))
|
||||
embycursor.execute(query, (embyid+"%",))
|
||||
items = embycursor.fetchall()
|
||||
|
||||
return items
|
||||
|
||||
def getItem_byView(self, mediafolderid):
|
||||
|
||||
embycursor = self.embycursor
|
||||
|
||||
query = ' '.join((
|
||||
|
||||
"SELECT kodi_id",
|
||||
"FROM emby",
|
||||
"WHERE media_folder = ?"
|
||||
))
|
||||
embycursor.execute(query, (mediafolderid,))
|
||||
items = embycursor.fetchall()
|
||||
|
||||
return items
|
||||
|
||||
def getItem_byKodiId(self, kodiid, mediatype):
|
||||
|
||||
embycursor = self.embycursor
|
||||
|
||||
query = ' '.join((
|
||||
|
||||
"SELECT emby_id, parent_id",
|
||||
"FROM emby",
|
||||
"WHERE kodi_id = ?",
|
||||
"AND media_type = ?"
|
||||
))
|
||||
embycursor.execute(query, (kodiid, mediatype,))
|
||||
item = embycursor.fetchone()
|
||||
|
||||
return item
|
||||
|
||||
def getItem_byParentId(self, parentid, mediatype):
|
||||
|
||||
embycursor = self.embycursor
|
||||
|
||||
query = ' '.join((
|
||||
|
||||
"SELECT emby_id, kodi_id, kodi_fileid",
|
||||
"FROM emby",
|
||||
"WHERE parent_id = ?",
|
||||
"AND media_type = ?"
|
||||
))
|
||||
embycursor.execute(query, (parentid, mediatype,))
|
||||
items = embycursor.fetchall()
|
||||
|
||||
return items
|
||||
|
||||
def getItemId_byParentId(self, parentid, mediatype):
|
||||
|
||||
embycursor = self.embycursor
|
||||
|
||||
query = ' '.join((
|
||||
|
||||
"SELECT emby_id, kodi_id",
|
||||
"FROM emby",
|
||||
"WHERE parent_id = ?",
|
||||
"AND media_type = ?"
|
||||
))
|
||||
embycursor.execute(query, (parentid, mediatype,))
|
||||
items = embycursor.fetchall()
|
||||
|
||||
return items
|
||||
|
||||
def getChecksum(self, mediatype):
|
||||
|
||||
embycursor = self.embycursor
|
||||
|
||||
query = ' '.join((
|
||||
|
||||
"SELECT emby_id, checksum",
|
||||
"FROM emby",
|
||||
"WHERE emby_type = ?"
|
||||
))
|
||||
embycursor.execute(query, (mediatype,))
|
||||
items = embycursor.fetchall()
|
||||
|
||||
return items
|
||||
|
||||
def getMediaType_byId(self, embyid):
|
||||
|
||||
embycursor = self.embycursor
|
||||
|
||||
query = ' '.join((
|
||||
|
||||
"SELECT emby_type",
|
||||
"FROM emby",
|
||||
"WHERE emby_id = ?"
|
||||
))
|
||||
embycursor.execute(query, (embyid,))
|
||||
try:
|
||||
itemtype = embycursor.fetchone()[0]
|
||||
|
||||
except TypeError:
|
||||
itemtype = None
|
||||
|
||||
return itemtype
|
||||
|
||||
def sortby_mediaType(self, itemids, unsorted=True):
|
||||
|
||||
sorted_items = {}
|
||||
|
||||
for itemid in itemids:
|
||||
|
||||
mediatype = self.getMediaType_byId(itemid)
|
||||
if mediatype:
|
||||
sorted_items.setdefault(mediatype, []).append(itemid)
|
||||
elif unsorted:
|
||||
sorted_items.setdefault('Unsorted', []).append(itemid)
|
||||
|
||||
return sorted_items
|
||||
|
||||
def addReference(self, embyid, kodiid, embytype, mediatype, fileid=None, pathid=None,
|
||||
parentid=None, checksum=None, mediafolderid=None):
|
||||
query = (
|
||||
'''
|
||||
INSERT OR REPLACE INTO emby(
|
||||
emby_id, kodi_id, kodi_fileid, kodi_pathid, emby_type, media_type, parent_id,
|
||||
checksum, media_folder)
|
||||
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
'''
|
||||
)
|
||||
self.embycursor.execute(query, (embyid, kodiid, fileid, pathid, embytype, mediatype,
|
||||
parentid, checksum, mediafolderid))
|
||||
|
||||
def updateReference(self, embyid, checksum):
|
||||
|
||||
query = "UPDATE emby SET checksum = ? WHERE emby_id = ?"
|
||||
self.embycursor.execute(query, (checksum, embyid))
|
||||
|
||||
def updateParentId(self, embyid, parent_kodiid):
|
||||
|
||||
query = "UPDATE emby SET parent_id = ? WHERE emby_id = ?"
|
||||
self.embycursor.execute(query, (parent_kodiid, embyid))
|
||||
|
||||
def removeItems_byParentId(self, parent_kodiid, mediatype):
|
||||
|
||||
query = ' '.join((
|
||||
|
||||
"DELETE FROM emby",
|
||||
"WHERE parent_id = ?",
|
||||
"AND media_type = ?"
|
||||
))
|
||||
self.embycursor.execute(query, (parent_kodiid, mediatype,))
|
||||
|
||||
def removeItem_byKodiId(self, kodiid, mediatype):
|
||||
|
||||
query = ' '.join((
|
||||
|
||||
"DELETE FROM emby",
|
||||
"WHERE kodi_id = ?",
|
||||
"AND media_type = ?"
|
||||
))
|
||||
self.embycursor.execute(query, (kodiid, mediatype,))
|
||||
|
||||
def removeItem(self, embyid):
|
||||
|
||||
query = "DELETE FROM emby WHERE emby_id = ?"
|
||||
self.embycursor.execute(query, (embyid,))
|
||||
|
||||
def removeWildItem(self, embyid):
|
||||
|
||||
query = "DELETE FROM emby WHERE emby_id LIKE ?"
|
||||
self.embycursor.execute(query, (embyid+"%",))
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#################################################################################################
|
||||
|
||||
import utils
|
||||
import clientinfo
|
||||
|
||||
#################################################################################################
|
||||
|
||||
|
||||
class Embydb_Functions():
|
||||
|
||||
|
||||
def __init__(self, embycursor):
|
||||
|
||||
self.embycursor = embycursor
|
||||
|
||||
self.clientInfo = clientinfo.ClientInfo()
|
||||
self.addonName = self.clientInfo.getAddonName()
|
||||
|
||||
def logMsg(self, msg, lvl=1):
|
||||
|
||||
className = self.__class__.__name__
|
||||
utils.logMsg("%s %s" % (self.addonName, className), msg, lvl)
|
||||
|
||||
def getViews(self):
|
||||
|
||||
views = []
|
||||
|
||||
query = ' '.join((
|
||||
|
||||
"SELECT view_id",
|
||||
"FROM view"
|
||||
))
|
||||
self.embycursor.execute(query)
|
||||
rows = self.embycursor.fetchall()
|
||||
for row in rows:
|
||||
views.append(row[0])
|
||||
|
||||
return views
|
||||
|
||||
def getView_byId(self, viewid):
|
||||
|
||||
|
||||
query = ' '.join((
|
||||
|
||||
"SELECT view_name, media_type, kodi_tagid",
|
||||
"FROM view",
|
||||
"WHERE view_id = ?"
|
||||
))
|
||||
self.embycursor.execute(query, (viewid,))
|
||||
view = self.embycursor.fetchone()
|
||||
|
||||
return view
|
||||
|
||||
def getView_byType(self, mediatype):
|
||||
|
||||
views = []
|
||||
|
||||
query = ' '.join((
|
||||
|
||||
"SELECT view_id, view_name",
|
||||
"FROM view",
|
||||
"WHERE media_type = ?"
|
||||
))
|
||||
self.embycursor.execute(query, (mediatype,))
|
||||
rows = self.embycursor.fetchall()
|
||||
for row in rows:
|
||||
views.append({
|
||||
|
||||
'id': row[0],
|
||||
'name': row[1]
|
||||
})
|
||||
|
||||
return views
|
||||
|
||||
def getView_byName(self, tagname):
|
||||
|
||||
query = ' '.join((
|
||||
|
||||
"SELECT view_id",
|
||||
"FROM view",
|
||||
"WHERE view_name = ?"
|
||||
))
|
||||
self.embycursor.execute(query, (tagname,))
|
||||
try:
|
||||
view = self.embycursor.fetchone()[0]
|
||||
|
||||
except TypeError:
|
||||
view = None
|
||||
|
||||
return view
|
||||
|
||||
def addView(self, embyid, name, mediatype, tagid):
|
||||
|
||||
query = (
|
||||
'''
|
||||
INSERT INTO view(
|
||||
view_id, view_name, media_type, kodi_tagid)
|
||||
|
||||
VALUES (?, ?, ?, ?)
|
||||
'''
|
||||
)
|
||||
self.embycursor.execute(query, (embyid, name, mediatype, tagid))
|
||||
|
||||
def updateView(self, name, tagid, mediafolderid):
|
||||
|
||||
query = ' '.join((
|
||||
|
||||
"UPDATE view",
|
||||
"SET view_name = ?, kodi_tagid = ?",
|
||||
"WHERE view_id = ?"
|
||||
))
|
||||
self.embycursor.execute(query, (name, tagid, mediafolderid))
|
||||
|
||||
def removeView(self, viewid):
|
||||
|
||||
query = ' '.join((
|
||||
|
||||
"DELETE FROM view",
|
||||
"WHERE view_id = ?"
|
||||
))
|
||||
self.embycursor.execute(query, (viewid,))
|
||||
|
||||
def getItem_byId(self, embyid):
|
||||
|
||||
query = ' '.join((
|
||||
|
||||
"SELECT kodi_id, kodi_fileid, kodi_pathid, parent_id, media_type, emby_type",
|
||||
"FROM emby",
|
||||
"WHERE emby_id = ?"
|
||||
))
|
||||
try:
|
||||
self.embycursor.execute(query, (embyid,))
|
||||
item = self.embycursor.fetchone()
|
||||
return item
|
||||
except: return None
|
||||
|
||||
def getItem_byWildId(self, embyid):
|
||||
|
||||
query = ' '.join((
|
||||
|
||||
"SELECT kodi_id, media_type",
|
||||
"FROM emby",
|
||||
"WHERE emby_id LIKE ?"
|
||||
))
|
||||
self.embycursor.execute(query, (embyid+"%",))
|
||||
return self.embycursor.fetchall()
|
||||
|
||||
def getItem_byView(self, mediafolderid):
|
||||
|
||||
query = ' '.join((
|
||||
|
||||
"SELECT kodi_id",
|
||||
"FROM emby",
|
||||
"WHERE media_folder = ?"
|
||||
))
|
||||
self.embycursor.execute(query, (mediafolderid,))
|
||||
return self.embycursor.fetchall()
|
||||
|
||||
def getItem_byKodiId(self, kodiid, mediatype):
|
||||
|
||||
query = ' '.join((
|
||||
|
||||
"SELECT emby_id, parent_id",
|
||||
"FROM emby",
|
||||
"WHERE kodi_id = ?",
|
||||
"AND media_type = ?"
|
||||
))
|
||||
self.embycursor.execute(query, (kodiid, mediatype,))
|
||||
return self.embycursor.fetchone()
|
||||
|
||||
def getItem_byParentId(self, parentid, mediatype):
|
||||
|
||||
query = ' '.join((
|
||||
|
||||
"SELECT emby_id, kodi_id, kodi_fileid",
|
||||
"FROM emby",
|
||||
"WHERE parent_id = ?",
|
||||
"AND media_type = ?"
|
||||
))
|
||||
self.embycursor.execute(query, (parentid, mediatype,))
|
||||
return self.embycursor.fetchall()
|
||||
|
||||
def getItemId_byParentId(self, parentid, mediatype):
|
||||
|
||||
query = ' '.join((
|
||||
|
||||
"SELECT emby_id, kodi_id",
|
||||
"FROM emby",
|
||||
"WHERE parent_id = ?",
|
||||
"AND media_type = ?"
|
||||
))
|
||||
self.embycursor.execute(query, (parentid, mediatype,))
|
||||
return self.embycursor.fetchall()
|
||||
|
||||
def getChecksum(self, mediatype):
|
||||
|
||||
query = ' '.join((
|
||||
|
||||
"SELECT emby_id, checksum",
|
||||
"FROM emby",
|
||||
"WHERE emby_type = ?"
|
||||
))
|
||||
self.embycursor.execute(query, (mediatype,))
|
||||
return self.embycursor.fetchall()
|
||||
|
||||
def getMediaType_byId(self, embyid):
|
||||
|
||||
query = ' '.join((
|
||||
|
||||
"SELECT emby_type",
|
||||
"FROM emby",
|
||||
"WHERE emby_id = ?"
|
||||
))
|
||||
self.embycursor.execute(query, (embyid,))
|
||||
try:
|
||||
itemtype = self.embycursor.fetchone()[0]
|
||||
|
||||
except TypeError:
|
||||
itemtype = None
|
||||
|
||||
return itemtype
|
||||
|
||||
def sortby_mediaType(self, itemids, unsorted=True):
|
||||
|
||||
sorted_items = {}
|
||||
|
||||
for itemid in itemids:
|
||||
|
||||
mediatype = self.getMediaType_byId(itemid)
|
||||
if mediatype:
|
||||
sorted_items.setdefault(mediatype, []).append(itemid)
|
||||
elif unsorted:
|
||||
sorted_items.setdefault('Unsorted', []).append(itemid)
|
||||
|
||||
return sorted_items
|
||||
|
||||
def addReference(self, embyid, kodiid, embytype, mediatype, fileid=None, pathid=None,
|
||||
parentid=None, checksum=None, mediafolderid=None):
|
||||
query = (
|
||||
'''
|
||||
INSERT OR REPLACE INTO emby(
|
||||
emby_id, kodi_id, kodi_fileid, kodi_pathid, emby_type, media_type, parent_id,
|
||||
checksum, media_folder)
|
||||
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
'''
|
||||
)
|
||||
self.embycursor.execute(query, (embyid, kodiid, fileid, pathid, embytype, mediatype,
|
||||
parentid, checksum, mediafolderid))
|
||||
|
||||
def updateReference(self, embyid, checksum):
|
||||
|
||||
query = "UPDATE emby SET checksum = ? WHERE emby_id = ?"
|
||||
self.embycursor.execute(query, (checksum, embyid))
|
||||
|
||||
def updateParentId(self, embyid, parent_kodiid):
|
||||
|
||||
query = "UPDATE emby SET parent_id = ? WHERE emby_id = ?"
|
||||
self.embycursor.execute(query, (parent_kodiid, embyid))
|
||||
|
||||
def removeItems_byParentId(self, parent_kodiid, mediatype):
|
||||
|
||||
query = ' '.join((
|
||||
|
||||
"DELETE FROM emby",
|
||||
"WHERE parent_id = ?",
|
||||
"AND media_type = ?"
|
||||
))
|
||||
self.embycursor.execute(query, (parent_kodiid, mediatype,))
|
||||
|
||||
def removeItem_byKodiId(self, kodiid, mediatype):
|
||||
|
||||
query = ' '.join((
|
||||
|
||||
"DELETE FROM emby",
|
||||
"WHERE kodi_id = ?",
|
||||
"AND media_type = ?"
|
||||
))
|
||||
self.embycursor.execute(query, (kodiid, mediatype,))
|
||||
|
||||
def removeItem(self, embyid):
|
||||
|
||||
query = "DELETE FROM emby WHERE emby_id = ?"
|
||||
self.embycursor.execute(query, (embyid,))
|
||||
|
||||
def removeWildItem(self, embyid):
|
||||
|
||||
query = "DELETE FROM emby WHERE emby_id LIKE ?"
|
||||
self.embycursor.execute(query, (embyid+"%",))
|
||||
|
File diff suppressed because it is too large
Load diff
|
@ -176,8 +176,8 @@ class InitialSetup():
|
|||
sock.setsockopt(socket.SOL_IP, socket.IP_MULTICAST_LOOP, 1)
|
||||
sock.setsockopt(socket.IPPROTO_IP, socket.SO_REUSEADDR, 1)
|
||||
|
||||
self.logMsg("MultiGroup : %s" % str(MULTI_GROUP), 2);
|
||||
self.logMsg("Sending UDP Data: %s" % MESSAGE, 2);
|
||||
self.logMsg("MultiGroup : %s" % str(MULTI_GROUP), 2)
|
||||
self.logMsg("Sending UDP Data: %s" % MESSAGE, 2)
|
||||
sock.sendto(MESSAGE, MULTI_GROUP)
|
||||
|
||||
try:
|
||||
|
|
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
@ -1,209 +1,209 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
#################################################################################################
|
||||
|
||||
import json
|
||||
|
||||
import xbmc
|
||||
import xbmcgui
|
||||
|
||||
import clientinfo
|
||||
import downloadutils
|
||||
import embydb_functions as embydb
|
||||
import playbackutils as pbutils
|
||||
import utils
|
||||
|
||||
#################################################################################################
|
||||
|
||||
|
||||
class KodiMonitor(xbmc.Monitor):
|
||||
|
||||
|
||||
def __init__(self):
|
||||
|
||||
self.clientInfo = clientinfo.ClientInfo()
|
||||
self.addonName = self.clientInfo.getAddonName()
|
||||
self.doUtils = downloadutils.DownloadUtils()
|
||||
|
||||
self.logMsg("Kodi monitor started.", 1)
|
||||
|
||||
def logMsg(self, msg, lvl=1):
|
||||
|
||||
self.className = self.__class__.__name__
|
||||
utils.logMsg("%s %s" % (self.addonName, self.className), msg, lvl)
|
||||
|
||||
|
||||
def onScanStarted(self, library):
|
||||
self.logMsg("Kodi library scan %s running." % library, 2)
|
||||
if library == "video":
|
||||
utils.window('emby_kodiScan', value="true")
|
||||
|
||||
def onScanFinished(self, library):
|
||||
self.logMsg("Kodi library scan %s finished." % library, 2)
|
||||
if library == "video":
|
||||
utils.window('emby_kodiScan', clear=True)
|
||||
|
||||
def onSettingsChanged(self):
|
||||
# Monitor emby settings
|
||||
# Review reset setting at a later time, need to be adjusted to account for initial setup
|
||||
# changes.
|
||||
'''currentPath = utils.settings('useDirectPaths')
|
||||
if utils.window('emby_pluginpath') != currentPath:
|
||||
# Plugin path value changed. Offer to reset
|
||||
self.logMsg("Changed to playback mode detected", 1)
|
||||
utils.window('emby_pluginpath', value=currentPath)
|
||||
resp = xbmcgui.Dialog().yesno(
|
||||
heading="Playback mode change detected",
|
||||
line1=(
|
||||
"Detected the playback mode has changed. The database "
|
||||
"needs to be recreated for the change to be applied. "
|
||||
"Proceed?"))
|
||||
if resp:
|
||||
utils.reset()'''
|
||||
|
||||
currentLog = utils.settings('logLevel')
|
||||
if utils.window('emby_logLevel') != currentLog:
|
||||
# The log level changed, set new prop
|
||||
self.logMsg("New log level: %s" % currentLog, 1)
|
||||
utils.window('emby_logLevel', value=currentLog)
|
||||
|
||||
def onNotification(self, sender, method, data):
|
||||
|
||||
doUtils = self.doUtils
|
||||
if method not in ("Playlist.OnAdd"):
|
||||
self.logMsg("Method: %s Data: %s" % (method, data), 1)
|
||||
|
||||
if data:
|
||||
data = json.loads(data,'utf-8')
|
||||
|
||||
|
||||
if method == "Player.OnPlay":
|
||||
# Set up report progress for emby playback
|
||||
item = data.get('item')
|
||||
try:
|
||||
kodiid = item['id']
|
||||
type = item['type']
|
||||
except (KeyError, TypeError):
|
||||
self.logMsg("Item is invalid for playstate update.", 1)
|
||||
else:
|
||||
if ((utils.settings('useDirectPaths') == "1" and not type == "song") or
|
||||
(type == "song" and utils.settings('enableMusic') == "true")):
|
||||
# Set up properties for player
|
||||
embyconn = utils.kodiSQL('emby')
|
||||
embycursor = embyconn.cursor()
|
||||
emby_db = embydb.Embydb_Functions(embycursor)
|
||||
emby_dbitem = emby_db.getItem_byKodiId(kodiid, type)
|
||||
try:
|
||||
itemid = emby_dbitem[0]
|
||||
except TypeError:
|
||||
self.logMsg("No kodiid returned.", 1)
|
||||
else:
|
||||
url = "{server}/emby/Users/{UserId}/Items/%s?format=json" % itemid
|
||||
result = doUtils.downloadUrl(url)
|
||||
self.logMsg("Item: %s" % result, 2)
|
||||
|
||||
playurl = None
|
||||
count = 0
|
||||
while not playurl and count < 2:
|
||||
try:
|
||||
playurl = xbmc.Player().getPlayingFile()
|
||||
except RuntimeError:
|
||||
count += 1
|
||||
xbmc.sleep(200)
|
||||
else:
|
||||
listItem = xbmcgui.ListItem()
|
||||
playback = pbutils.PlaybackUtils(result)
|
||||
|
||||
if type == "song" and utils.settings('streamMusic') == "true":
|
||||
utils.window('emby_%s.playmethod' % playurl,
|
||||
value="DirectStream")
|
||||
else:
|
||||
utils.window('emby_%s.playmethod' % playurl,
|
||||
value="DirectPlay")
|
||||
# Set properties for player.py
|
||||
playback.setProperties(playurl, listItem)
|
||||
finally:
|
||||
embycursor.close()
|
||||
|
||||
|
||||
elif method == "VideoLibrary.OnUpdate":
|
||||
# Manually marking as watched/unwatched
|
||||
playcount = data.get('playcount')
|
||||
item = data.get('item')
|
||||
try:
|
||||
kodiid = item['id']
|
||||
type = item['type']
|
||||
except (KeyError, TypeError):
|
||||
self.logMsg("Item is invalid for playstate update.", 1)
|
||||
else:
|
||||
# Send notification to the server.
|
||||
embyconn = utils.kodiSQL('emby')
|
||||
embycursor = embyconn.cursor()
|
||||
emby_db = embydb.Embydb_Functions(embycursor)
|
||||
emby_dbitem = emby_db.getItem_byKodiId(kodiid, type)
|
||||
try:
|
||||
itemid = emby_dbitem[0]
|
||||
except TypeError:
|
||||
self.logMsg("Could not find itemid in emby database.", 1)
|
||||
else:
|
||||
# Stop from manually marking as watched unwatched, with actual playback.
|
||||
if utils.window('emby_skipWatched%s' % itemid) == "true":
|
||||
# property is set in player.py
|
||||
utils.window('emby_skipWatched%s' % itemid, clear=True)
|
||||
else:
|
||||
# notify the server
|
||||
url = "{server}/emby/Users/{UserId}/PlayedItems/%s?format=json" % itemid
|
||||
if playcount != 0:
|
||||
doUtils.downloadUrl(url, type="POST")
|
||||
self.logMsg("Mark as watched for itemid: %s" % itemid, 1)
|
||||
else:
|
||||
doUtils.downloadUrl(url, type="DELETE")
|
||||
self.logMsg("Mark as unwatched for itemid: %s" % itemid, 1)
|
||||
finally:
|
||||
embycursor.close()
|
||||
|
||||
|
||||
elif method == "VideoLibrary.OnRemove":
|
||||
# Removed function, because with plugin paths + clean library, it will wipe
|
||||
# entire library if user has permissions. Instead, use the emby context menu available
|
||||
# in Isengard and higher version
|
||||
pass
|
||||
'''try:
|
||||
kodiid = data['id']
|
||||
type = data['type']
|
||||
except (KeyError, TypeError):
|
||||
self.logMsg("Item is invalid for emby deletion.", 1)
|
||||
else:
|
||||
# Send the delete action to the server.
|
||||
embyconn = utils.kodiSQL('emby')
|
||||
embycursor = embyconn.cursor()
|
||||
emby_db = embydb.Embydb_Functions(embycursor)
|
||||
emby_dbitem = emby_db.getItem_byKodiId(kodiid, type)
|
||||
try:
|
||||
itemid = emby_dbitem[0]
|
||||
except TypeError:
|
||||
self.logMsg("Could not find itemid in emby database.", 1)
|
||||
else:
|
||||
if utils.settings('skipContextMenu') != "true":
|
||||
resp = xbmcgui.Dialog().yesno(
|
||||
heading="Confirm delete",
|
||||
line1="Delete file on Emby Server?")
|
||||
if not resp:
|
||||
self.logMsg("User skipped deletion.", 1)
|
||||
embycursor.close()
|
||||
return
|
||||
|
||||
url = "{server}/emby/Items/%s?format=json" % itemid
|
||||
self.logMsg("Deleting request: %s" % itemid)
|
||||
doUtils.downloadUrl(url, type="DELETE")
|
||||
finally:
|
||||
embycursor.close()'''
|
||||
|
||||
|
||||
elif method == "System.OnWake":
|
||||
# Allow network to wake up
|
||||
xbmc.sleep(10000)
|
||||
utils.window('emby_onWake', value="true")
|
||||
|
||||
elif method == "Playlist.OnClear":
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#################################################################################################
|
||||
|
||||
import json
|
||||
|
||||
import xbmc
|
||||
import xbmcgui
|
||||
|
||||
import clientinfo
|
||||
import downloadutils
|
||||
import embydb_functions as embydb
|
||||
import playbackutils as pbutils
|
||||
import utils
|
||||
|
||||
#################################################################################################
|
||||
|
||||
|
||||
class KodiMonitor(xbmc.Monitor):
|
||||
|
||||
|
||||
def __init__(self):
|
||||
|
||||
self.clientInfo = clientinfo.ClientInfo()
|
||||
self.addonName = self.clientInfo.getAddonName()
|
||||
self.doUtils = downloadutils.DownloadUtils()
|
||||
|
||||
self.logMsg("Kodi monitor started.", 1)
|
||||
|
||||
def logMsg(self, msg, lvl=1):
|
||||
|
||||
self.className = self.__class__.__name__
|
||||
utils.logMsg("%s %s" % (self.addonName, self.className), msg, lvl)
|
||||
|
||||
|
||||
def onScanStarted(self, library):
|
||||
self.logMsg("Kodi library scan %s running." % library, 2)
|
||||
if library == "video":
|
||||
utils.window('emby_kodiScan', value="true")
|
||||
|
||||
def onScanFinished(self, library):
|
||||
self.logMsg("Kodi library scan %s finished." % library, 2)
|
||||
if library == "video":
|
||||
utils.window('emby_kodiScan', clear=True)
|
||||
|
||||
def onSettingsChanged(self):
|
||||
# Monitor emby settings
|
||||
# Review reset setting at a later time, need to be adjusted to account for initial setup
|
||||
# changes.
|
||||
'''currentPath = utils.settings('useDirectPaths')
|
||||
if utils.window('emby_pluginpath') != currentPath:
|
||||
# Plugin path value changed. Offer to reset
|
||||
self.logMsg("Changed to playback mode detected", 1)
|
||||
utils.window('emby_pluginpath', value=currentPath)
|
||||
resp = xbmcgui.Dialog().yesno(
|
||||
heading="Playback mode change detected",
|
||||
line1=(
|
||||
"Detected the playback mode has changed. The database "
|
||||
"needs to be recreated for the change to be applied. "
|
||||
"Proceed?"))
|
||||
if resp:
|
||||
utils.reset()'''
|
||||
|
||||
currentLog = utils.settings('logLevel')
|
||||
if utils.window('emby_logLevel') != currentLog:
|
||||
# The log level changed, set new prop
|
||||
self.logMsg("New log level: %s" % currentLog, 1)
|
||||
utils.window('emby_logLevel', value=currentLog)
|
||||
|
||||
def onNotification(self, sender, method, data):
|
||||
|
||||
doUtils = self.doUtils
|
||||
if method not in ("Playlist.OnAdd"):
|
||||
self.logMsg("Method: %s Data: %s" % (method, data), 1)
|
||||
|
||||
if data:
|
||||
data = json.loads(data,'utf-8')
|
||||
|
||||
|
||||
if method == "Player.OnPlay":
|
||||
# Set up report progress for emby playback
|
||||
item = data.get('item')
|
||||
try:
|
||||
kodiid = item['id']
|
||||
item_type = item['type']
|
||||
except (KeyError, TypeError):
|
||||
self.logMsg("Item is invalid for playstate update.", 1)
|
||||
else:
|
||||
if ((utils.settings('useDirectPaths') == "1" and not item_type == "song") or
|
||||
(item_type == "song" and utils.settings('enableMusic') == "true")):
|
||||
# Set up properties for player
|
||||
embyconn = utils.kodiSQL('emby')
|
||||
embycursor = embyconn.cursor()
|
||||
emby_db = embydb.Embydb_Functions(embycursor)
|
||||
emby_dbitem = emby_db.getItem_byKodiId(kodiid, item_type)
|
||||
try:
|
||||
itemid = emby_dbitem[0]
|
||||
except TypeError:
|
||||
self.logMsg("No kodiid returned.", 1)
|
||||
else:
|
||||
url = "{server}/emby/Users/{UserId}/Items/%s?format=json" % itemid
|
||||
result = doUtils.downloadUrl(url)
|
||||
self.logMsg("Item: %s" % result, 2)
|
||||
|
||||
playurl = None
|
||||
count = 0
|
||||
while not playurl and count < 2:
|
||||
try:
|
||||
playurl = xbmc.Player().getPlayingFile()
|
||||
except RuntimeError:
|
||||
count += 1
|
||||
xbmc.sleep(200)
|
||||
else:
|
||||
listItem = xbmcgui.ListItem()
|
||||
playback = pbutils.PlaybackUtils(result)
|
||||
|
||||
if item_type == "song" and utils.settings('streamMusic') == "true":
|
||||
utils.window('emby_%s.playmethod' % playurl,
|
||||
value="DirectStream")
|
||||
else:
|
||||
utils.window('emby_%s.playmethod' % playurl,
|
||||
value="DirectPlay")
|
||||
# Set properties for player.py
|
||||
playback.setProperties(playurl, listItem)
|
||||
finally:
|
||||
embycursor.close()
|
||||
|
||||
|
||||
elif method == "VideoLibrary.OnUpdate":
|
||||
# Manually marking as watched/unwatched
|
||||
playcount = data.get('playcount')
|
||||
item = data.get('item')
|
||||
try:
|
||||
kodiid = item['id']
|
||||
item_type = item['type']
|
||||
except (KeyError, TypeError):
|
||||
self.logMsg("Item is invalid for playstate update.", 1)
|
||||
else:
|
||||
# Send notification to the server.
|
||||
embyconn = utils.kodiSQL('emby')
|
||||
embycursor = embyconn.cursor()
|
||||
emby_db = embydb.Embydb_Functions(embycursor)
|
||||
emby_dbitem = emby_db.getItem_byKodiId(kodiid, item_type)
|
||||
try:
|
||||
itemid = emby_dbitem[0]
|
||||
except TypeError:
|
||||
self.logMsg("Could not find itemid in emby database.", 1)
|
||||
else:
|
||||
# Stop from manually marking as watched unwatched, with actual playback.
|
||||
if utils.window('emby_skipWatched%s' % itemid) == "true":
|
||||
# property is set in player.py
|
||||
utils.window('emby_skipWatched%s' % itemid, clear=True)
|
||||
else:
|
||||
# notify the server
|
||||
url = "{server}/emby/Users/{UserId}/PlayedItems/%s?format=json" % itemid
|
||||
if playcount != 0:
|
||||
doUtils.downloadUrl(url, action_type="POST")
|
||||
self.logMsg("Mark as watched for itemid: %s" % itemid, 1)
|
||||
else:
|
||||
doUtils.downloadUrl(url, action_type="DELETE")
|
||||
self.logMsg("Mark as unwatched for itemid: %s" % itemid, 1)
|
||||
finally:
|
||||
embycursor.close()
|
||||
|
||||
|
||||
elif method == "VideoLibrary.OnRemove":
|
||||
# Removed function, because with plugin paths + clean library, it will wipe
|
||||
# entire library if user has permissions. Instead, use the emby context menu available
|
||||
# in Isengard and higher version
|
||||
pass
|
||||
'''try:
|
||||
kodiid = data['id']
|
||||
type = data['type']
|
||||
except (KeyError, TypeError):
|
||||
self.logMsg("Item is invalid for emby deletion.", 1)
|
||||
else:
|
||||
# Send the delete action to the server.
|
||||
embyconn = utils.kodiSQL('emby')
|
||||
embycursor = embyconn.cursor()
|
||||
emby_db = embydb.Embydb_Functions(embycursor)
|
||||
emby_dbitem = emby_db.getItem_byKodiId(kodiid, type)
|
||||
try:
|
||||
itemid = emby_dbitem[0]
|
||||
except TypeError:
|
||||
self.logMsg("Could not find itemid in emby database.", 1)
|
||||
else:
|
||||
if utils.settings('skipContextMenu') != "true":
|
||||
resp = xbmcgui.Dialog().yesno(
|
||||
heading="Confirm delete",
|
||||
line1="Delete file on Emby Server?")
|
||||
if not resp:
|
||||
self.logMsg("User skipped deletion.", 1)
|
||||
embycursor.close()
|
||||
return
|
||||
|
||||
url = "{server}/emby/Items/%s?format=json" % itemid
|
||||
self.logMsg("Deleting request: %s" % itemid)
|
||||
doUtils.downloadUrl(url, action_type="DELETE")
|
||||
finally:
|
||||
embycursor.close()'''
|
||||
|
||||
|
||||
elif method == "System.OnWake":
|
||||
# Allow network to wake up
|
||||
xbmc.sleep(10000)
|
||||
utils.window('emby_onWake', value="true")
|
||||
|
||||
elif method == "Playlist.OnClear":
|
||||
pass
|
File diff suppressed because it is too large
Load diff
|
@ -1,286 +1,287 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
#################################################################################################
|
||||
|
||||
import os
|
||||
|
||||
import xbmc
|
||||
import xbmcaddon
|
||||
import xbmcvfs
|
||||
|
||||
from mutagen.flac import FLAC, Picture
|
||||
from mutagen.id3 import ID3
|
||||
from mutagen import id3
|
||||
import base64
|
||||
|
||||
import read_embyserver as embyserver
|
||||
import utils
|
||||
|
||||
#################################################################################################
|
||||
|
||||
# Helper for the music library, intended to fix missing song ID3 tags on Emby
|
||||
|
||||
def logMsg(msg, lvl=1):
|
||||
utils.logMsg("%s %s" % ("Emby", "musictools"), msg, lvl)
|
||||
|
||||
def getRealFileName(filename, isTemp=False):
|
||||
#get the filename path accessible by python if possible...
|
||||
|
||||
if not xbmcvfs.exists(filename):
|
||||
logMsg( "File does not exist! %s" %(filename), 0)
|
||||
return (False, "")
|
||||
|
||||
#if we use os.path method on older python versions (sunch as some android builds), we need to pass arguments as string
|
||||
if os.path.supports_unicode_filenames:
|
||||
checkfile = filename
|
||||
else:
|
||||
checkfile = filename.encode("utf-8")
|
||||
|
||||
# determine if our python module is able to access the file directly...
|
||||
if os.path.exists(checkfile):
|
||||
filename = filename
|
||||
elif os.path.exists(checkfile.replace("smb://","\\\\").replace("/","\\")):
|
||||
filename = filename.replace("smb://","\\\\").replace("/","\\")
|
||||
else:
|
||||
#file can not be accessed by python directly, we copy it for processing...
|
||||
isTemp = True
|
||||
if "/" in filename: filepart = filename.split("/")[-1]
|
||||
else: filepart = filename.split("\\")[-1]
|
||||
tempfile = "special://temp/"+filepart
|
||||
xbmcvfs.copy(filename, tempfile)
|
||||
filename = xbmc.translatePath(tempfile).decode("utf-8")
|
||||
|
||||
return (isTemp,filename)
|
||||
|
||||
def getEmbyRatingFromKodiRating(rating):
|
||||
# Translation needed between Kodi/ID3 rating and emby likes/favourites:
|
||||
# 3+ rating in ID3 = emby like
|
||||
# 5+ rating in ID3 = emby favourite
|
||||
# rating 0 = emby dislike
|
||||
# rating 1-2 = emby no likes or dislikes (returns 1 in results)
|
||||
favourite = False
|
||||
deletelike = False
|
||||
like = False
|
||||
if (rating >= 3): like = True
|
||||
if (rating == 0): like = False
|
||||
if (rating == 1 or rating == 2): deletelike = True
|
||||
if (rating >= 5): favourite = True
|
||||
return(like, favourite, deletelike)
|
||||
|
||||
def getAdditionalSongTags(embyid, emby_rating, API, kodicursor, emby_db, enableimportsongrating, enableexportsongrating, enableupdatesongrating):
|
||||
|
||||
emby = embyserver.Read_EmbyServer()
|
||||
|
||||
previous_values = None
|
||||
filename = API.getFilePath()
|
||||
rating = 0
|
||||
emby_rating = int(round(emby_rating, 0))
|
||||
|
||||
#get file rating and comment tag from file itself.
|
||||
if enableimportsongrating:
|
||||
file_rating, comment, hasEmbeddedCover = getSongTags(filename)
|
||||
else:
|
||||
file_rating = 0
|
||||
comment = ""
|
||||
hasEmbeddedCover = False
|
||||
|
||||
|
||||
emby_dbitem = emby_db.getItem_byId(embyid)
|
||||
try:
|
||||
kodiid = emby_dbitem[0]
|
||||
except TypeError:
|
||||
# Item is not in database.
|
||||
currentvalue = None
|
||||
else:
|
||||
query = "SELECT rating FROM song WHERE idSong = ?"
|
||||
kodicursor.execute(query, (kodiid,))
|
||||
try:
|
||||
currentvalue = int(round(float(kodicursor.fetchone()[0]),0))
|
||||
except: currentvalue = None
|
||||
|
||||
# Only proceed if we actually have a rating from the file
|
||||
if file_rating is None and currentvalue:
|
||||
return (currentvalue, comment, False)
|
||||
elif file_rating is None and not currentvalue:
|
||||
return (emby_rating, comment, False)
|
||||
|
||||
logMsg("getAdditionalSongTags --> embyid: %s - emby_rating: %s - file_rating: %s - current rating in kodidb: %s" %(embyid, emby_rating, file_rating, currentvalue))
|
||||
|
||||
updateFileRating = False
|
||||
updateEmbyRating = False
|
||||
|
||||
if currentvalue != None:
|
||||
# we need to translate the emby values...
|
||||
if emby_rating == 1 and currentvalue == 2:
|
||||
emby_rating = 2
|
||||
if emby_rating == 3 and currentvalue == 4:
|
||||
emby_rating = 4
|
||||
|
||||
#if updating rating into file is disabled, we ignore the rating in the file...
|
||||
if not enableupdatesongrating:
|
||||
file_rating = currentvalue
|
||||
#if convert emby likes/favourites convert to song rating is disabled, we ignore the emby rating...
|
||||
if not enableexportsongrating:
|
||||
emby_rating = currentvalue
|
||||
|
||||
if (emby_rating == file_rating) and (file_rating != currentvalue):
|
||||
#the rating has been updated from kodi itself, update change to both emby ands file
|
||||
rating = currentvalue
|
||||
updateFileRating = True
|
||||
updateEmbyRating = True
|
||||
elif (emby_rating != currentvalue) and (file_rating == currentvalue):
|
||||
#emby rating changed - update the file
|
||||
rating = emby_rating
|
||||
updateFileRating = True
|
||||
elif (file_rating != currentvalue) and (emby_rating == currentvalue):
|
||||
#file rating was updated, sync change to emby
|
||||
rating = file_rating
|
||||
updateEmbyRating = True
|
||||
elif (emby_rating != currentvalue) and (file_rating != currentvalue):
|
||||
#both ratings have changed (corner case) - the highest rating wins...
|
||||
if emby_rating > file_rating:
|
||||
rating = emby_rating
|
||||
updateFileRating = True
|
||||
else:
|
||||
rating = file_rating
|
||||
updateEmbyRating = True
|
||||
else:
|
||||
#nothing has changed, just return the current value
|
||||
rating = currentvalue
|
||||
else:
|
||||
# no rating yet in DB
|
||||
if enableimportsongrating:
|
||||
#prefer the file rating
|
||||
rating = file_rating
|
||||
#determine if we should also send the rating to emby server
|
||||
if enableexportsongrating:
|
||||
if emby_rating == 1 and file_rating == 2:
|
||||
emby_rating = 2
|
||||
if emby_rating == 3 and file_rating == 4:
|
||||
emby_rating = 4
|
||||
if emby_rating != file_rating:
|
||||
updateEmbyRating = True
|
||||
|
||||
elif enableexportsongrating:
|
||||
#set the initial rating to emby value
|
||||
rating = emby_rating
|
||||
|
||||
if updateFileRating and enableupdatesongrating:
|
||||
updateRatingToFile(rating, filename)
|
||||
|
||||
if updateEmbyRating and enableexportsongrating:
|
||||
# sync details to emby server. Translation needed between ID3 rating and emby likes/favourites:
|
||||
like, favourite, deletelike = getEmbyRatingFromKodiRating(rating)
|
||||
utils.window("ignore-update-%s" %embyid, "true") #set temp windows prop to ignore the update from webclient update
|
||||
emby.updateUserRating(embyid, like, favourite, deletelike)
|
||||
|
||||
return (rating, comment, hasEmbeddedCover)
|
||||
|
||||
def getSongTags(file):
|
||||
# Get the actual ID3 tags for music songs as the server is lacking that info
|
||||
rating = 0
|
||||
comment = ""
|
||||
hasEmbeddedCover = False
|
||||
|
||||
isTemp,filename = getRealFileName(file)
|
||||
logMsg( "getting song ID3 tags for " + filename)
|
||||
|
||||
try:
|
||||
###### FLAC FILES #############
|
||||
if filename.lower().endswith(".flac"):
|
||||
audio = FLAC(filename)
|
||||
if audio.get("comment"):
|
||||
comment = audio.get("comment")[0]
|
||||
for pic in audio.pictures:
|
||||
if pic.type == 3 and pic.data:
|
||||
#the file has an embedded cover
|
||||
hasEmbeddedCover = True
|
||||
if audio.get("rating"):
|
||||
rating = float(audio.get("rating")[0])
|
||||
#flac rating is 0-100 and needs to be converted to 0-5 range
|
||||
if rating > 5: rating = (rating / 100) * 5
|
||||
|
||||
###### MP3 FILES #############
|
||||
elif filename.lower().endswith(".mp3"):
|
||||
audio = ID3(filename)
|
||||
|
||||
if audio.get("APIC:Front Cover"):
|
||||
if audio.get("APIC:Front Cover").data:
|
||||
hasEmbeddedCover = True
|
||||
|
||||
if audio.get("comment"):
|
||||
comment = audio.get("comment")[0]
|
||||
if audio.get("POPM:Windows Media Player 9 Series"):
|
||||
if audio.get("POPM:Windows Media Player 9 Series").rating:
|
||||
rating = float(audio.get("POPM:Windows Media Player 9 Series").rating)
|
||||
#POPM rating is 0-255 and needs to be converted to 0-5 range
|
||||
if rating > 5: rating = (rating / 255) * 5
|
||||
else:
|
||||
logMsg( "Not supported fileformat or unable to access file: %s" %(filename))
|
||||
|
||||
#the rating must be a round value
|
||||
rating = int(round(rating,0))
|
||||
|
||||
except Exception as e:
|
||||
#file in use ?
|
||||
utils.logMsg("Exception in getSongTags", str(e),0)
|
||||
rating = None
|
||||
|
||||
#remove tempfile if needed....
|
||||
if isTemp: xbmcvfs.delete(filename)
|
||||
|
||||
return (rating, comment, hasEmbeddedCover)
|
||||
|
||||
def updateRatingToFile(rating, file):
|
||||
#update the rating from Emby to the file
|
||||
|
||||
f = xbmcvfs.File(file)
|
||||
org_size = f.size()
|
||||
f.close()
|
||||
|
||||
#create tempfile
|
||||
if "/" in file: filepart = file.split("/")[-1]
|
||||
else: filepart = file.split("\\")[-1]
|
||||
tempfile = "special://temp/"+filepart
|
||||
xbmcvfs.copy(file, tempfile)
|
||||
tempfile = xbmc.translatePath(tempfile).decode("utf-8")
|
||||
|
||||
logMsg( "setting song rating: %s for filename: %s - using tempfile: %s" %(rating,file,tempfile))
|
||||
|
||||
if not tempfile:
|
||||
return
|
||||
|
||||
try:
|
||||
if tempfile.lower().endswith(".flac"):
|
||||
audio = FLAC(tempfile)
|
||||
calcrating = int(round((float(rating) / 5) * 100, 0))
|
||||
audio["rating"] = str(calcrating)
|
||||
audio.save()
|
||||
elif tempfile.lower().endswith(".mp3"):
|
||||
audio = ID3(tempfile)
|
||||
calcrating = int(round((float(rating) / 5) * 255, 0))
|
||||
audio.add(id3.POPM(email="Windows Media Player 9 Series", rating=calcrating, count=1))
|
||||
audio.save()
|
||||
else:
|
||||
logMsg( "Not supported fileformat: %s" %(tempfile))
|
||||
|
||||
#once we have succesfully written the flags we move the temp file to destination, otherwise not proceeding and just delete the temp
|
||||
#safety check: we check the file size of the temp file before proceeding with overwite of original file
|
||||
f = xbmcvfs.File(tempfile)
|
||||
checksum_size = f.size()
|
||||
f.close()
|
||||
if checksum_size >= org_size:
|
||||
xbmcvfs.delete(file)
|
||||
xbmcvfs.copy(tempfile,file)
|
||||
else:
|
||||
logMsg( "Checksum mismatch for filename: %s - using tempfile: %s - not proceeding with file overwite!" %(rating,file,tempfile))
|
||||
|
||||
#always delete the tempfile
|
||||
xbmcvfs.delete(tempfile)
|
||||
|
||||
except Exception as e:
|
||||
#file in use ?
|
||||
logMsg("Exception in updateRatingToFile %s" %e,0)
|
||||
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#################################################################################################
|
||||
|
||||
import os
|
||||
|
||||
import xbmc
|
||||
import xbmcaddon
|
||||
import xbmcvfs
|
||||
|
||||
from mutagen.flac import FLAC, Picture
|
||||
from mutagen.id3 import ID3
|
||||
from mutagen import id3
|
||||
import base64
|
||||
|
||||
import read_embyserver as embyserver
|
||||
import utils
|
||||
|
||||
#################################################################################################
|
||||
|
||||
# Helper for the music library, intended to fix missing song ID3 tags on Emby
|
||||
|
||||
def logMsg(msg, lvl=1):
|
||||
utils.logMsg("%s %s" % ("Emby", "musictools"), msg, lvl)
|
||||
|
||||
def getRealFileName(filename, isTemp=False):
|
||||
#get the filename path accessible by python if possible...
|
||||
|
||||
if not xbmcvfs.exists(filename):
|
||||
logMsg( "File does not exist! %s" %(filename), 0)
|
||||
return (False, "")
|
||||
|
||||
#if we use os.path method on older python versions (sunch as some android builds), we need to pass arguments as string
|
||||
if os.path.supports_unicode_filenames:
|
||||
checkfile = filename
|
||||
else:
|
||||
checkfile = filename.encode("utf-8")
|
||||
|
||||
# determine if our python module is able to access the file directly...
|
||||
if os.path.exists(checkfile):
|
||||
filename = filename
|
||||
elif os.path.exists(checkfile.replace("smb://","\\\\").replace("/","\\")):
|
||||
filename = filename.replace("smb://","\\\\").replace("/","\\")
|
||||
else:
|
||||
#file can not be accessed by python directly, we copy it for processing...
|
||||
isTemp = True
|
||||
if "/" in filename: filepart = filename.split("/")[-1]
|
||||
else: filepart = filename.split("\\")[-1]
|
||||
tempfile = "special://temp/"+filepart
|
||||
xbmcvfs.copy(filename, tempfile)
|
||||
filename = xbmc.translatePath(tempfile).decode("utf-8")
|
||||
|
||||
return (isTemp,filename)
|
||||
|
||||
def getEmbyRatingFromKodiRating(rating):
|
||||
# Translation needed between Kodi/ID3 rating and emby likes/favourites:
|
||||
# 3+ rating in ID3 = emby like
|
||||
# 5+ rating in ID3 = emby favourite
|
||||
# rating 0 = emby dislike
|
||||
# rating 1-2 = emby no likes or dislikes (returns 1 in results)
|
||||
favourite = False
|
||||
deletelike = False
|
||||
like = False
|
||||
if (rating >= 3): like = True
|
||||
if (rating == 0): like = False
|
||||
if (rating == 1 or rating == 2): deletelike = True
|
||||
if (rating >= 5): favourite = True
|
||||
return(like, favourite, deletelike)
|
||||
|
||||
def getAdditionalSongTags(embyid, emby_rating, API, kodicursor, emby_db, enableimportsongrating, enableexportsongrating, enableupdatesongrating):
|
||||
|
||||
emby = embyserver.Read_EmbyServer()
|
||||
|
||||
previous_values = None
|
||||
filename = API.getFilePath()
|
||||
rating = 0
|
||||
emby_rating = int(round(emby_rating, 0))
|
||||
|
||||
#get file rating and comment tag from file itself.
|
||||
if enableimportsongrating:
|
||||
file_rating, comment, hasEmbeddedCover = getSongTags(filename)
|
||||
else:
|
||||
file_rating = 0
|
||||
comment = ""
|
||||
hasEmbeddedCover = False
|
||||
|
||||
|
||||
emby_dbitem = emby_db.getItem_byId(embyid)
|
||||
try:
|
||||
kodiid = emby_dbitem[0]
|
||||
except TypeError:
|
||||
# Item is not in database.
|
||||
currentvalue = None
|
||||
else:
|
||||
query = "SELECT rating FROM song WHERE idSong = ?"
|
||||
kodicursor.execute(query, (kodiid,))
|
||||
try:
|
||||
currentvalue = int(round(float(kodicursor.fetchone()[0]),0))
|
||||
except: currentvalue = None
|
||||
|
||||
# Only proceed if we actually have a rating from the file
|
||||
if file_rating is None and currentvalue:
|
||||
return (currentvalue, comment, False)
|
||||
elif file_rating is None and not currentvalue:
|
||||
return (emby_rating, comment, False)
|
||||
|
||||
logMsg("getAdditionalSongTags --> embyid: %s - emby_rating: %s - file_rating: %s - current rating in kodidb: %s" %(embyid, emby_rating, file_rating, currentvalue))
|
||||
|
||||
updateFileRating = False
|
||||
updateEmbyRating = False
|
||||
|
||||
if currentvalue != None:
|
||||
# we need to translate the emby values...
|
||||
if emby_rating == 1 and currentvalue == 2:
|
||||
emby_rating = 2
|
||||
if emby_rating == 3 and currentvalue == 4:
|
||||
emby_rating = 4
|
||||
|
||||
#if updating rating into file is disabled, we ignore the rating in the file...
|
||||
if not enableupdatesongrating:
|
||||
file_rating = currentvalue
|
||||
#if convert emby likes/favourites convert to song rating is disabled, we ignore the emby rating...
|
||||
if not enableexportsongrating:
|
||||
emby_rating = currentvalue
|
||||
|
||||
if (emby_rating == file_rating) and (file_rating != currentvalue):
|
||||
#the rating has been updated from kodi itself, update change to both emby ands file
|
||||
rating = currentvalue
|
||||
updateFileRating = True
|
||||
updateEmbyRating = True
|
||||
elif (emby_rating != currentvalue) and (file_rating == currentvalue):
|
||||
#emby rating changed - update the file
|
||||
rating = emby_rating
|
||||
updateFileRating = True
|
||||
elif (file_rating != currentvalue) and (emby_rating == currentvalue):
|
||||
#file rating was updated, sync change to emby
|
||||
rating = file_rating
|
||||
updateEmbyRating = True
|
||||
elif (emby_rating != currentvalue) and (file_rating != currentvalue):
|
||||
#both ratings have changed (corner case) - the highest rating wins...
|
||||
if emby_rating > file_rating:
|
||||
rating = emby_rating
|
||||
updateFileRating = True
|
||||
else:
|
||||
rating = file_rating
|
||||
updateEmbyRating = True
|
||||
else:
|
||||
#nothing has changed, just return the current value
|
||||
rating = currentvalue
|
||||
else:
|
||||
# no rating yet in DB
|
||||
if enableimportsongrating:
|
||||
#prefer the file rating
|
||||
rating = file_rating
|
||||
#determine if we should also send the rating to emby server
|
||||
if enableexportsongrating:
|
||||
if emby_rating == 1 and file_rating == 2:
|
||||
emby_rating = 2
|
||||
if emby_rating == 3 and file_rating == 4:
|
||||
emby_rating = 4
|
||||
if emby_rating != file_rating:
|
||||
updateEmbyRating = True
|
||||
|
||||
elif enableexportsongrating:
|
||||
#set the initial rating to emby value
|
||||
rating = emby_rating
|
||||
|
||||
if updateFileRating and enableupdatesongrating:
|
||||
updateRatingToFile(rating, filename)
|
||||
|
||||
if updateEmbyRating and enableexportsongrating:
|
||||
# sync details to emby server. Translation needed between ID3 rating and emby likes/favourites:
|
||||
like, favourite, deletelike = getEmbyRatingFromKodiRating(rating)
|
||||
utils.window("ignore-update-%s" %embyid, "true") #set temp windows prop to ignore the update from webclient update
|
||||
emby.updateUserRating(embyid, like, favourite, deletelike)
|
||||
|
||||
return (rating, comment, hasEmbeddedCover)
|
||||
|
||||
def getSongTags(file):
|
||||
# Get the actual ID3 tags for music songs as the server is lacking that info
|
||||
rating = 0
|
||||
comment = ""
|
||||
hasEmbeddedCover = False
|
||||
|
||||
isTemp,filename = getRealFileName(file)
|
||||
logMsg( "getting song ID3 tags for " + filename)
|
||||
|
||||
try:
|
||||
###### FLAC FILES #############
|
||||
if filename.lower().endswith(".flac"):
|
||||
audio = FLAC(filename)
|
||||
if audio.get("comment"):
|
||||
comment = audio.get("comment")[0]
|
||||
for pic in audio.pictures:
|
||||
if pic.type == 3 and pic.data:
|
||||
#the file has an embedded cover
|
||||
hasEmbeddedCover = True
|
||||
break
|
||||
if audio.get("rating"):
|
||||
rating = float(audio.get("rating")[0])
|
||||
#flac rating is 0-100 and needs to be converted to 0-5 range
|
||||
if rating > 5: rating = (rating / 100) * 5
|
||||
|
||||
###### MP3 FILES #############
|
||||
elif filename.lower().endswith(".mp3"):
|
||||
audio = ID3(filename)
|
||||
|
||||
if audio.get("APIC:Front Cover"):
|
||||
if audio.get("APIC:Front Cover").data:
|
||||
hasEmbeddedCover = True
|
||||
|
||||
if audio.get("comment"):
|
||||
comment = audio.get("comment")[0]
|
||||
if audio.get("POPM:Windows Media Player 9 Series"):
|
||||
if audio.get("POPM:Windows Media Player 9 Series").rating:
|
||||
rating = float(audio.get("POPM:Windows Media Player 9 Series").rating)
|
||||
#POPM rating is 0-255 and needs to be converted to 0-5 range
|
||||
if rating > 5: rating = (rating / 255) * 5
|
||||
else:
|
||||
logMsg( "Not supported fileformat or unable to access file: %s" %(filename))
|
||||
|
||||
#the rating must be a round value
|
||||
rating = int(round(rating,0))
|
||||
|
||||
except Exception as e:
|
||||
#file in use ?
|
||||
utils.logMsg("Exception in getSongTags", str(e),0)
|
||||
rating = None
|
||||
|
||||
#remove tempfile if needed....
|
||||
if isTemp: xbmcvfs.delete(filename)
|
||||
|
||||
return (rating, comment, hasEmbeddedCover)
|
||||
|
||||
def updateRatingToFile(rating, file):
|
||||
#update the rating from Emby to the file
|
||||
|
||||
f = xbmcvfs.File(file)
|
||||
org_size = f.size()
|
||||
f.close()
|
||||
|
||||
#create tempfile
|
||||
if "/" in file: filepart = file.split("/")[-1]
|
||||
else: filepart = file.split("\\")[-1]
|
||||
tempfile = "special://temp/"+filepart
|
||||
xbmcvfs.copy(file, tempfile)
|
||||
tempfile = xbmc.translatePath(tempfile).decode("utf-8")
|
||||
|
||||
logMsg( "setting song rating: %s for filename: %s - using tempfile: %s" %(rating,file,tempfile))
|
||||
|
||||
if not tempfile:
|
||||
return
|
||||
|
||||
try:
|
||||
if tempfile.lower().endswith(".flac"):
|
||||
audio = FLAC(tempfile)
|
||||
calcrating = int(round((float(rating) / 5) * 100, 0))
|
||||
audio["rating"] = str(calcrating)
|
||||
audio.save()
|
||||
elif tempfile.lower().endswith(".mp3"):
|
||||
audio = ID3(tempfile)
|
||||
calcrating = int(round((float(rating) / 5) * 255, 0))
|
||||
audio.add(id3.POPM(email="Windows Media Player 9 Series", rating=calcrating, count=1))
|
||||
audio.save()
|
||||
else:
|
||||
logMsg( "Not supported fileformat: %s" %(tempfile))
|
||||
|
||||
#once we have succesfully written the flags we move the temp file to destination, otherwise not proceeding and just delete the temp
|
||||
#safety check: we check the file size of the temp file before proceeding with overwite of original file
|
||||
f = xbmcvfs.File(tempfile)
|
||||
checksum_size = f.size()
|
||||
f.close()
|
||||
if checksum_size >= org_size:
|
||||
xbmcvfs.delete(file)
|
||||
xbmcvfs.copy(tempfile,file)
|
||||
else:
|
||||
logMsg( "Checksum mismatch for filename: %s - using tempfile: %s - not proceeding with file overwite!" %(rating,file,tempfile))
|
||||
|
||||
#always delete the tempfile
|
||||
xbmcvfs.delete(tempfile)
|
||||
|
||||
except Exception as e:
|
||||
#file in use ?
|
||||
logMsg("Exception in updateRatingToFile %s" %e,0)
|
||||
|
||||
|
||||
|
|
@ -1,358 +1,346 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
#################################################################################################
|
||||
|
||||
import json
|
||||
import sys
|
||||
|
||||
import xbmc
|
||||
import xbmcgui
|
||||
import xbmcplugin
|
||||
|
||||
import api
|
||||
import artwork
|
||||
import clientinfo
|
||||
import downloadutils
|
||||
import playutils as putils
|
||||
import playlist
|
||||
import read_embyserver as embyserver
|
||||
import utils
|
||||
|
||||
#################################################################################################
|
||||
|
||||
|
||||
class PlaybackUtils():
|
||||
|
||||
|
||||
def __init__(self, item):
|
||||
|
||||
self.item = item
|
||||
self.API = api.API(self.item)
|
||||
|
||||
self.clientInfo = clientinfo.ClientInfo()
|
||||
self.addonName = self.clientInfo.getAddonName()
|
||||
self.doUtils = downloadutils.DownloadUtils().downloadUrl
|
||||
|
||||
self.userid = utils.window('emby_currUser')
|
||||
self.server = utils.window('emby_server%s' % self.userid)
|
||||
|
||||
self.artwork = artwork.Artwork()
|
||||
self.emby = embyserver.Read_EmbyServer()
|
||||
self.pl = playlist.Playlist()
|
||||
|
||||
def logMsg(self, msg, lvl=1):
|
||||
|
||||
self.className = self.__class__.__name__
|
||||
utils.logMsg("%s %s" % (self.addonName, self.className), msg, lvl)
|
||||
|
||||
|
||||
def play(self, itemid, dbid=None):
|
||||
|
||||
log = self.logMsg
|
||||
window = utils.window
|
||||
settings = utils.settings
|
||||
|
||||
doUtils = self.doUtils
|
||||
item = self.item
|
||||
API = self.API
|
||||
listitem = xbmcgui.ListItem()
|
||||
playutils = putils.PlayUtils(item)
|
||||
|
||||
log("Play called.", 1)
|
||||
playurl = playutils.getPlayUrl()
|
||||
if not playurl:
|
||||
return xbmcplugin.setResolvedUrl(int(sys.argv[1]), False, listitem)
|
||||
|
||||
if dbid is None:
|
||||
# Item is not in Kodi database
|
||||
listitem.setPath(playurl)
|
||||
self.setProperties(playurl, listitem)
|
||||
return xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, listitem)
|
||||
|
||||
############### ORGANIZE CURRENT PLAYLIST ################
|
||||
|
||||
homeScreen = xbmc.getCondVisibility('Window.IsActive(home)')
|
||||
playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
|
||||
startPos = max(playlist.getposition(), 0) # Can return -1
|
||||
sizePlaylist = playlist.size()
|
||||
currentPosition = startPos
|
||||
|
||||
propertiesPlayback = window('emby_playbackProps') == "true"
|
||||
introsPlaylist = False
|
||||
dummyPlaylist = False
|
||||
|
||||
log("Playlist start position: %s" % startPos, 2)
|
||||
log("Playlist plugin position: %s" % currentPosition, 2)
|
||||
log("Playlist size: %s" % sizePlaylist, 2)
|
||||
|
||||
############### RESUME POINT ################
|
||||
|
||||
userdata = API.getUserData()
|
||||
seektime = API.adjustResume(userdata['Resume'])
|
||||
|
||||
# We need to ensure we add the intro and additional parts only once.
|
||||
# Otherwise we get a loop.
|
||||
if not propertiesPlayback:
|
||||
|
||||
window('emby_playbackProps', value="true")
|
||||
log("Setting up properties in playlist.", 1)
|
||||
|
||||
if (not homeScreen and not seektime and
|
||||
window('emby_customPlaylist') != "true"):
|
||||
|
||||
log("Adding dummy file to playlist.", 2)
|
||||
dummyPlaylist = True
|
||||
playlist.add(playurl, listitem, index=startPos)
|
||||
# Remove the original item from playlist
|
||||
self.pl.removefromPlaylist(startPos+1)
|
||||
# Readd the original item to playlist - via jsonrpc so we have full metadata
|
||||
self.pl.insertintoPlaylist(currentPosition+1, dbid, item['Type'].lower())
|
||||
currentPosition += 1
|
||||
|
||||
############### -- CHECK FOR INTROS ################
|
||||
|
||||
if settings('enableCinema') == "true" and not seektime:
|
||||
# if we have any play them when the movie/show is not being resumed
|
||||
url = "{server}/emby/Users/{UserId}/Items/%s/Intros?format=json" % itemid
|
||||
intros = doUtils(url)
|
||||
|
||||
if intros['TotalRecordCount'] != 0:
|
||||
getTrailers = True
|
||||
|
||||
if settings('askCinema') == "true":
|
||||
resp = xbmcgui.Dialog().yesno("Emby for Kodi", utils.language(33016))
|
||||
if not resp:
|
||||
# User selected to not play trailers
|
||||
getTrailers = False
|
||||
log("Skip trailers.", 1)
|
||||
|
||||
if getTrailers:
|
||||
for intro in intros['Items']:
|
||||
# The server randomly returns intros, process them.
|
||||
introListItem = xbmcgui.ListItem()
|
||||
introPlayurl = putils.PlayUtils(intro).getPlayUrl()
|
||||
log("Adding Intro: %s" % introPlayurl, 1)
|
||||
|
||||
# Set listitem and properties for intros
|
||||
pbutils = PlaybackUtils(intro)
|
||||
pbutils.setProperties(introPlayurl, introListItem)
|
||||
|
||||
self.pl.insertintoPlaylist(currentPosition, url=introPlayurl)
|
||||
introsPlaylist = True
|
||||
currentPosition += 1
|
||||
|
||||
|
||||
############### -- ADD MAIN ITEM ONLY FOR HOMESCREEN ###############
|
||||
|
||||
if homeScreen and not seektime and not sizePlaylist:
|
||||
# Extend our current playlist with the actual item to play
|
||||
# only if there's no playlist first
|
||||
log("Adding main item to playlist.", 1)
|
||||
self.pl.addtoPlaylist(dbid, item['Type'].lower())
|
||||
|
||||
# Ensure that additional parts are played after the main item
|
||||
currentPosition += 1
|
||||
|
||||
############### -- CHECK FOR ADDITIONAL PARTS ################
|
||||
|
||||
if item.get('PartCount'):
|
||||
# Only add to the playlist after intros have played
|
||||
partcount = item['PartCount']
|
||||
url = "{server}/emby/Videos/%s/AdditionalParts?format=json" % itemid
|
||||
parts = doUtils(url)
|
||||
for part in parts['Items']:
|
||||
|
||||
additionalListItem = xbmcgui.ListItem()
|
||||
additionalPlayurl = putils.PlayUtils(part).getPlayUrl()
|
||||
log("Adding additional part: %s" % partcount, 1)
|
||||
|
||||
# Set listitem and properties for each additional parts
|
||||
pbutils = PlaybackUtils(part)
|
||||
pbutils.setProperties(additionalPlayurl, additionalListItem)
|
||||
pbutils.setArtwork(additionalListItem)
|
||||
|
||||
playlist.add(additionalPlayurl, additionalListItem, index=currentPosition)
|
||||
self.pl.verifyPlaylist()
|
||||
currentPosition += 1
|
||||
|
||||
if dummyPlaylist:
|
||||
# Added a dummy file to the playlist,
|
||||
# because the first item is going to fail automatically.
|
||||
log("Processed as a playlist. First item is skipped.", 1)
|
||||
return xbmcplugin.setResolvedUrl(int(sys.argv[1]), False, listitem)
|
||||
|
||||
|
||||
# We just skipped adding properties. Reset flag for next time.
|
||||
elif propertiesPlayback:
|
||||
log("Resetting properties playback flag.", 2)
|
||||
window('emby_playbackProps', clear=True)
|
||||
|
||||
#self.pl.verifyPlaylist()
|
||||
########## SETUP MAIN ITEM ##########
|
||||
|
||||
# For transcoding only, ask for audio/subs pref
|
||||
if window('emby_%s.playmethod' % playurl) == "Transcode":
|
||||
playurl = playutils.audioSubsPref(playurl, listitem)
|
||||
window('emby_%s.playmethod' % playurl, value="Transcode")
|
||||
|
||||
listitem.setPath(playurl)
|
||||
self.setProperties(playurl, listitem)
|
||||
|
||||
############### PLAYBACK ################
|
||||
|
||||
if homeScreen and seektime and window('emby_customPlaylist') != "true":
|
||||
log("Play as a widget item.", 1)
|
||||
self.setListItem(listitem)
|
||||
xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, listitem)
|
||||
|
||||
elif ((introsPlaylist and window('emby_customPlaylist') == "true") or
|
||||
(homeScreen and not sizePlaylist)):
|
||||
# Playlist was created just now, play it.
|
||||
log("Play playlist.", 1)
|
||||
xbmc.Player().play(playlist, startpos=startPos)
|
||||
|
||||
else:
|
||||
log("Play as a regular item.", 1)
|
||||
xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, listitem)
|
||||
|
||||
def setProperties(self, playurl, listitem):
|
||||
|
||||
window = utils.window
|
||||
# Set all properties necessary for plugin path playback
|
||||
item = self.item
|
||||
itemid = item['Id']
|
||||
itemtype = item['Type']
|
||||
|
||||
embyitem = "emby_%s" % playurl
|
||||
window('%s.runtime' % embyitem, value=str(item.get('RunTimeTicks')))
|
||||
window('%s.type' % embyitem, value=itemtype)
|
||||
window('%s.itemid' % embyitem, value=itemid)
|
||||
|
||||
if itemtype == "Episode":
|
||||
window('%s.refreshid' % embyitem, value=item.get('SeriesId'))
|
||||
else:
|
||||
window('%s.refreshid' % embyitem, value=itemid)
|
||||
|
||||
# Append external subtitles to stream
|
||||
playmethod = utils.window('%s.playmethod' % embyitem)
|
||||
# Only for direct stream
|
||||
if playmethod in ("DirectStream"):
|
||||
# Direct play automatically appends external
|
||||
subtitles = self.externalSubs(playurl)
|
||||
listitem.setSubtitles(subtitles)
|
||||
|
||||
self.setArtwork(listitem)
|
||||
|
||||
def externalSubs(self, playurl):
|
||||
|
||||
externalsubs = []
|
||||
mapping = {}
|
||||
|
||||
item = self.item
|
||||
itemid = item['Id']
|
||||
try:
|
||||
mediastreams = item['MediaSources'][0]['MediaStreams']
|
||||
except (TypeError, KeyError, IndexError):
|
||||
return
|
||||
|
||||
kodiindex = 0
|
||||
for stream in mediastreams:
|
||||
|
||||
index = stream['Index']
|
||||
# Since Emby returns all possible tracks together, have to pull only external subtitles.
|
||||
# IsTextSubtitleStream if true, is available to download from emby.
|
||||
if (stream['Type'] == "Subtitle" and
|
||||
stream['IsExternal'] and stream['IsTextSubtitleStream']):
|
||||
|
||||
# Direct stream
|
||||
url = ("%s/Videos/%s/%s/Subtitles/%s/Stream.srt"
|
||||
% (self.server, itemid, itemid, index))
|
||||
|
||||
# map external subtitles for mapping
|
||||
mapping[kodiindex] = index
|
||||
externalsubs.append(url)
|
||||
kodiindex += 1
|
||||
|
||||
mapping = json.dumps(mapping)
|
||||
utils.window('emby_%s.indexMapping' % playurl, value=mapping)
|
||||
|
||||
return externalsubs
|
||||
|
||||
def setArtwork(self, listItem):
|
||||
# Set up item and item info
|
||||
item = self.item
|
||||
artwork = self.artwork
|
||||
|
||||
allartwork = artwork.getAllArtwork(item, parentInfo=True)
|
||||
# Set artwork for listitem
|
||||
arttypes = {
|
||||
|
||||
'poster': "Primary",
|
||||
'tvshow.poster': "Primary",
|
||||
'clearart': "Art",
|
||||
'tvshow.clearart': "Art",
|
||||
'clearlogo': "Logo",
|
||||
'tvshow.clearlogo': "Logo",
|
||||
'discart': "Disc",
|
||||
'fanart_image': "Backdrop",
|
||||
'landscape': "Thumb"
|
||||
}
|
||||
for arttype in arttypes:
|
||||
|
||||
art = arttypes[arttype]
|
||||
if art == "Backdrop":
|
||||
try: # Backdrop is a list, grab the first backdrop
|
||||
self.setArtProp(listItem, arttype, allartwork[art][0])
|
||||
except: pass
|
||||
else:
|
||||
self.setArtProp(listItem, arttype, allartwork[art])
|
||||
|
||||
def setArtProp(self, listItem, arttype, path):
|
||||
|
||||
if arttype in (
|
||||
'thumb', 'fanart_image', 'small_poster', 'tiny_poster',
|
||||
'medium_landscape', 'medium_poster', 'small_fanartimage',
|
||||
'medium_fanartimage', 'fanart_noindicators'):
|
||||
|
||||
listItem.setProperty(arttype, path)
|
||||
else:
|
||||
listItem.setArt({arttype: path})
|
||||
|
||||
def setListItem(self, listItem):
|
||||
|
||||
item = self.item
|
||||
itemtype = item['Type']
|
||||
API = self.API
|
||||
people = API.getPeople()
|
||||
studios = API.getStudios()
|
||||
|
||||
metadata = {
|
||||
|
||||
'title': item.get('Name', "Missing name"),
|
||||
'year': item.get('ProductionYear'),
|
||||
'plot': API.getOverview(),
|
||||
'director': people.get('Director'),
|
||||
'writer': people.get('Writer'),
|
||||
'mpaa': API.getMpaa(),
|
||||
'genre': " / ".join(item['Genres']),
|
||||
'studio': " / ".join(studios),
|
||||
'aired': API.getPremiereDate(),
|
||||
'rating': item.get('CommunityRating'),
|
||||
'votes': item.get('VoteCount')
|
||||
}
|
||||
|
||||
if "Episode" in itemtype:
|
||||
# Only for tv shows
|
||||
thumbId = item.get('SeriesId')
|
||||
season = item.get('ParentIndexNumber', -1)
|
||||
episode = item.get('IndexNumber', -1)
|
||||
show = item.get('SeriesName', "")
|
||||
|
||||
metadata['TVShowTitle'] = show
|
||||
metadata['season'] = season
|
||||
metadata['episode'] = episode
|
||||
|
||||
listItem.setProperty('IsPlayable', 'true')
|
||||
listItem.setProperty('IsFolder', 'false')
|
||||
listItem.setLabel(metadata['title'])
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#################################################################################################
|
||||
|
||||
import json
|
||||
import sys
|
||||
|
||||
import xbmc
|
||||
import xbmcgui
|
||||
import xbmcplugin
|
||||
|
||||
import api
|
||||
import artwork
|
||||
import clientinfo
|
||||
import downloadutils
|
||||
import playutils as putils
|
||||
import playlist
|
||||
import read_embyserver as embyserver
|
||||
import utils
|
||||
|
||||
#################################################################################################
|
||||
|
||||
|
||||
class PlaybackUtils():
|
||||
|
||||
|
||||
def __init__(self, item):
|
||||
|
||||
self.item = item
|
||||
self.API = api.API(self.item)
|
||||
|
||||
self.clientInfo = clientinfo.ClientInfo()
|
||||
self.addonName = self.clientInfo.getAddonName()
|
||||
self.doUtils = downloadutils.DownloadUtils().downloadUrl
|
||||
|
||||
self.userid = utils.window('emby_currUser')
|
||||
self.server = utils.window('emby_server%s' % self.userid)
|
||||
|
||||
self.artwork = artwork.Artwork()
|
||||
self.emby = embyserver.Read_EmbyServer()
|
||||
self.pl = playlist.Playlist()
|
||||
|
||||
def logMsg(self, msg, lvl=1):
|
||||
|
||||
self.className = self.__class__.__name__
|
||||
utils.logMsg("%s %s" % (self.addonName, self.className), msg, lvl)
|
||||
|
||||
|
||||
def play(self, itemid, dbid=None):
|
||||
|
||||
window = utils.window
|
||||
settings = utils.settings
|
||||
|
||||
listitem = xbmcgui.ListItem()
|
||||
playutils = putils.PlayUtils(self.item)
|
||||
|
||||
self.logMsg("Play called.", 1)
|
||||
playurl = playutils.getPlayUrl()
|
||||
if not playurl:
|
||||
return xbmcplugin.setResolvedUrl(int(sys.argv[1]), False, listitem)
|
||||
|
||||
if dbid is None:
|
||||
# Item is not in Kodi database
|
||||
listitem.setPath(playurl)
|
||||
self.setProperties(playurl, listitem)
|
||||
return xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, listitem)
|
||||
|
||||
############### ORGANIZE CURRENT PLAYLIST ################
|
||||
|
||||
homeScreen = xbmc.getCondVisibility('Window.IsActive(home)')
|
||||
playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
|
||||
startPos = max(playlist.getposition(), 0) # Can return -1
|
||||
sizePlaylist = playlist.size()
|
||||
currentPosition = startPos
|
||||
|
||||
propertiesPlayback = window('emby_playbackProps') == "true"
|
||||
introsPlaylist = False
|
||||
dummyPlaylist = False
|
||||
|
||||
self.logMsg("Playlist start position: %s" % startPos, 2)
|
||||
self.logMsg("Playlist plugin position: %s" % currentPosition, 2)
|
||||
self.logMsg("Playlist size: %s" % sizePlaylist, 2)
|
||||
|
||||
############### RESUME POINT ################
|
||||
|
||||
userdata = self.API.getUserData()
|
||||
seektime = self.API.adjustResume(userdata['Resume'])
|
||||
|
||||
# We need to ensure we add the intro and additional parts only once.
|
||||
# Otherwise we get a loop.
|
||||
if not propertiesPlayback:
|
||||
|
||||
window('emby_playbackProps', value="true")
|
||||
self.logMsg("Setting up properties in playlist.", 1)
|
||||
|
||||
if (not homeScreen and not seektime and
|
||||
window('emby_customPlaylist') != "true"):
|
||||
|
||||
self.logMsg("Adding dummy file to playlist.", 2)
|
||||
dummyPlaylist = True
|
||||
playlist.add(playurl, listitem, index=startPos)
|
||||
# Remove the original item from playlist
|
||||
self.pl.removefromPlaylist(startPos+1)
|
||||
# Readd the original item to playlist - via jsonrpc so we have full metadata
|
||||
self.pl.insertintoPlaylist(currentPosition+1, dbid, self.item['Type'].lower())
|
||||
currentPosition += 1
|
||||
|
||||
############### -- CHECK FOR INTROS ################
|
||||
|
||||
if settings('enableCinema') == "true" and not seektime:
|
||||
# if we have any play them when the movie/show is not being resumed
|
||||
url = "{server}/emby/Users/{UserId}/Items/%s/Intros?format=json" % itemid
|
||||
intros = self.doUtils(url)
|
||||
|
||||
if intros['TotalRecordCount'] != 0:
|
||||
getTrailers = True
|
||||
|
||||
if settings('askCinema') == "true":
|
||||
resp = xbmcgui.Dialog().yesno("Emby for Kodi", utils.language(33016))
|
||||
if not resp:
|
||||
# User selected to not play trailers
|
||||
getTrailers = False
|
||||
self.logMsg("Skip trailers.", 1)
|
||||
|
||||
if getTrailers:
|
||||
for intro in intros['Items']:
|
||||
# The server randomly returns intros, process them.
|
||||
introListItem = xbmcgui.ListItem()
|
||||
introPlayurl = putils.PlayUtils(intro).getPlayUrl()
|
||||
self.logMsg("Adding Intro: %s" % introPlayurl, 1)
|
||||
|
||||
# Set listitem and properties for intros
|
||||
pbutils = PlaybackUtils(intro)
|
||||
pbutils.setProperties(introPlayurl, introListItem)
|
||||
|
||||
self.pl.insertintoPlaylist(currentPosition, url=introPlayurl)
|
||||
introsPlaylist = True
|
||||
currentPosition += 1
|
||||
|
||||
|
||||
############### -- ADD MAIN ITEM ONLY FOR HOMESCREEN ###############
|
||||
|
||||
if homeScreen and not seektime and not sizePlaylist:
|
||||
# Extend our current playlist with the actual item to play
|
||||
# only if there's no playlist first
|
||||
self.logMsg("Adding main item to playlist.", 1)
|
||||
self.pl.addtoPlaylist(dbid, self.item['Type'].lower())
|
||||
|
||||
# Ensure that additional parts are played after the main item
|
||||
currentPosition += 1
|
||||
|
||||
############### -- CHECK FOR ADDITIONAL PARTS ################
|
||||
|
||||
if self.item.get('PartCount'):
|
||||
# Only add to the playlist after intros have played
|
||||
partcount = self.item['PartCount']
|
||||
url = "{server}/emby/Videos/%s/AdditionalParts?format=json" % itemid
|
||||
parts = self.doUtils(url)
|
||||
for part in parts['Items']:
|
||||
|
||||
additionalListItem = xbmcgui.ListItem()
|
||||
additionalPlayurl = putils.PlayUtils(part).getPlayUrl()
|
||||
self.logMsg("Adding additional part: %s" % partcount, 1)
|
||||
|
||||
# Set listitem and properties for each additional parts
|
||||
pbutils = PlaybackUtils(part)
|
||||
pbutils.setProperties(additionalPlayurl, additionalListItem)
|
||||
pbutils.setArtwork(additionalListItem)
|
||||
|
||||
playlist.add(additionalPlayurl, additionalListItem, index=currentPosition)
|
||||
self.pl.verifyPlaylist()
|
||||
currentPosition += 1
|
||||
|
||||
if dummyPlaylist:
|
||||
# Added a dummy file to the playlist,
|
||||
# because the first item is going to fail automatically.
|
||||
self.logMsg("Processed as a playlist. First item is skipped.", 1)
|
||||
return xbmcplugin.setResolvedUrl(int(sys.argv[1]), False, listitem)
|
||||
|
||||
|
||||
# We just skipped adding properties. Reset flag for next time.
|
||||
elif propertiesPlayback:
|
||||
self.logMsg("Resetting properties playback flag.", 2)
|
||||
window('emby_playbackProps', clear=True)
|
||||
|
||||
#self.pl.verifyPlaylist()
|
||||
########## SETUP MAIN ITEM ##########
|
||||
|
||||
# For transcoding only, ask for audio/subs pref
|
||||
if window('emby_%s.playmethod' % playurl) == "Transcode":
|
||||
playurl = playutils.audioSubsPref(playurl, listitem)
|
||||
window('emby_%s.playmethod' % playurl, value="Transcode")
|
||||
|
||||
listitem.setPath(playurl)
|
||||
self.setProperties(playurl, listitem)
|
||||
|
||||
############### PLAYBACK ################
|
||||
|
||||
if homeScreen and seektime and window('emby_customPlaylist') != "true":
|
||||
self.logMsg("Play as a widget item.", 1)
|
||||
self.setListItem(listitem)
|
||||
xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, listitem)
|
||||
|
||||
elif ((introsPlaylist and window('emby_customPlaylist') == "true") or
|
||||
(homeScreen and not sizePlaylist)):
|
||||
# Playlist was created just now, play it.
|
||||
self.logMsg("Play playlist.", 1)
|
||||
xbmc.Player().play(playlist, startpos=startPos)
|
||||
|
||||
else:
|
||||
self.logMsg("Play as a regular item.", 1)
|
||||
xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, listitem)
|
||||
|
||||
def setProperties(self, playurl, listitem):
|
||||
|
||||
window = utils.window
|
||||
# Set all properties necessary for plugin path playback
|
||||
itemid = self.item['Id']
|
||||
itemtype = self.item['Type']
|
||||
|
||||
embyitem = "emby_%s" % playurl
|
||||
window('%s.runtime' % embyitem, value=str(self.item.get('RunTimeTicks')))
|
||||
window('%s.type' % embyitem, value=itemtype)
|
||||
window('%s.itemid' % embyitem, value=itemid)
|
||||
|
||||
if itemtype == "Episode":
|
||||
window('%s.refreshid' % embyitem, value=self.item.get('SeriesId'))
|
||||
else:
|
||||
window('%s.refreshid' % embyitem, value=itemid)
|
||||
|
||||
# Append external subtitles to stream
|
||||
playmethod = utils.window('%s.playmethod' % embyitem)
|
||||
# Only for direct stream
|
||||
if playmethod in ("DirectStream"):
|
||||
# Direct play automatically appends external
|
||||
subtitles = self.externalSubs(playurl)
|
||||
listitem.setSubtitles(subtitles)
|
||||
|
||||
self.setArtwork(listitem)
|
||||
|
||||
def externalSubs(self, playurl):
|
||||
|
||||
externalsubs = []
|
||||
mapping = {}
|
||||
|
||||
itemid = self.item['Id']
|
||||
try:
|
||||
mediastreams = self.item['MediaSources'][0]['MediaStreams']
|
||||
except (TypeError, KeyError, IndexError):
|
||||
return
|
||||
|
||||
kodiindex = 0
|
||||
for stream in mediastreams:
|
||||
|
||||
index = stream['Index']
|
||||
# Since Emby returns all possible tracks together, have to pull only external subtitles.
|
||||
# IsTextSubtitleStream if true, is available to download from emby.
|
||||
if (stream['Type'] == "Subtitle" and
|
||||
stream['IsExternal'] and stream['IsTextSubtitleStream']):
|
||||
|
||||
# Direct stream
|
||||
url = ("%s/Videos/%s/%s/Subtitles/%s/Stream.srt"
|
||||
% (self.server, itemid, itemid, index))
|
||||
|
||||
# map external subtitles for mapping
|
||||
mapping[kodiindex] = index
|
||||
externalsubs.append(url)
|
||||
kodiindex += 1
|
||||
|
||||
mapping = json.dumps(mapping)
|
||||
utils.window('emby_%s.indexMapping' % playurl, value=mapping)
|
||||
|
||||
return externalsubs
|
||||
|
||||
def setArtwork(self, listItem):
|
||||
# Set up item and item info
|
||||
allartwork = self.artwork.getAllArtwork(self.item, parentInfo=True)
|
||||
# Set artwork for listitem
|
||||
arttypes = {
|
||||
|
||||
'poster': "Primary",
|
||||
'tvshow.poster': "Primary",
|
||||
'clearart': "Art",
|
||||
'tvshow.clearart': "Art",
|
||||
'clearlogo': "Logo",
|
||||
'tvshow.clearlogo': "Logo",
|
||||
'discart': "Disc",
|
||||
'fanart_image': "Backdrop",
|
||||
'landscape': "Thumb"
|
||||
}
|
||||
for arttype in arttypes:
|
||||
|
||||
art = arttypes[arttype]
|
||||
if art == "Backdrop":
|
||||
try: # Backdrop is a list, grab the first backdrop
|
||||
self.setArtProp(listItem, arttype, allartwork[art][0])
|
||||
except: pass
|
||||
else:
|
||||
self.setArtProp(listItem, arttype, allartwork[art])
|
||||
|
||||
def setArtProp(self, listItem, arttype, path):
|
||||
|
||||
if arttype in (
|
||||
'thumb', 'fanart_image', 'small_poster', 'tiny_poster',
|
||||
'medium_landscape', 'medium_poster', 'small_fanartimage',
|
||||
'medium_fanartimage', 'fanart_noindicators'):
|
||||
|
||||
listItem.setProperty(arttype, path)
|
||||
else:
|
||||
listItem.setArt({arttype: path})
|
||||
|
||||
def setListItem(self, listItem):
|
||||
|
||||
people = self.API.getPeople()
|
||||
studios = self.API.getStudios()
|
||||
|
||||
metadata = {
|
||||
|
||||
'title': self.item.get('Name', "Missing name"),
|
||||
'year': self.item.get('ProductionYear'),
|
||||
'plot': self.API.getOverview(),
|
||||
'director': people.get('Director'),
|
||||
'writer': people.get('Writer'),
|
||||
'mpaa': self.API.getMpaa(),
|
||||
'genre': " / ".join(self.item['Genres']),
|
||||
'studio': " / ".join(studios),
|
||||
'aired': self.API.getPremiereDate(),
|
||||
'rating': self.item.get('CommunityRating'),
|
||||
'votes': self.item.get('VoteCount')
|
||||
}
|
||||
|
||||
if "Episode" in self.item['Type']:
|
||||
# Only for tv shows
|
||||
thumbId = self.item.get('SeriesId')
|
||||
season = self.item.get('ParentIndexNumber', -1)
|
||||
episode = self.item.get('IndexNumber', -1)
|
||||
show = self.item.get('SeriesName', "")
|
||||
|
||||
metadata['TVShowTitle'] = show
|
||||
metadata['season'] = season
|
||||
metadata['episode'] = episode
|
||||
|
||||
listItem.setProperty('IsPlayable', 'true')
|
||||
listItem.setProperty('IsFolder', 'false')
|
||||
listItem.setLabel(metadata['title'])
|
||||
listItem.setInfo('video', infoLabels=metadata)
|
File diff suppressed because it is too large
Load diff
|
@ -1,204 +1,196 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
#################################################################################################
|
||||
|
||||
import json
|
||||
|
||||
import xbmc
|
||||
import xbmcgui
|
||||
import xbmcplugin
|
||||
|
||||
import clientinfo
|
||||
import playutils
|
||||
import playbackutils
|
||||
import embydb_functions as embydb
|
||||
import read_embyserver as embyserver
|
||||
import utils
|
||||
|
||||
#################################################################################################
|
||||
|
||||
|
||||
class Playlist():
|
||||
|
||||
|
||||
def __init__(self):
|
||||
|
||||
self.clientInfo = clientinfo.ClientInfo()
|
||||
self.addonName = self.clientInfo.getAddonName()
|
||||
|
||||
self.userid = utils.window('emby_currUser')
|
||||
self.server = utils.window('emby_server%s' % self.userid)
|
||||
|
||||
self.emby = embyserver.Read_EmbyServer()
|
||||
|
||||
def logMsg(self, msg, lvl=1):
|
||||
|
||||
self.className = self.__class__.__name__
|
||||
utils.logMsg("%s %s" % (self.addonName, self.className), msg, lvl)
|
||||
|
||||
|
||||
def playAll(self, itemids, startat):
|
||||
|
||||
log = self.logMsg
|
||||
window = utils.window
|
||||
|
||||
embyconn = utils.kodiSQL('emby')
|
||||
embycursor = embyconn.cursor()
|
||||
emby_db = embydb.Embydb_Functions(embycursor)
|
||||
|
||||
player = xbmc.Player()
|
||||
playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
|
||||
playlist.clear()
|
||||
|
||||
log("---*** PLAY ALL ***---", 1)
|
||||
log("Items: %s and start at: %s" % (itemids, startat), 1)
|
||||
|
||||
started = False
|
||||
window('emby_customplaylist', value="true")
|
||||
|
||||
if startat != 0:
|
||||
# Seek to the starting position
|
||||
window('emby_customplaylist.seektime', str(startat))
|
||||
|
||||
for itemid in itemids:
|
||||
embydb_item = emby_db.getItem_byId(itemid)
|
||||
try:
|
||||
dbid = embydb_item[0]
|
||||
mediatype = embydb_item[4]
|
||||
except TypeError:
|
||||
# Item is not found in our database, add item manually
|
||||
log("Item was not found in the database, manually adding item.", 1)
|
||||
item = self.emby.getItem(itemid)
|
||||
self.addtoPlaylist_xbmc(playlist, item)
|
||||
else:
|
||||
# Add to playlist
|
||||
self.addtoPlaylist(dbid, mediatype)
|
||||
|
||||
log("Adding %s to playlist." % itemid, 1)
|
||||
|
||||
if not started:
|
||||
started = True
|
||||
player.play(playlist)
|
||||
|
||||
self.verifyPlaylist()
|
||||
embycursor.close()
|
||||
|
||||
def modifyPlaylist(self, itemids):
|
||||
|
||||
log = self.logMsg
|
||||
|
||||
embyconn = utils.kodiSQL('emby')
|
||||
embycursor = embyconn.cursor()
|
||||
emby_db = embydb.Embydb_Functions(embycursor)
|
||||
|
||||
log("---*** ADD TO PLAYLIST ***---", 1)
|
||||
log("Items: %s" % itemids, 1)
|
||||
|
||||
player = xbmc.Player()
|
||||
playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
|
||||
|
||||
for itemid in itemids:
|
||||
embydb_item = emby_db.getItem_byId(itemid)
|
||||
try:
|
||||
dbid = embydb_item[0]
|
||||
mediatype = embydb_item[4]
|
||||
except TypeError:
|
||||
# Item is not found in our database, add item manually
|
||||
item = self.emby.getItem(itemid)
|
||||
self.addtoPlaylist_xbmc(playlist, item)
|
||||
else:
|
||||
# Add to playlist
|
||||
self.addtoPlaylist(dbid, mediatype)
|
||||
|
||||
log("Adding %s to playlist." % itemid, 1)
|
||||
|
||||
self.verifyPlaylist()
|
||||
embycursor.close()
|
||||
return playlist
|
||||
|
||||
def addtoPlaylist(self, dbid=None, mediatype=None, url=None):
|
||||
|
||||
pl = {
|
||||
|
||||
'jsonrpc': "2.0",
|
||||
'id': 1,
|
||||
'method': "Playlist.Add",
|
||||
'params': {
|
||||
|
||||
'playlistid': 1
|
||||
}
|
||||
}
|
||||
if dbid is not None:
|
||||
pl['params']['item'] = {'%sid' % mediatype: int(dbid)}
|
||||
else:
|
||||
pl['params']['item'] = {'file': url}
|
||||
|
||||
result = xbmc.executeJSONRPC(json.dumps(pl))
|
||||
self.logMsg(result, 2)
|
||||
|
||||
def addtoPlaylist_xbmc(self, playlist, item):
|
||||
|
||||
itemid = item['Id']
|
||||
playurl = playutils.PlayUtils(item).getPlayUrl()
|
||||
if not playurl:
|
||||
# Playurl failed
|
||||
self.logMsg("Failed to retrieve playurl.", 1)
|
||||
return
|
||||
|
||||
self.logMsg("Playurl: %s" % playurl)
|
||||
listitem = xbmcgui.ListItem()
|
||||
playbackutils.PlaybackUtils(item).setProperties(playurl, listitem)
|
||||
|
||||
playlist.add(playurl, listitem)
|
||||
|
||||
def insertintoPlaylist(self, position, dbid=None, mediatype=None, url=None):
|
||||
|
||||
pl = {
|
||||
|
||||
'jsonrpc': "2.0",
|
||||
'id': 1,
|
||||
'method': "Playlist.Insert",
|
||||
'params': {
|
||||
|
||||
'playlistid': 1,
|
||||
'position': position
|
||||
}
|
||||
}
|
||||
if dbid is not None:
|
||||
pl['params']['item'] = {'%sid' % mediatype: int(dbid)}
|
||||
else:
|
||||
pl['params']['item'] = {'file': url}
|
||||
|
||||
result = xbmc.executeJSONRPC(json.dumps(pl))
|
||||
self.logMsg(result, 2)
|
||||
|
||||
def verifyPlaylist(self):
|
||||
|
||||
pl = {
|
||||
|
||||
'jsonrpc': "2.0",
|
||||
'id': 1,
|
||||
'method': "Playlist.GetItems",
|
||||
'params': {
|
||||
|
||||
'playlistid': 1
|
||||
}
|
||||
}
|
||||
result = xbmc.executeJSONRPC(json.dumps(pl))
|
||||
self.logMsg(result, 2)
|
||||
|
||||
def removefromPlaylist(self, position):
|
||||
|
||||
pl = {
|
||||
|
||||
'jsonrpc': "2.0",
|
||||
'id': 1,
|
||||
'method': "Playlist.Remove",
|
||||
'params': {
|
||||
|
||||
'playlistid': 1,
|
||||
'position': position
|
||||
}
|
||||
}
|
||||
result = xbmc.executeJSONRPC(json.dumps(pl))
|
||||
self.logMsg(result, 2)
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#################################################################################################
|
||||
|
||||
import json
|
||||
|
||||
import xbmc
|
||||
import xbmcgui
|
||||
import xbmcplugin
|
||||
|
||||
import clientinfo
|
||||
import playutils
|
||||
import playbackutils
|
||||
import embydb_functions as embydb
|
||||
import read_embyserver as embyserver
|
||||
import utils
|
||||
|
||||
#################################################################################################
|
||||
|
||||
|
||||
class Playlist():
|
||||
|
||||
|
||||
def __init__(self):
|
||||
|
||||
self.clientInfo = clientinfo.ClientInfo()
|
||||
self.addonName = self.clientInfo.getAddonName()
|
||||
|
||||
self.userid = utils.window('emby_currUser')
|
||||
self.server = utils.window('emby_server%s' % self.userid)
|
||||
|
||||
self.emby = embyserver.Read_EmbyServer()
|
||||
|
||||
def logMsg(self, msg, lvl=1):
|
||||
|
||||
self.className = self.__class__.__name__
|
||||
utils.logMsg("%s %s" % (self.addonName, self.className), msg, lvl)
|
||||
|
||||
|
||||
def playAll(self, itemids, startat):
|
||||
|
||||
window = utils.window
|
||||
|
||||
embyconn = utils.kodiSQL('emby')
|
||||
embycursor = embyconn.cursor()
|
||||
emby_db = embydb.Embydb_Functions(embycursor)
|
||||
|
||||
player = xbmc.Player()
|
||||
playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
|
||||
playlist.clear()
|
||||
|
||||
self.logMsg("---*** PLAY ALL ***---", 1)
|
||||
self.logMsg("Items: %s and start at: %s" % (itemids, startat), 1)
|
||||
|
||||
started = False
|
||||
window('emby_customplaylist', value="true")
|
||||
|
||||
if startat != 0:
|
||||
# Seek to the starting position
|
||||
window('emby_customplaylist.seektime', str(startat))
|
||||
|
||||
for itemid in itemids:
|
||||
embydb_item = emby_db.getItem_byId(itemid)
|
||||
try:
|
||||
dbid = embydb_item[0]
|
||||
mediatype = embydb_item[4]
|
||||
except TypeError:
|
||||
# Item is not found in our database, add item manually
|
||||
self.logMsg("Item was not found in the database, manually adding item.", 1)
|
||||
item = self.emby.getItem(itemid)
|
||||
self.addtoPlaylist_xbmc(playlist, item)
|
||||
else:
|
||||
# Add to playlist
|
||||
self.addtoPlaylist(dbid, mediatype)
|
||||
|
||||
self.logMsg("Adding %s to playlist." % itemid, 1)
|
||||
|
||||
if not started:
|
||||
started = True
|
||||
player.play(playlist)
|
||||
|
||||
self.verifyPlaylist()
|
||||
embycursor.close()
|
||||
|
||||
def modifyPlaylist(self, itemids):
|
||||
|
||||
embyconn = utils.kodiSQL('emby')
|
||||
embycursor = embyconn.cursor()
|
||||
emby_db = embydb.Embydb_Functions(embycursor)
|
||||
|
||||
self.logMsg("---*** ADD TO PLAYLIST ***---", 1)
|
||||
self.logMsg("Items: %s" % itemids, 1)
|
||||
|
||||
player = xbmc.Player()
|
||||
playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
|
||||
|
||||
for itemid in itemids:
|
||||
embydb_item = emby_db.getItem_byId(itemid)
|
||||
try:
|
||||
dbid = embydb_item[0]
|
||||
mediatype = embydb_item[4]
|
||||
except TypeError:
|
||||
# Item is not found in our database, add item manually
|
||||
item = self.emby.getItem(itemid)
|
||||
self.addtoPlaylist_xbmc(playlist, item)
|
||||
else:
|
||||
# Add to playlist
|
||||
self.addtoPlaylist(dbid, mediatype)
|
||||
|
||||
self.logMsg("Adding %s to playlist." % itemid, 1)
|
||||
|
||||
self.verifyPlaylist()
|
||||
embycursor.close()
|
||||
return playlist
|
||||
|
||||
def addtoPlaylist(self, dbid=None, mediatype=None, url=None):
|
||||
|
||||
pl = {
|
||||
|
||||
'jsonrpc': "2.0",
|
||||
'id': 1,
|
||||
'method': "Playlist.Add",
|
||||
'params': {
|
||||
|
||||
'playlistid': 1
|
||||
}
|
||||
}
|
||||
if dbid is not None:
|
||||
pl['params']['item'] = {'%sid' % mediatype: int(dbid)}
|
||||
else:
|
||||
pl['params']['item'] = {'file': url}
|
||||
|
||||
self.logMsg(xbmc.executeJSONRPC(json.dumps(pl)), 2)
|
||||
|
||||
def addtoPlaylist_xbmc(self, playlist, item):
|
||||
|
||||
playurl = playutils.PlayUtils(item).getPlayUrl()
|
||||
if not playurl:
|
||||
# Playurl failed
|
||||
self.logMsg("Failed to retrieve playurl.", 1)
|
||||
return
|
||||
|
||||
self.logMsg("Playurl: %s" % playurl)
|
||||
listitem = xbmcgui.ListItem()
|
||||
playbackutils.PlaybackUtils(item).setProperties(playurl, listitem)
|
||||
|
||||
playlist.add(playurl, listitem)
|
||||
|
||||
def insertintoPlaylist(self, position, dbid=None, mediatype=None, url=None):
|
||||
|
||||
pl = {
|
||||
|
||||
'jsonrpc': "2.0",
|
||||
'id': 1,
|
||||
'method': "Playlist.Insert",
|
||||
'params': {
|
||||
|
||||
'playlistid': 1,
|
||||
'position': position
|
||||
}
|
||||
}
|
||||
if dbid is not None:
|
||||
pl['params']['item'] = {'%sid' % mediatype: int(dbid)}
|
||||
else:
|
||||
pl['params']['item'] = {'file': url}
|
||||
|
||||
self.logMsg(xbmc.executeJSONRPC(json.dumps(pl)), 2)
|
||||
|
||||
def verifyPlaylist(self):
|
||||
|
||||
pl = {
|
||||
|
||||
'jsonrpc': "2.0",
|
||||
'id': 1,
|
||||
'method': "Playlist.GetItems",
|
||||
'params': {
|
||||
|
||||
'playlistid': 1
|
||||
}
|
||||
}
|
||||
self.logMsg(xbmc.executeJSONRPC(json.dumps(pl)), 2)
|
||||
|
||||
def removefromPlaylist(self, position):
|
||||
|
||||
pl = {
|
||||
|
||||
'jsonrpc': "2.0",
|
||||
'id': 1,
|
||||
'method': "Playlist.Remove",
|
||||
'params': {
|
||||
|
||||
'playlistid': 1,
|
||||
'position': position
|
||||
}
|
||||
}
|
||||
self.logMsg(xbmc.executeJSONRPC(json.dumps(pl)), 2)
|
||||
|
|
|
@ -1,458 +1,426 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
#################################################################################################
|
||||
|
||||
import xbmc
|
||||
import xbmcgui
|
||||
import xbmcvfs
|
||||
|
||||
import clientinfo
|
||||
import utils
|
||||
|
||||
#################################################################################################
|
||||
|
||||
|
||||
class PlayUtils():
|
||||
|
||||
|
||||
def __init__(self, item):
|
||||
|
||||
self.item = item
|
||||
|
||||
self.clientInfo = clientinfo.ClientInfo()
|
||||
self.addonName = self.clientInfo.getAddonName()
|
||||
|
||||
self.userid = utils.window('emby_currUser')
|
||||
self.server = utils.window('emby_server%s' % self.userid)
|
||||
|
||||
def logMsg(self, msg, lvl=1):
|
||||
|
||||
self.className = self.__class__.__name__
|
||||
utils.logMsg("%s %s" % (self.addonName, self.className), msg, lvl)
|
||||
|
||||
|
||||
def getPlayUrl(self):
|
||||
|
||||
log = self.logMsg
|
||||
window = utils.window
|
||||
|
||||
item = self.item
|
||||
playurl = None
|
||||
|
||||
if (item.get('Type') in ("Recording", "TvChannel") and
|
||||
item.get('MediaSources') and item['MediaSources'][0]['Protocol'] == "Http"):
|
||||
# Play LiveTV or recordings
|
||||
log("File protocol is http (livetv).", 1)
|
||||
playurl = "%s/emby/Videos/%s/live.m3u8?static=true" % (self.server, item['Id'])
|
||||
window('emby_%s.playmethod' % playurl, value="Transcode")
|
||||
|
||||
elif item.get('MediaSources') and item['MediaSources'][0]['Protocol'] == "Http":
|
||||
# Only play as http, used for channels, or online hosting of content
|
||||
log("File protocol is http.", 1)
|
||||
playurl = self.httpPlay()
|
||||
window('emby_%s.playmethod' % playurl, value="DirectStream")
|
||||
|
||||
elif self.isDirectPlay():
|
||||
|
||||
log("File is direct playing.", 1)
|
||||
playurl = self.directPlay()
|
||||
playurl = playurl.encode('utf-8')
|
||||
# Set playmethod property
|
||||
window('emby_%s.playmethod' % playurl, value="DirectPlay")
|
||||
|
||||
elif self.isDirectStream():
|
||||
|
||||
log("File is direct streaming.", 1)
|
||||
playurl = self.directStream()
|
||||
# Set playmethod property
|
||||
window('emby_%s.playmethod' % playurl, value="DirectStream")
|
||||
|
||||
elif self.isTranscoding():
|
||||
|
||||
log("File is transcoding.", 1)
|
||||
playurl = self.transcoding()
|
||||
# Set playmethod property
|
||||
window('emby_%s.playmethod' % playurl, value="Transcode")
|
||||
|
||||
return playurl
|
||||
|
||||
def httpPlay(self):
|
||||
# Audio, Video, Photo
|
||||
item = self.item
|
||||
server = self.server
|
||||
|
||||
itemid = item['Id']
|
||||
mediatype = item['MediaType']
|
||||
|
||||
if mediatype == "Audio":
|
||||
playurl = "%s/emby/Audio/%s/stream" % (server, itemid)
|
||||
else:
|
||||
playurl = "%s/emby/Videos/%s/stream?static=true" % (server, itemid)
|
||||
|
||||
return playurl
|
||||
|
||||
def isDirectPlay(self):
|
||||
|
||||
log = self.logMsg
|
||||
lang = utils.language
|
||||
settings = utils.settings
|
||||
dialog = xbmcgui.Dialog()
|
||||
|
||||
item = self.item
|
||||
|
||||
# Requirement: Filesystem, Accessible path
|
||||
if settings('playFromStream') == "true":
|
||||
# User forcing to play via HTTP
|
||||
log("Can't direct play, play from HTTP enabled.", 1)
|
||||
return False
|
||||
|
||||
videotrack = item['MediaSources'][0]['Name']
|
||||
transcodeH265 = settings('transcodeH265')
|
||||
|
||||
if transcodeH265 in ("1", "2", "3") and ("HEVC" in videotrack or "H265" in videotrack):
|
||||
# Avoid H265/HEVC depending on the resolution
|
||||
resolution = int(videotrack.split("P", 1)[0])
|
||||
res = {
|
||||
|
||||
'1': 480,
|
||||
'2': 720,
|
||||
'3': 1080
|
||||
}
|
||||
log("Resolution is: %sP, transcode for resolution: %sP+"
|
||||
% (resolution, res[transcodeH265]), 1)
|
||||
if res[transcodeH265] <= resolution:
|
||||
return False
|
||||
|
||||
canDirectPlay = item['MediaSources'][0]['SupportsDirectPlay']
|
||||
# Make sure direct play is supported by the server
|
||||
if not canDirectPlay:
|
||||
log("Can't direct play, server doesn't allow/support it.", 1)
|
||||
return False
|
||||
|
||||
location = item['LocationType']
|
||||
if location == "FileSystem":
|
||||
# Verify the path
|
||||
if not self.fileExists():
|
||||
log("Unable to direct play.")
|
||||
try:
|
||||
count = int(settings('failCount'))
|
||||
except ValueError:
|
||||
count = 0
|
||||
log("Direct play failed: %s times." % count, 1)
|
||||
|
||||
if count < 2:
|
||||
# Let the user know that direct play failed
|
||||
settings('failCount', value=str(count+1))
|
||||
dialog.notification(
|
||||
heading="Emby for Kodi",
|
||||
message=lang(33011),
|
||||
icon="special://home/addons/plugin.video.emby/icon.png",
|
||||
sound=False)
|
||||
elif settings('playFromStream') != "true":
|
||||
# Permanently set direct stream as true
|
||||
settings('playFromStream', value="true")
|
||||
settings('failCount', value="0")
|
||||
dialog.notification(
|
||||
heading="Emby for Kodi",
|
||||
message=lang(33012),
|
||||
icon="special://home/addons/plugin.video.emby/icon.png",
|
||||
sound=False)
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def directPlay(self):
|
||||
|
||||
item = self.item
|
||||
|
||||
try:
|
||||
playurl = item['MediaSources'][0]['Path']
|
||||
except (IndexError, KeyError):
|
||||
playurl = item['Path']
|
||||
|
||||
if item.get('VideoType'):
|
||||
# Specific format modification
|
||||
type = item['VideoType']
|
||||
|
||||
if type == "Dvd":
|
||||
playurl = "%s/VIDEO_TS/VIDEO_TS.IFO" % playurl
|
||||
elif type == "BluRay":
|
||||
playurl = "%s/BDMV/index.bdmv" % playurl
|
||||
|
||||
# Assign network protocol
|
||||
if playurl.startswith('\\\\'):
|
||||
playurl = playurl.replace("\\\\", "smb://")
|
||||
playurl = playurl.replace("\\", "/")
|
||||
|
||||
if "apple.com" in playurl:
|
||||
USER_AGENT = "QuickTime/7.7.4"
|
||||
playurl += "?|User-Agent=%s" % USER_AGENT
|
||||
|
||||
return playurl
|
||||
|
||||
def fileExists(self):
|
||||
|
||||
log = self.logMsg
|
||||
|
||||
if 'Path' not in self.item:
|
||||
# File has no path defined in server
|
||||
return False
|
||||
|
||||
# Convert path to direct play
|
||||
path = self.directPlay()
|
||||
log("Verifying path: %s" % path, 1)
|
||||
|
||||
if xbmcvfs.exists(path):
|
||||
log("Path exists.", 1)
|
||||
return True
|
||||
|
||||
elif ":" not in path:
|
||||
log("Can't verify path, assumed linux. Still try to direct play.", 1)
|
||||
return True
|
||||
|
||||
else:
|
||||
log("Failed to find file.", 1)
|
||||
return False
|
||||
|
||||
def isDirectStream(self):
|
||||
|
||||
log = self.logMsg
|
||||
|
||||
item = self.item
|
||||
|
||||
videotrack = item['MediaSources'][0]['Name']
|
||||
transcodeH265 = utils.settings('transcodeH265')
|
||||
|
||||
if transcodeH265 in ("1", "2", "3") and ("HEVC" in videotrack or "H265" in videotrack):
|
||||
# Avoid H265/HEVC depending on the resolution
|
||||
resolution = int(videotrack.split("P", 1)[0])
|
||||
res = {
|
||||
|
||||
'1': 480,
|
||||
'2': 720,
|
||||
'3': 1080
|
||||
}
|
||||
log("Resolution is: %sP, transcode for resolution: %sP+"
|
||||
% (resolution, res[transcodeH265]), 1)
|
||||
if res[transcodeH265] <= resolution:
|
||||
return False
|
||||
|
||||
# Requirement: BitRate, supported encoding
|
||||
canDirectStream = item['MediaSources'][0]['SupportsDirectStream']
|
||||
# Make sure the server supports it
|
||||
if not canDirectStream:
|
||||
return False
|
||||
|
||||
# Verify the bitrate
|
||||
if not self.isNetworkSufficient():
|
||||
log("The network speed is insufficient to direct stream file.", 1)
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def directStream(self):
|
||||
|
||||
item = self.item
|
||||
server = self.server
|
||||
|
||||
itemid = item['Id']
|
||||
itemtype = item['Type']
|
||||
|
||||
if 'Path' in item and item['Path'].endswith('.strm'):
|
||||
# Allow strm loading when direct streaming
|
||||
playurl = self.directPlay()
|
||||
elif itemtype == "Audio":
|
||||
playurl = "%s/emby/Audio/%s/stream.mp3" % (server, itemid)
|
||||
else:
|
||||
playurl = "%s/emby/Videos/%s/stream?static=true" % (server, itemid)
|
||||
|
||||
return playurl
|
||||
|
||||
def isNetworkSufficient(self):
|
||||
|
||||
log = self.logMsg
|
||||
|
||||
settings = self.getBitrate()*1000
|
||||
|
||||
try:
|
||||
sourceBitrate = int(self.item['MediaSources'][0]['Bitrate'])
|
||||
except (KeyError, TypeError):
|
||||
log("Bitrate value is missing.", 1)
|
||||
else:
|
||||
log("The add-on settings bitrate is: %s, the video bitrate required is: %s"
|
||||
% (settings, sourceBitrate), 1)
|
||||
if settings < sourceBitrate:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def isTranscoding(self):
|
||||
|
||||
item = self.item
|
||||
|
||||
canTranscode = item['MediaSources'][0]['SupportsTranscoding']
|
||||
# Make sure the server supports it
|
||||
if not canTranscode:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def transcoding(self):
|
||||
|
||||
item = self.item
|
||||
|
||||
if 'Path' in item and item['Path'].endswith('.strm'):
|
||||
# Allow strm loading when transcoding
|
||||
playurl = self.directPlay()
|
||||
else:
|
||||
itemid = item['Id']
|
||||
deviceId = self.clientInfo.getDeviceId()
|
||||
playurl = (
|
||||
"%s/emby/Videos/%s/master.m3u8?MediaSourceId=%s"
|
||||
% (self.server, itemid, itemid)
|
||||
)
|
||||
playurl = (
|
||||
"%s&VideoCodec=h264&AudioCodec=ac3&MaxAudioChannels=6&deviceId=%s&VideoBitrate=%s"
|
||||
% (playurl, deviceId, self.getBitrate()*1000))
|
||||
|
||||
return playurl
|
||||
|
||||
def getBitrate(self):
|
||||
|
||||
# get the addon video quality
|
||||
videoQuality = utils.settings('videoBitrate')
|
||||
bitrate = {
|
||||
|
||||
'0': 664,
|
||||
'1': 996,
|
||||
'2': 1320,
|
||||
'3': 2000,
|
||||
'4': 3200,
|
||||
'5': 4700,
|
||||
'6': 6200,
|
||||
'7': 7700,
|
||||
'8': 9200,
|
||||
'9': 10700,
|
||||
'10': 12200,
|
||||
'11': 13700,
|
||||
'12': 15200,
|
||||
'13': 16700,
|
||||
'14': 18200,
|
||||
'15': 20000,
|
||||
'16': 40000,
|
||||
'17': 100000,
|
||||
'18': 1000000
|
||||
}
|
||||
|
||||
# max bit rate supported by server (max signed 32bit integer)
|
||||
return bitrate.get(videoQuality, 2147483)
|
||||
|
||||
def audioSubsPref(self, url, listitem):
|
||||
|
||||
log = self.logMsg
|
||||
lang = utils.language
|
||||
dialog = xbmcgui.Dialog()
|
||||
# For transcoding only
|
||||
# Present the list of audio to select from
|
||||
audioStreamsList = {}
|
||||
audioStreams = []
|
||||
audioStreamsChannelsList = {}
|
||||
subtitleStreamsList = {}
|
||||
subtitleStreams = ['No subtitles']
|
||||
downloadableStreams = []
|
||||
selectAudioIndex = ""
|
||||
selectSubsIndex = ""
|
||||
playurlprefs = "%s" % url
|
||||
|
||||
item = self.item
|
||||
try:
|
||||
mediasources = item['MediaSources'][0]
|
||||
mediastreams = mediasources['MediaStreams']
|
||||
except (TypeError, KeyError, IndexError):
|
||||
return
|
||||
|
||||
for stream in mediastreams:
|
||||
# Since Emby returns all possible tracks together, have to sort them.
|
||||
index = stream['Index']
|
||||
type = stream['Type']
|
||||
|
||||
if 'Audio' in type:
|
||||
codec = stream['Codec']
|
||||
channelLayout = stream.get('ChannelLayout', "")
|
||||
|
||||
try:
|
||||
track = "%s - %s - %s %s" % (index, stream['Language'], codec, channelLayout)
|
||||
except:
|
||||
track = "%s - %s %s" % (index, codec, channelLayout)
|
||||
|
||||
audioStreamsChannelsList[index] = stream['Channels']
|
||||
audioStreamsList[track] = index
|
||||
audioStreams.append(track)
|
||||
|
||||
elif 'Subtitle' in type:
|
||||
try:
|
||||
track = "%s - %s" % (index, stream['Language'])
|
||||
except:
|
||||
track = "%s - %s" % (index, stream['Codec'])
|
||||
|
||||
default = stream['IsDefault']
|
||||
forced = stream['IsForced']
|
||||
downloadable = stream['IsTextSubtitleStream']
|
||||
|
||||
if default:
|
||||
track = "%s - Default" % track
|
||||
if forced:
|
||||
track = "%s - Forced" % track
|
||||
if downloadable:
|
||||
downloadableStreams.append(index)
|
||||
|
||||
subtitleStreamsList[track] = index
|
||||
subtitleStreams.append(track)
|
||||
|
||||
|
||||
if len(audioStreams) > 1:
|
||||
resp = dialog.select(lang(33013), audioStreams)
|
||||
if resp > -1:
|
||||
# User selected audio
|
||||
selected = audioStreams[resp]
|
||||
selectAudioIndex = audioStreamsList[selected]
|
||||
playurlprefs += "&AudioStreamIndex=%s" % selectAudioIndex
|
||||
else: # User backed out of selection
|
||||
playurlprefs += "&AudioStreamIndex=%s" % mediasources['DefaultAudioStreamIndex']
|
||||
else: # There's only one audiotrack.
|
||||
selectAudioIndex = audioStreamsList[audioStreams[0]]
|
||||
playurlprefs += "&AudioStreamIndex=%s" % selectAudioIndex
|
||||
|
||||
if len(subtitleStreams) > 1:
|
||||
resp = dialog.select(lang(33014), subtitleStreams)
|
||||
if resp == 0:
|
||||
# User selected no subtitles
|
||||
pass
|
||||
elif resp > -1:
|
||||
# User selected subtitles
|
||||
selected = subtitleStreams[resp]
|
||||
selectSubsIndex = subtitleStreamsList[selected]
|
||||
|
||||
# Load subtitles in the listitem if downloadable
|
||||
if selectSubsIndex in downloadableStreams:
|
||||
|
||||
itemid = item['Id']
|
||||
url = [("%s/Videos/%s/%s/Subtitles/%s/Stream.srt"
|
||||
% (self.server, itemid, itemid, selectSubsIndex))]
|
||||
log("Set up subtitles: %s %s" % (selectSubsIndex, url), 1)
|
||||
listitem.setSubtitles(url)
|
||||
else:
|
||||
# Burn subtitles
|
||||
playurlprefs += "&SubtitleStreamIndex=%s" % selectSubsIndex
|
||||
|
||||
else: # User backed out of selection
|
||||
playurlprefs += "&SubtitleStreamIndex=%s" % mediasources.get('DefaultSubtitleStreamIndex', "")
|
||||
|
||||
# Get number of channels for selected audio track
|
||||
audioChannels = audioStreamsChannelsList.get(selectAudioIndex, 0)
|
||||
if audioChannels > 2:
|
||||
playurlprefs += "&AudioBitrate=384000"
|
||||
else:
|
||||
playurlprefs += "&AudioBitrate=192000"
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#################################################################################################
|
||||
|
||||
import xbmc
|
||||
import xbmcgui
|
||||
import xbmcvfs
|
||||
|
||||
import clientinfo
|
||||
import utils
|
||||
|
||||
#################################################################################################
|
||||
|
||||
|
||||
class PlayUtils():
|
||||
|
||||
|
||||
def __init__(self, item):
|
||||
|
||||
self.item = item
|
||||
|
||||
self.clientInfo = clientinfo.ClientInfo()
|
||||
self.addonName = self.clientInfo.getAddonName()
|
||||
|
||||
self.userid = utils.window('emby_currUser')
|
||||
self.server = utils.window('emby_server%s' % self.userid)
|
||||
|
||||
def logMsg(self, msg, lvl=1):
|
||||
|
||||
self.className = self.__class__.__name__
|
||||
utils.logMsg("%s %s" % (self.addonName, self.className), msg, lvl)
|
||||
|
||||
|
||||
def getPlayUrl(self):
|
||||
|
||||
window = utils.window
|
||||
|
||||
playurl = None
|
||||
|
||||
if (self.item.get('Type') in ("Recording", "TvChannel") and
|
||||
self.item.get('MediaSources') and self.item['MediaSources'][0]['Protocol'] == "Http"):
|
||||
# Play LiveTV or recordings
|
||||
self.logMsg("File protocol is http (livetv).", 1)
|
||||
playurl = "%s/emby/Videos/%s/live.m3u8?static=true" % (self.server, self.item['Id'])
|
||||
window('emby_%s.playmethod' % playurl, value="Transcode")
|
||||
|
||||
elif self.item.get('MediaSources') and self.item['MediaSources'][0]['Protocol'] == "Http":
|
||||
# Only play as http, used for channels, or online hosting of content
|
||||
self.logMsg("File protocol is http.", 1)
|
||||
playurl = self.httpPlay()
|
||||
window('emby_%s.playmethod' % playurl, value="DirectStream")
|
||||
|
||||
elif self.isDirectPlay():
|
||||
|
||||
self.logMsg("File is direct playing.", 1)
|
||||
playurl = self.directPlay()
|
||||
playurl = playurl.encode('utf-8')
|
||||
# Set playmethod property
|
||||
window('emby_%s.playmethod' % playurl, value="DirectPlay")
|
||||
|
||||
elif self.isDirectStream():
|
||||
|
||||
self.logMsg("File is direct streaming.", 1)
|
||||
playurl = self.directStream()
|
||||
# Set playmethod property
|
||||
window('emby_%s.playmethod' % playurl, value="DirectStream")
|
||||
|
||||
elif self.isTranscoding():
|
||||
|
||||
self.logMsg("File is transcoding.", 1)
|
||||
playurl = self.transcoding()
|
||||
# Set playmethod property
|
||||
window('emby_%s.playmethod' % playurl, value="Transcode")
|
||||
|
||||
return playurl
|
||||
|
||||
def httpPlay(self):
|
||||
# Audio, Video, Photo
|
||||
|
||||
itemid = self.item['Id']
|
||||
mediatype = self.item['MediaType']
|
||||
|
||||
if mediatype == "Audio":
|
||||
playurl = "%s/emby/Audio/%s/stream" % (self.server, itemid)
|
||||
else:
|
||||
playurl = "%s/emby/Videos/%s/stream?static=true" % (self.server, itemid)
|
||||
|
||||
return playurl
|
||||
|
||||
def isDirectPlay(self):
|
||||
|
||||
lang = utils.language
|
||||
settings = utils.settings
|
||||
dialog = xbmcgui.Dialog()
|
||||
|
||||
|
||||
# Requirement: Filesystem, Accessible path
|
||||
if settings('playFromStream') == "true":
|
||||
# User forcing to play via HTTP
|
||||
self.logMsg("Can't direct play, play from HTTP enabled.", 1)
|
||||
return False
|
||||
|
||||
videotrack = self.item['MediaSources'][0]['Name']
|
||||
transcodeH265 = settings('transcodeH265')
|
||||
|
||||
if transcodeH265 in ("1", "2", "3") and ("HEVC" in videotrack or "H265" in videotrack):
|
||||
# Avoid H265/HEVC depending on the resolution
|
||||
resolution = int(videotrack.split("P", 1)[0])
|
||||
res = {
|
||||
|
||||
'1': 480,
|
||||
'2': 720,
|
||||
'3': 1080
|
||||
}
|
||||
self.logMsg("Resolution is: %sP, transcode for resolution: %sP+"
|
||||
% (resolution, res[transcodeH265]), 1)
|
||||
if res[transcodeH265] <= resolution:
|
||||
return False
|
||||
|
||||
canDirectPlay = self.item['MediaSources'][0]['SupportsDirectPlay']
|
||||
# Make sure direct play is supported by the server
|
||||
if not canDirectPlay:
|
||||
self.logMsg("Can't direct play, server doesn't allow/support it.", 1)
|
||||
return False
|
||||
|
||||
location = self.item['LocationType']
|
||||
if location == "FileSystem":
|
||||
# Verify the path
|
||||
if not self.fileExists():
|
||||
self.logMsg("Unable to direct play.")
|
||||
try:
|
||||
count = int(settings('failCount'))
|
||||
except ValueError:
|
||||
count = 0
|
||||
self.logMsg("Direct play failed: %s times." % count, 1)
|
||||
|
||||
if count < 2:
|
||||
# Let the user know that direct play failed
|
||||
settings('failCount', value=str(count+1))
|
||||
dialog.notification(
|
||||
heading="Emby for Kodi",
|
||||
message=lang(33011),
|
||||
icon="special://home/addons/plugin.video.emby/icon.png",
|
||||
sound=False)
|
||||
elif settings('playFromStream') != "true":
|
||||
# Permanently set direct stream as true
|
||||
settings('playFromStream', value="true")
|
||||
settings('failCount', value="0")
|
||||
dialog.notification(
|
||||
heading="Emby for Kodi",
|
||||
message=lang(33012),
|
||||
icon="special://home/addons/plugin.video.emby/icon.png",
|
||||
sound=False)
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def directPlay(self):
|
||||
|
||||
try:
|
||||
playurl = self.item['MediaSources'][0]['Path']
|
||||
except (IndexError, KeyError):
|
||||
playurl = self.item['Path']
|
||||
|
||||
if self.item.get('VideoType'):
|
||||
# Specific format modification
|
||||
if self.item['VideoType'] == "Dvd":
|
||||
playurl = "%s/VIDEO_TS/VIDEO_TS.IFO" % playurl
|
||||
elif self.item['VideoType'] == "BluRay":
|
||||
playurl = "%s/BDMV/index.bdmv" % playurl
|
||||
|
||||
# Assign network protocol
|
||||
if playurl.startswith('\\\\'):
|
||||
playurl = playurl.replace("\\\\", "smb://")
|
||||
playurl = playurl.replace("\\", "/")
|
||||
|
||||
if "apple.com" in playurl:
|
||||
USER_AGENT = "QuickTime/7.7.4"
|
||||
playurl += "?|User-Agent=%s" % USER_AGENT
|
||||
|
||||
return playurl
|
||||
|
||||
def fileExists(self):
|
||||
|
||||
if 'Path' not in self.item:
|
||||
# File has no path defined in server
|
||||
return False
|
||||
|
||||
# Convert path to direct play
|
||||
path = self.directPlay()
|
||||
self.logMsg("Verifying path: %s" % path, 1)
|
||||
|
||||
if xbmcvfs.exists(path):
|
||||
self.logMsg("Path exists.", 1)
|
||||
return True
|
||||
|
||||
elif ":" not in path:
|
||||
self.logMsg("Can't verify path, assumed linux. Still try to direct play.", 1)
|
||||
return True
|
||||
|
||||
else:
|
||||
self.logMsg("Failed to find file.", 1)
|
||||
return False
|
||||
|
||||
def isDirectStream(self):
|
||||
|
||||
|
||||
videotrack = self.item['MediaSources'][0]['Name']
|
||||
transcodeH265 = utils.settings('transcodeH265')
|
||||
|
||||
if transcodeH265 in ("1", "2", "3") and ("HEVC" in videotrack or "H265" in videotrack):
|
||||
# Avoid H265/HEVC depending on the resolution
|
||||
resolution = int(videotrack.split("P", 1)[0])
|
||||
res = {
|
||||
|
||||
'1': 480,
|
||||
'2': 720,
|
||||
'3': 1080
|
||||
}
|
||||
self.logMsg("Resolution is: %sP, transcode for resolution: %sP+"
|
||||
% (resolution, res[transcodeH265]), 1)
|
||||
if res[transcodeH265] <= resolution:
|
||||
return False
|
||||
|
||||
# Requirement: BitRate, supported encoding
|
||||
canDirectStream = self.item['MediaSources'][0]['SupportsDirectStream']
|
||||
# Make sure the server supports it
|
||||
if not canDirectStream:
|
||||
return False
|
||||
|
||||
# Verify the bitrate
|
||||
if not self.isNetworkSufficient():
|
||||
self.logMsg("The network speed is insufficient to direct stream file.", 1)
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def directStream(self):
|
||||
|
||||
if 'Path' in self.item and self.item['Path'].endswith('.strm'):
|
||||
# Allow strm loading when direct streaming
|
||||
playurl = self.directPlay()
|
||||
elif self.item['Type'] == "Audio":
|
||||
playurl = "%s/emby/Audio/%s/stream.mp3" % (self.server, self.item['Id'])
|
||||
else:
|
||||
playurl = "%s/emby/Videos/%s/stream?static=true" % (self.server, self.item['Id'])
|
||||
|
||||
return playurl
|
||||
|
||||
def isNetworkSufficient(self):
|
||||
|
||||
|
||||
settings = self.getBitrate()*1000
|
||||
|
||||
try:
|
||||
sourceBitrate = int(self.item['MediaSources'][0]['Bitrate'])
|
||||
except (KeyError, TypeError):
|
||||
self.logMsg("Bitrate value is missing.", 1)
|
||||
else:
|
||||
self.logMsg("The add-on settings bitrate is: %s, the video bitrate required is: %s"
|
||||
% (settings, sourceBitrate), 1)
|
||||
if settings < sourceBitrate:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def isTranscoding(self):
|
||||
# Make sure the server supports it
|
||||
if not self.item['MediaSources'][0]['SupportsTranscoding']:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def transcoding(self):
|
||||
|
||||
if 'Path' in self.item and self.item['Path'].endswith('.strm'):
|
||||
# Allow strm loading when transcoding
|
||||
playurl = self.directPlay()
|
||||
else:
|
||||
itemid = self.item['Id']
|
||||
deviceId = self.clientInfo.getDeviceId()
|
||||
playurl = (
|
||||
"%s/emby/Videos/%s/master.m3u8?MediaSourceId=%s"
|
||||
% (self.server, itemid, itemid)
|
||||
)
|
||||
playurl = (
|
||||
"%s&VideoCodec=h264&AudioCodec=ac3&MaxAudioChannels=6&deviceId=%s&VideoBitrate=%s"
|
||||
% (playurl, deviceId, self.getBitrate()*1000))
|
||||
|
||||
return playurl
|
||||
|
||||
def getBitrate(self):
|
||||
|
||||
# get the addon video quality
|
||||
bitrate = {
|
||||
|
||||
'0': 664,
|
||||
'1': 996,
|
||||
'2': 1320,
|
||||
'3': 2000,
|
||||
'4': 3200,
|
||||
'5': 4700,
|
||||
'6': 6200,
|
||||
'7': 7700,
|
||||
'8': 9200,
|
||||
'9': 10700,
|
||||
'10': 12200,
|
||||
'11': 13700,
|
||||
'12': 15200,
|
||||
'13': 16700,
|
||||
'14': 18200,
|
||||
'15': 20000,
|
||||
'16': 40000,
|
||||
'17': 100000,
|
||||
'18': 1000000
|
||||
}
|
||||
|
||||
# max bit rate supported by server (max signed 32bit integer)
|
||||
return bitrate.get(utils.settings('videoBitrate'), 2147483)
|
||||
|
||||
def audioSubsPref(self, url, listitem):
|
||||
|
||||
lang = utils.language
|
||||
dialog = xbmcgui.Dialog()
|
||||
# For transcoding only
|
||||
# Present the list of audio to select from
|
||||
audioStreamsList = {}
|
||||
audioStreams = []
|
||||
audioStreamsChannelsList = {}
|
||||
subtitleStreamsList = {}
|
||||
subtitleStreams = ['No subtitles']
|
||||
downloadableStreams = []
|
||||
selectAudioIndex = ""
|
||||
selectSubsIndex = ""
|
||||
playurlprefs = "%s" % url
|
||||
|
||||
try:
|
||||
mediasources = self.item['MediaSources'][0]
|
||||
mediastreams = mediasources['MediaStreams']
|
||||
except (TypeError, KeyError, IndexError):
|
||||
return
|
||||
|
||||
for stream in mediastreams:
|
||||
# Since Emby returns all possible tracks together, have to sort them.
|
||||
index = stream['Index']
|
||||
|
||||
if 'Audio' in stream['Type']:
|
||||
codec = stream['Codec']
|
||||
channelLayout = stream.get('ChannelLayout', "")
|
||||
|
||||
try:
|
||||
track = "%s - %s - %s %s" % (index, stream['Language'], codec, channelLayout)
|
||||
except:
|
||||
track = "%s - %s %s" % (index, codec, channelLayout)
|
||||
|
||||
audioStreamsChannelsList[index] = stream['Channels']
|
||||
audioStreamsList[track] = index
|
||||
audioStreams.append(track)
|
||||
|
||||
elif 'Subtitle' in stream['Type']:
|
||||
try:
|
||||
track = "%s - %s" % (index, stream['Language'])
|
||||
except:
|
||||
track = "%s - %s" % (index, stream['Codec'])
|
||||
|
||||
default = stream['IsDefault']
|
||||
forced = stream['IsForced']
|
||||
downloadable = stream['IsTextSubtitleStream']
|
||||
|
||||
if default:
|
||||
track = "%s - Default" % track
|
||||
if forced:
|
||||
track = "%s - Forced" % track
|
||||
if downloadable:
|
||||
downloadableStreams.append(index)
|
||||
|
||||
subtitleStreamsList[track] = index
|
||||
subtitleStreams.append(track)
|
||||
|
||||
|
||||
if len(audioStreams) > 1:
|
||||
resp = dialog.select(lang(33013), audioStreams)
|
||||
if resp > -1:
|
||||
# User selected audio
|
||||
selected = audioStreams[resp]
|
||||
selectAudioIndex = audioStreamsList[selected]
|
||||
playurlprefs += "&AudioStreamIndex=%s" % selectAudioIndex
|
||||
else: # User backed out of selection
|
||||
playurlprefs += "&AudioStreamIndex=%s" % mediasources['DefaultAudioStreamIndex']
|
||||
else: # There's only one audiotrack.
|
||||
selectAudioIndex = audioStreamsList[audioStreams[0]]
|
||||
playurlprefs += "&AudioStreamIndex=%s" % selectAudioIndex
|
||||
|
||||
if len(subtitleStreams) > 1:
|
||||
resp = dialog.select(lang(33014), subtitleStreams)
|
||||
if resp == 0:
|
||||
# User selected no subtitles
|
||||
pass
|
||||
elif resp > -1:
|
||||
# User selected subtitles
|
||||
selected = subtitleStreams[resp]
|
||||
selectSubsIndex = subtitleStreamsList[selected]
|
||||
|
||||
# Load subtitles in the listitem if downloadable
|
||||
if selectSubsIndex in downloadableStreams:
|
||||
|
||||
itemid = self.item['Id']
|
||||
url = [("%s/Videos/%s/%s/Subtitles/%s/Stream.srt"
|
||||
% (self.server, itemid, itemid, selectSubsIndex))]
|
||||
self.logMsg("Set up subtitles: %s %s" % (selectSubsIndex, url), 1)
|
||||
listitem.setSubtitles(url)
|
||||
else:
|
||||
# Burn subtitles
|
||||
playurlprefs += "&SubtitleStreamIndex=%s" % selectSubsIndex
|
||||
|
||||
else: # User backed out of selection
|
||||
playurlprefs += "&SubtitleStreamIndex=%s" % mediasources.get('DefaultSubtitleStreamIndex', "")
|
||||
|
||||
# Get number of channels for selected audio track
|
||||
audioChannels = audioStreamsChannelsList.get(selectAudioIndex, 0)
|
||||
if audioChannels > 2:
|
||||
playurlprefs += "&AudioBitrate=384000"
|
||||
else:
|
||||
playurlprefs += "&AudioBitrate=192000"
|
||||
|
||||
return playurlprefs
|
File diff suppressed because it is too large
Load diff
|
@ -1,487 +1,468 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
##################################################################################################
|
||||
|
||||
import hashlib
|
||||
import threading
|
||||
|
||||
import xbmc
|
||||
import xbmcgui
|
||||
import xbmcaddon
|
||||
import xbmcvfs
|
||||
|
||||
import artwork
|
||||
import utils
|
||||
import clientinfo
|
||||
import downloadutils
|
||||
|
||||
##################################################################################################
|
||||
|
||||
|
||||
class UserClient(threading.Thread):
|
||||
|
||||
# Borg - multiple instances, shared state
|
||||
_shared_state = {}
|
||||
|
||||
stopClient = False
|
||||
auth = True
|
||||
retry = 0
|
||||
|
||||
currUser = None
|
||||
currUserId = None
|
||||
currServer = None
|
||||
currToken = None
|
||||
HasAccess = True
|
||||
AdditionalUser = []
|
||||
|
||||
userSettings = None
|
||||
|
||||
|
||||
def __init__(self):
|
||||
|
||||
self.__dict__ = self._shared_state
|
||||
self.addon = xbmcaddon.Addon()
|
||||
|
||||
self.addonName = clientinfo.ClientInfo().getAddonName()
|
||||
self.doUtils = downloadutils.DownloadUtils()
|
||||
|
||||
threading.Thread.__init__(self)
|
||||
|
||||
def logMsg(self, msg, lvl=1):
|
||||
|
||||
className = self.__class__.__name__
|
||||
utils.logMsg("%s %s" % (self.addonName, className), msg, lvl)
|
||||
|
||||
|
||||
def getAdditionalUsers(self):
|
||||
|
||||
additionalUsers = utils.settings('additionalUsers')
|
||||
|
||||
if additionalUsers:
|
||||
self.AdditionalUser = additionalUsers.split(',')
|
||||
|
||||
def getUsername(self):
|
||||
|
||||
username = utils.settings('username')
|
||||
|
||||
if not username:
|
||||
self.logMsg("No username saved.", 2)
|
||||
return ""
|
||||
|
||||
return username
|
||||
|
||||
def getLogLevel(self):
|
||||
|
||||
try:
|
||||
logLevel = int(utils.settings('logLevel'))
|
||||
except ValueError:
|
||||
logLevel = 0
|
||||
|
||||
return logLevel
|
||||
|
||||
def getUserId(self):
|
||||
|
||||
log = self.logMsg
|
||||
window = utils.window
|
||||
settings = utils.settings
|
||||
|
||||
username = self.getUsername()
|
||||
w_userId = window('emby_currUser')
|
||||
s_userId = settings('userId%s' % username)
|
||||
|
||||
# Verify the window property
|
||||
if w_userId:
|
||||
if not s_userId:
|
||||
# Save access token if it's missing from settings
|
||||
settings('userId%s' % username, value=w_userId)
|
||||
log("Returning userId from WINDOW for username: %s UserId: %s"
|
||||
% (username, w_userId), 2)
|
||||
return w_userId
|
||||
# Verify the settings
|
||||
elif s_userId:
|
||||
log("Returning userId from SETTINGS for username: %s userId: %s"
|
||||
% (username, s_userId), 2)
|
||||
return s_userId
|
||||
# No userId found
|
||||
else:
|
||||
log("No userId saved for username: %s." % username, 1)
|
||||
|
||||
def getServer(self, prefix=True):
|
||||
|
||||
settings = utils.settings
|
||||
|
||||
alternate = settings('altip') == "true"
|
||||
if alternate:
|
||||
# Alternate host
|
||||
HTTPS = settings('secondhttps') == "true"
|
||||
host = settings('secondipaddress')
|
||||
port = settings('secondport')
|
||||
else:
|
||||
# Original host
|
||||
HTTPS = settings('https') == "true"
|
||||
host = settings('ipaddress')
|
||||
port = settings('port')
|
||||
|
||||
server = host + ":" + port
|
||||
|
||||
if not host:
|
||||
self.logMsg("No server information saved.", 2)
|
||||
return False
|
||||
|
||||
# If https is true
|
||||
if prefix and HTTPS:
|
||||
server = "https://%s" % server
|
||||
return server
|
||||
# If https is false
|
||||
elif prefix and not HTTPS:
|
||||
server = "http://%s" % server
|
||||
return server
|
||||
# If only the host:port is required
|
||||
elif not prefix:
|
||||
return server
|
||||
|
||||
def getToken(self):
|
||||
|
||||
log = self.logMsg
|
||||
window = utils.window
|
||||
settings = utils.settings
|
||||
|
||||
username = self.getUsername()
|
||||
userId = self.getUserId()
|
||||
w_token = window('emby_accessToken%s' % userId)
|
||||
s_token = settings('accessToken')
|
||||
|
||||
# Verify the window property
|
||||
if w_token:
|
||||
if not s_token:
|
||||
# Save access token if it's missing from settings
|
||||
settings('accessToken', value=w_token)
|
||||
log("Returning accessToken from WINDOW for username: %s accessToken: %s"
|
||||
% (username, w_token), 2)
|
||||
return w_token
|
||||
# Verify the settings
|
||||
elif s_token:
|
||||
log("Returning accessToken from SETTINGS for username: %s accessToken: %s"
|
||||
% (username, s_token), 2)
|
||||
window('emby_accessToken%s' % username, value=s_token)
|
||||
return s_token
|
||||
else:
|
||||
log("No token found.", 1)
|
||||
return ""
|
||||
|
||||
def getSSLverify(self):
|
||||
# Verify host certificate
|
||||
settings = utils.settings
|
||||
|
||||
s_sslverify = settings('sslverify')
|
||||
if settings('altip') == "true":
|
||||
s_sslverify = settings('secondsslverify')
|
||||
|
||||
if s_sslverify == "true":
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def getSSL(self):
|
||||
# Client side certificate
|
||||
settings = utils.settings
|
||||
|
||||
s_cert = settings('sslcert')
|
||||
if settings('altip') == "true":
|
||||
s_cert = settings('secondsslcert')
|
||||
|
||||
if s_cert == "None":
|
||||
return None
|
||||
else:
|
||||
return s_cert
|
||||
|
||||
def setUserPref(self):
|
||||
|
||||
doUtils = self.doUtils.downloadUrl
|
||||
art = artwork.Artwork()
|
||||
|
||||
url = "{server}/emby/Users/{UserId}?format=json"
|
||||
result = doUtils(url)
|
||||
self.userSettings = result
|
||||
# Set user image for skin display
|
||||
if result.get('PrimaryImageTag'):
|
||||
utils.window('EmbyUserImage', value=art.getUserArtwork(result['Id'], 'Primary'))
|
||||
|
||||
# Set resume point max
|
||||
url = "{server}/emby/System/Configuration?format=json"
|
||||
result = doUtils(url)
|
||||
|
||||
utils.settings('markPlayed', value=str(result['MaxResumePct']))
|
||||
|
||||
def getPublicUsers(self):
|
||||
|
||||
server = self.getServer()
|
||||
|
||||
# Get public Users
|
||||
url = "%s/emby/Users/Public?format=json" % server
|
||||
result = self.doUtils.downloadUrl(url, authenticate=False)
|
||||
|
||||
if result != "":
|
||||
return result
|
||||
else:
|
||||
# Server connection failed
|
||||
return False
|
||||
|
||||
def hasAccess(self):
|
||||
# hasAccess is verified in service.py
|
||||
log = self.logMsg
|
||||
window = utils.window
|
||||
|
||||
url = "{server}/emby/Users?format=json"
|
||||
result = self.doUtils.downloadUrl(url)
|
||||
|
||||
if result == False:
|
||||
# Access is restricted, set in downloadutils.py via exception
|
||||
log("Access is restricted.", 1)
|
||||
self.HasAccess = False
|
||||
|
||||
elif window('emby_online') != "true":
|
||||
# Server connection failed
|
||||
pass
|
||||
|
||||
elif window('emby_serverStatus') == "restricted":
|
||||
log("Access is granted.", 1)
|
||||
self.HasAccess = True
|
||||
window('emby_serverStatus', clear=True)
|
||||
xbmcgui.Dialog().notification("Emby for Kodi", utils.language(33007))
|
||||
|
||||
def loadCurrUser(self, authenticated=False):
|
||||
|
||||
window = utils.window
|
||||
|
||||
doUtils = self.doUtils
|
||||
username = self.getUsername()
|
||||
userId = self.getUserId()
|
||||
|
||||
# Only to be used if token exists
|
||||
self.currUserId = userId
|
||||
self.currServer = self.getServer()
|
||||
self.currToken = self.getToken()
|
||||
self.ssl = self.getSSLverify()
|
||||
self.sslcert = self.getSSL()
|
||||
|
||||
# Test the validity of current token
|
||||
if authenticated == False:
|
||||
url = "%s/emby/Users/%s?format=json" % (self.currServer, userId)
|
||||
window('emby_currUser', value=userId)
|
||||
window('emby_accessToken%s' % userId, value=self.currToken)
|
||||
result = doUtils.downloadUrl(url)
|
||||
|
||||
if result == 401:
|
||||
# Token is no longer valid
|
||||
self.resetClient()
|
||||
return False
|
||||
|
||||
# Set to windows property
|
||||
window('emby_currUser', value=userId)
|
||||
window('emby_accessToken%s' % userId, value=self.currToken)
|
||||
window('emby_server%s' % userId, value=self.currServer)
|
||||
window('emby_server_%s' % userId, value=self.getServer(prefix=False))
|
||||
|
||||
# Set DownloadUtils values
|
||||
doUtils.setUsername(username)
|
||||
doUtils.setUserId(self.currUserId)
|
||||
doUtils.setServer(self.currServer)
|
||||
doUtils.setToken(self.currToken)
|
||||
doUtils.setSSL(self.ssl, self.sslcert)
|
||||
# parental control - let's verify if access is restricted
|
||||
self.hasAccess()
|
||||
# Start DownloadUtils session
|
||||
doUtils.startSession()
|
||||
self.getAdditionalUsers()
|
||||
# Set user preferences in settings
|
||||
self.currUser = username
|
||||
self.setUserPref()
|
||||
|
||||
|
||||
def authenticate(self):
|
||||
|
||||
log = self.logMsg
|
||||
lang = utils.language
|
||||
window = utils.window
|
||||
settings = utils.settings
|
||||
dialog = xbmcgui.Dialog()
|
||||
|
||||
# Get /profile/addon_data
|
||||
addondir = xbmc.translatePath(self.addon.getAddonInfo('profile')).decode('utf-8')
|
||||
hasSettings = xbmcvfs.exists("%ssettings.xml" % addondir)
|
||||
|
||||
username = self.getUsername()
|
||||
server = self.getServer()
|
||||
|
||||
# If there's no settings.xml
|
||||
if not hasSettings:
|
||||
log("No settings.xml found.", 1)
|
||||
self.auth = False
|
||||
return
|
||||
# If no user information
|
||||
elif not server or not username:
|
||||
log("Missing server information.", 1)
|
||||
self.auth = False
|
||||
return
|
||||
# If there's a token, load the user
|
||||
elif self.getToken():
|
||||
result = self.loadCurrUser()
|
||||
|
||||
if result == False:
|
||||
pass
|
||||
else:
|
||||
log("Current user: %s" % self.currUser, 1)
|
||||
log("Current userId: %s" % self.currUserId, 1)
|
||||
log("Current accessToken: %s" % self.currToken, 2)
|
||||
return
|
||||
|
||||
##### AUTHENTICATE USER #####
|
||||
|
||||
users = self.getPublicUsers()
|
||||
password = ""
|
||||
|
||||
# Find user in list
|
||||
for user in users:
|
||||
name = user['Name']
|
||||
|
||||
if username.decode('utf-8') in name:
|
||||
# If user has password
|
||||
if user['HasPassword'] == True:
|
||||
password = dialog.input(
|
||||
heading="%s %s" % (lang(33008), username.decode('utf-8')),
|
||||
option=xbmcgui.ALPHANUM_HIDE_INPUT)
|
||||
# If password dialog is cancelled
|
||||
if not password:
|
||||
log("No password entered.", 0)
|
||||
window('emby_serverStatus', value="Stop")
|
||||
self.auth = False
|
||||
return
|
||||
break
|
||||
else:
|
||||
# Manual login, user is hidden
|
||||
password = dialog.input(
|
||||
heading="%s %s" % (lang(33008), username),
|
||||
option=xbmcgui.ALPHANUM_HIDE_INPUT)
|
||||
sha1 = hashlib.sha1(password)
|
||||
sha1 = sha1.hexdigest()
|
||||
|
||||
# Authenticate username and password
|
||||
url = "%s/emby/Users/AuthenticateByName?format=json" % server
|
||||
data = {'username': username, 'password': sha1}
|
||||
log(data, 2)
|
||||
|
||||
result = self.doUtils.downloadUrl(url, postBody=data, type="POST", authenticate=False)
|
||||
|
||||
try:
|
||||
log("Auth response: %s" % result, 1)
|
||||
accessToken = result['AccessToken']
|
||||
|
||||
except (KeyError, TypeError):
|
||||
log("Failed to retrieve the api key.", 1)
|
||||
accessToken = None
|
||||
|
||||
if accessToken is not None:
|
||||
self.currUser = username
|
||||
dialog.notification("Emby for Kodi",
|
||||
"%s %s!" % (lang(33000), self.currUser.decode('utf-8')))
|
||||
userId = result['User']['Id']
|
||||
settings('accessToken', value=accessToken)
|
||||
settings('userId%s' % username, value=userId)
|
||||
log("User Authenticated: %s" % accessToken, 1)
|
||||
self.loadCurrUser(authenticated=True)
|
||||
window('emby_serverStatus', clear=True)
|
||||
self.retry = 0
|
||||
else:
|
||||
log("User authentication failed.", 1)
|
||||
settings('accessToken', value="")
|
||||
settings('userId%s' % username, value="")
|
||||
dialog.ok(lang(33001), lang(33009))
|
||||
|
||||
# Give two attempts at entering password
|
||||
if self.retry == 2:
|
||||
log("Too many retries. "
|
||||
"You can retry by resetting attempts in the addon settings.", 1)
|
||||
window('emby_serverStatus', value="Stop")
|
||||
dialog.ok(lang(33001), lang(33010))
|
||||
|
||||
self.retry += 1
|
||||
self.auth = False
|
||||
|
||||
def resetClient(self):
|
||||
|
||||
log = self.logMsg
|
||||
|
||||
log("Reset UserClient authentication.", 1)
|
||||
userId = self.getUserId()
|
||||
|
||||
if self.currToken is not None:
|
||||
# In case of 401, removed saved token
|
||||
utils.settings('accessToken', value="")
|
||||
utils.window('emby_accessToken%s' % userId, clear=True)
|
||||
self.currToken = None
|
||||
log("User token has been removed.", 1)
|
||||
|
||||
self.auth = True
|
||||
self.currUser = None
|
||||
|
||||
def run(self):
|
||||
|
||||
log = self.logMsg
|
||||
window = utils.window
|
||||
|
||||
monitor = xbmc.Monitor()
|
||||
log("----===## Starting UserClient ##===----", 0)
|
||||
|
||||
while not monitor.abortRequested():
|
||||
|
||||
status = window('emby_serverStatus')
|
||||
if status:
|
||||
# Verify the connection status to server
|
||||
if status == "restricted":
|
||||
# Parental control is restricting access
|
||||
self.HasAccess = False
|
||||
|
||||
elif status == "401":
|
||||
# Unauthorized access, revoke token
|
||||
window('emby_serverStatus', value="Auth")
|
||||
self.resetClient()
|
||||
|
||||
if self.auth and (self.currUser is None):
|
||||
# Try to authenticate user
|
||||
status = window('emby_serverStatus')
|
||||
if not status or status == "Auth":
|
||||
# Set auth flag because we no longer need
|
||||
# to authenticate the user
|
||||
self.auth = False
|
||||
self.authenticate()
|
||||
|
||||
|
||||
if not self.auth and (self.currUser is None):
|
||||
# If authenticate failed.
|
||||
server = self.getServer()
|
||||
username = self.getUsername()
|
||||
status = window('emby_serverStatus')
|
||||
|
||||
# The status Stop is for when user cancelled password dialog.
|
||||
if server and username and status != "Stop":
|
||||
# Only if there's information found to login
|
||||
log("Server found: %s" % server, 2)
|
||||
log("Username found: %s" % username, 2)
|
||||
self.auth = True
|
||||
|
||||
|
||||
if self.stopClient == True:
|
||||
# If stopping the client didn't work
|
||||
break
|
||||
|
||||
if monitor.waitForAbort(1):
|
||||
# Abort was requested while waiting. We should exit
|
||||
break
|
||||
|
||||
self.doUtils.stopSession()
|
||||
log("##===---- UserClient Stopped ----===##", 0)
|
||||
|
||||
def stopClient(self):
|
||||
# When emby for kodi terminates
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
##################################################################################################
|
||||
|
||||
import hashlib
|
||||
import threading
|
||||
|
||||
import xbmc
|
||||
import xbmcgui
|
||||
import xbmcaddon
|
||||
import xbmcvfs
|
||||
|
||||
import artwork
|
||||
import utils
|
||||
import clientinfo
|
||||
import downloadutils
|
||||
|
||||
##################################################################################################
|
||||
|
||||
|
||||
class UserClient(threading.Thread):
|
||||
|
||||
# Borg - multiple instances, shared state
|
||||
_shared_state = {}
|
||||
|
||||
stopClient = False
|
||||
auth = True
|
||||
retry = 0
|
||||
|
||||
currUser = None
|
||||
currUserId = None
|
||||
currServer = None
|
||||
currToken = None
|
||||
HasAccess = True
|
||||
AdditionalUser = []
|
||||
|
||||
userSettings = None
|
||||
|
||||
|
||||
def __init__(self):
|
||||
|
||||
self.__dict__ = self._shared_state
|
||||
self.addon = xbmcaddon.Addon()
|
||||
|
||||
self.addonName = clientinfo.ClientInfo().getAddonName()
|
||||
self.doUtils = downloadutils.DownloadUtils()
|
||||
|
||||
threading.Thread.__init__(self)
|
||||
|
||||
def logMsg(self, msg, lvl=1):
|
||||
|
||||
className = self.__class__.__name__
|
||||
utils.logMsg("%s %s" % (self.addonName, className), msg, lvl)
|
||||
|
||||
|
||||
def getAdditionalUsers(self):
|
||||
|
||||
additionalUsers = utils.settings('additionalUsers')
|
||||
|
||||
if additionalUsers:
|
||||
self.AdditionalUser = additionalUsers.split(',')
|
||||
|
||||
def getUsername(self):
|
||||
|
||||
username = utils.settings('username')
|
||||
|
||||
if not username:
|
||||
self.logMsg("No username saved.", 2)
|
||||
return ""
|
||||
|
||||
return username
|
||||
|
||||
def getLogLevel(self):
|
||||
|
||||
try:
|
||||
logLevel = int(utils.settings('logLevel'))
|
||||
except ValueError:
|
||||
logLevel = 0
|
||||
|
||||
return logLevel
|
||||
|
||||
def getUserId(self):
|
||||
|
||||
window = utils.window
|
||||
settings = utils.settings
|
||||
|
||||
username = self.getUsername()
|
||||
w_userId = window('emby_currUser')
|
||||
s_userId = settings('userId%s' % username)
|
||||
|
||||
# Verify the window property
|
||||
if w_userId:
|
||||
if not s_userId:
|
||||
# Save access token if it's missing from settings
|
||||
settings('userId%s' % username, value=w_userId)
|
||||
self.logMsg("Returning userId from WINDOW for username: %s UserId: %s"
|
||||
% (username, w_userId), 2)
|
||||
return w_userId
|
||||
# Verify the settings
|
||||
elif s_userId:
|
||||
self.logMsg("Returning userId from SETTINGS for username: %s userId: %s"
|
||||
% (username, s_userId), 2)
|
||||
return s_userId
|
||||
# No userId found
|
||||
else:
|
||||
self.logMsg("No userId saved for username: %s." % username, 1)
|
||||
|
||||
def getServer(self, prefix=True):
|
||||
|
||||
settings = utils.settings
|
||||
|
||||
alternate = settings('altip') == "true"
|
||||
if alternate:
|
||||
# Alternate host
|
||||
HTTPS = settings('secondhttps') == "true"
|
||||
host = settings('secondipaddress')
|
||||
port = settings('secondport')
|
||||
else:
|
||||
# Original host
|
||||
HTTPS = settings('https') == "true"
|
||||
host = settings('ipaddress')
|
||||
port = settings('port')
|
||||
|
||||
server = host + ":" + port
|
||||
|
||||
if not host:
|
||||
self.logMsg("No server information saved.", 2)
|
||||
return False
|
||||
|
||||
# If https is true
|
||||
if prefix and HTTPS:
|
||||
server = "https://%s" % server
|
||||
return server
|
||||
# If https is false
|
||||
elif prefix and not HTTPS:
|
||||
server = "http://%s" % server
|
||||
return server
|
||||
# If only the host:port is required
|
||||
elif not prefix:
|
||||
return server
|
||||
|
||||
def getToken(self):
|
||||
|
||||
window = utils.window
|
||||
settings = utils.settings
|
||||
|
||||
username = self.getUsername()
|
||||
userId = self.getUserId()
|
||||
w_token = window('emby_accessToken%s' % userId)
|
||||
s_token = settings('accessToken')
|
||||
|
||||
# Verify the window property
|
||||
if w_token:
|
||||
if not s_token:
|
||||
# Save access token if it's missing from settings
|
||||
settings('accessToken', value=w_token)
|
||||
self.logMsg("Returning accessToken from WINDOW for username: %s accessToken: %s"
|
||||
% (username, w_token), 2)
|
||||
return w_token
|
||||
# Verify the settings
|
||||
elif s_token:
|
||||
self.logMsg("Returning accessToken from SETTINGS for username: %s accessToken: %s"
|
||||
% (username, s_token), 2)
|
||||
window('emby_accessToken%s' % username, value=s_token)
|
||||
return s_token
|
||||
else:
|
||||
self.logMsg("No token found.", 1)
|
||||
return ""
|
||||
|
||||
def getSSLverify(self):
|
||||
# Verify host certificate
|
||||
settings = utils.settings
|
||||
|
||||
s_sslverify = settings('sslverify')
|
||||
if settings('altip') == "true":
|
||||
s_sslverify = settings('secondsslverify')
|
||||
|
||||
if s_sslverify == "true":
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def getSSL(self):
|
||||
# Client side certificate
|
||||
settings = utils.settings
|
||||
|
||||
s_cert = settings('sslcert')
|
||||
if settings('altip') == "true":
|
||||
s_cert = settings('secondsslcert')
|
||||
|
||||
if s_cert == "None":
|
||||
return None
|
||||
else:
|
||||
return s_cert
|
||||
|
||||
def setUserPref(self):
|
||||
|
||||
doUtils = self.doUtils.downloadUrl
|
||||
|
||||
result = doUtils("{server}/emby/Users/{UserId}?format=json")
|
||||
self.userSettings = result
|
||||
# Set user image for skin display
|
||||
if result.get('PrimaryImageTag'):
|
||||
utils.window('EmbyUserImage', value=artwork.Artwork().getUserArtwork(result['Id'], 'Primary'))
|
||||
|
||||
# Set resume point max
|
||||
result = doUtils("{server}/emby/System/Configuration?format=json")
|
||||
|
||||
utils.settings('markPlayed', value=str(result['MaxResumePct']))
|
||||
|
||||
def getPublicUsers(self):
|
||||
# Get public Users
|
||||
result = self.doUtils.downloadUrl("%s/emby/Users/Public?format=json" % self.getServer(), authenticate=False)
|
||||
if result != "":
|
||||
return result
|
||||
else:
|
||||
# Server connection failed
|
||||
return False
|
||||
|
||||
|
||||
def hasAccess(self):
|
||||
# hasAccess is verified in service.py
|
||||
window = utils.window
|
||||
|
||||
result = self.doUtils.downloadUrl("{server}/emby/Users?format=json")
|
||||
|
||||
if result == False:
|
||||
# Access is restricted, set in downloadutils.py via exception
|
||||
self.logMsg("Access is restricted.", 1)
|
||||
self.HasAccess = False
|
||||
|
||||
elif window('emby_online') != "true":
|
||||
# Server connection failed
|
||||
pass
|
||||
|
||||
elif window('emby_serverStatus') == "restricted":
|
||||
self.logMsg("Access is granted.", 1)
|
||||
self.HasAccess = True
|
||||
window('emby_serverStatus', clear=True)
|
||||
xbmcgui.Dialog().notification("Emby for Kodi", utils.language(33007))
|
||||
|
||||
def loadCurrUser(self, authenticated=False):
|
||||
|
||||
window = utils.window
|
||||
|
||||
doUtils = self.doUtils
|
||||
username = self.getUsername()
|
||||
userId = self.getUserId()
|
||||
|
||||
# Only to be used if token exists
|
||||
self.currUserId = userId
|
||||
self.currServer = self.getServer()
|
||||
self.currToken = self.getToken()
|
||||
self.ssl = self.getSSLverify()
|
||||
self.sslcert = self.getSSL()
|
||||
|
||||
# Test the validity of current token
|
||||
if authenticated == False:
|
||||
url = "%s/emby/Users/%s?format=json" % (self.currServer, userId)
|
||||
window('emby_currUser', value=userId)
|
||||
window('emby_accessToken%s' % userId, value=self.currToken)
|
||||
result = doUtils.downloadUrl(url)
|
||||
|
||||
if result == 401:
|
||||
# Token is no longer valid
|
||||
self.resetClient()
|
||||
return False
|
||||
|
||||
# Set to windows property
|
||||
window('emby_currUser', value=userId)
|
||||
window('emby_accessToken%s' % userId, value=self.currToken)
|
||||
window('emby_server%s' % userId, value=self.currServer)
|
||||
window('emby_server_%s' % userId, value=self.getServer(prefix=False))
|
||||
|
||||
# Set DownloadUtils values
|
||||
doUtils.setUsername(username)
|
||||
doUtils.setUserId(self.currUserId)
|
||||
doUtils.setServer(self.currServer)
|
||||
doUtils.setToken(self.currToken)
|
||||
doUtils.setSSL(self.ssl, self.sslcert)
|
||||
# parental control - let's verify if access is restricted
|
||||
self.hasAccess()
|
||||
# Start DownloadUtils session
|
||||
doUtils.startSession()
|
||||
self.getAdditionalUsers()
|
||||
# Set user preferences in settings
|
||||
self.currUser = username
|
||||
self.setUserPref()
|
||||
|
||||
|
||||
def authenticate(self):
|
||||
|
||||
lang = utils.language
|
||||
window = utils.window
|
||||
settings = utils.settings
|
||||
dialog = xbmcgui.Dialog()
|
||||
|
||||
# Get /profile/addon_data
|
||||
addondir = xbmc.translatePath(self.addon.getAddonInfo('profile')).decode('utf-8')
|
||||
hasSettings = xbmcvfs.exists("%ssettings.xml" % addondir)
|
||||
|
||||
username = self.getUsername()
|
||||
server = self.getServer()
|
||||
|
||||
# If there's no settings.xml
|
||||
if not hasSettings:
|
||||
self.logMsg("No settings.xml found.", 1)
|
||||
self.auth = False
|
||||
return
|
||||
# If no user information
|
||||
elif not server or not username:
|
||||
self.logMsg("Missing server information.", 1)
|
||||
self.auth = False
|
||||
return
|
||||
# If there's a token, load the user
|
||||
elif self.getToken():
|
||||
result = self.loadCurrUser()
|
||||
|
||||
if result is False:
|
||||
pass
|
||||
else:
|
||||
self.logMsg("Current user: %s" % self.currUser, 1)
|
||||
self.logMsg("Current userId: %s" % self.currUserId, 1)
|
||||
self.logMsg("Current accessToken: %s" % self.currToken, 2)
|
||||
return
|
||||
|
||||
##### AUTHENTICATE USER #####
|
||||
|
||||
users = self.getPublicUsers()
|
||||
password = ""
|
||||
|
||||
# Find user in list
|
||||
for user in users:
|
||||
name = user['Name']
|
||||
|
||||
if username.decode('utf-8') in name:
|
||||
# If user has password
|
||||
if user['HasPassword'] == True:
|
||||
password = dialog.input(
|
||||
heading="%s %s" % (lang(33008), username.decode('utf-8')),
|
||||
option=xbmcgui.ALPHANUM_HIDE_INPUT)
|
||||
# If password dialog is cancelled
|
||||
if not password:
|
||||
self.logMsg("No password entered.", 0)
|
||||
window('emby_serverStatus', value="Stop")
|
||||
self.auth = False
|
||||
return
|
||||
break
|
||||
else:
|
||||
# Manual login, user is hidden
|
||||
password = dialog.input(
|
||||
heading="%s %s" % (lang(33008), username),
|
||||
option=xbmcgui.ALPHANUM_HIDE_INPUT)
|
||||
sha1 = hashlib.sha1(password)
|
||||
sha1 = sha1.hexdigest()
|
||||
|
||||
# Authenticate username and password
|
||||
data = {'username': username, 'password': sha1}
|
||||
self.logMsg(data, 2)
|
||||
|
||||
result = self.doUtils.downloadUrl("%s/emby/Users/AuthenticateByName?format=json" % server, postBody=data, action_type="POST", authenticate=False)
|
||||
|
||||
try:
|
||||
self.logMsg("Auth response: %s" % result, 1)
|
||||
accessToken = result['AccessToken']
|
||||
|
||||
except (KeyError, TypeError):
|
||||
self.logMsg("Failed to retrieve the api key.", 1)
|
||||
accessToken = None
|
||||
|
||||
if accessToken is not None:
|
||||
self.currUser = username
|
||||
dialog.notification("Emby for Kodi",
|
||||
"%s %s!" % (lang(33000), self.currUser.decode('utf-8')))
|
||||
settings('accessToken', value=accessToken)
|
||||
settings('userId%s' % username, value=result['User']['Id'])
|
||||
self.logMsg("User Authenticated: %s" % accessToken, 1)
|
||||
self.loadCurrUser(authenticated=True)
|
||||
window('emby_serverStatus', clear=True)
|
||||
self.retry = 0
|
||||
else:
|
||||
self.logMsg("User authentication failed.", 1)
|
||||
settings('accessToken', value="")
|
||||
settings('userId%s' % username, value="")
|
||||
dialog.ok(lang(33001), lang(33009))
|
||||
|
||||
# Give two attempts at entering password
|
||||
if self.retry == 2:
|
||||
self.logMsg("Too many retries. "
|
||||
"You can retry by resetting attempts in the addon settings.", 1)
|
||||
window('emby_serverStatus', value="Stop")
|
||||
dialog.ok(lang(33001), lang(33010))
|
||||
|
||||
self.retry += 1
|
||||
self.auth = False
|
||||
|
||||
def resetClient(self):
|
||||
|
||||
self.logMsg("Reset UserClient authentication.", 1)
|
||||
if self.currToken is not None:
|
||||
# In case of 401, removed saved token
|
||||
utils.settings('accessToken', value="")
|
||||
utils.window('emby_accessToken%s' % self.getUserId(), clear=True)
|
||||
self.currToken = None
|
||||
self.logMsg("User token has been removed.", 1)
|
||||
|
||||
self.auth = True
|
||||
self.currUser = None
|
||||
|
||||
def run(self):
|
||||
|
||||
window = utils.window
|
||||
|
||||
monitor = xbmc.Monitor()
|
||||
self.logMsg("----===## Starting UserClient ##===----", 0)
|
||||
|
||||
while not monitor.abortRequested():
|
||||
|
||||
status = window('emby_serverStatus')
|
||||
if status:
|
||||
# Verify the connection status to server
|
||||
if status == "restricted":
|
||||
# Parental control is restricting access
|
||||
self.HasAccess = False
|
||||
|
||||
elif status == "401":
|
||||
# Unauthorized access, revoke token
|
||||
window('emby_serverStatus', value="Auth")
|
||||
self.resetClient()
|
||||
|
||||
if self.auth and (self.currUser is None):
|
||||
# Try to authenticate user
|
||||
status = window('emby_serverStatus')
|
||||
if not status or status == "Auth":
|
||||
# Set auth flag because we no longer need
|
||||
# to authenticate the user
|
||||
self.auth = False
|
||||
self.authenticate()
|
||||
|
||||
|
||||
if not self.auth and (self.currUser is None):
|
||||
# If authenticate failed.
|
||||
server = self.getServer()
|
||||
username = self.getUsername()
|
||||
status = window('emby_serverStatus')
|
||||
|
||||
# The status Stop is for when user cancelled password dialog.
|
||||
if server and username and status != "Stop":
|
||||
# Only if there's information found to login
|
||||
self.logMsg("Server found: %s" % server, 2)
|
||||
self.logMsg("Username found: %s" % username, 2)
|
||||
self.auth = True
|
||||
|
||||
|
||||
if self.stopClient == True:
|
||||
# If stopping the client didn't work
|
||||
break
|
||||
|
||||
if monitor.waitForAbort(1):
|
||||
# Abort was requested while waiting. We should exit
|
||||
break
|
||||
|
||||
self.doUtils.stopSession()
|
||||
self.logMsg("##===---- UserClient Stopped ----===##", 0)
|
||||
|
||||
def stopClient(self):
|
||||
# When emby for kodi terminates
|
||||
self.stopClient = True
|
File diff suppressed because it is too large
Load diff
|
@ -1,395 +1,394 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
#################################################################################################
|
||||
|
||||
import shutil
|
||||
import xml.etree.ElementTree as etree
|
||||
|
||||
import xbmc
|
||||
import xbmcaddon
|
||||
import xbmcvfs
|
||||
|
||||
import clientinfo
|
||||
import utils
|
||||
|
||||
#################################################################################################
|
||||
|
||||
|
||||
class VideoNodes(object):
|
||||
|
||||
|
||||
def __init__(self):
|
||||
|
||||
clientInfo = clientinfo.ClientInfo()
|
||||
self.addonName = clientInfo.getAddonName()
|
||||
|
||||
self.kodiversion = int(xbmc.getInfoLabel('System.BuildVersion')[:2])
|
||||
|
||||
def logMsg(self, msg, lvl=1):
|
||||
|
||||
className = self.__class__.__name__
|
||||
utils.logMsg("%s %s" % (self.addonName, className), msg, lvl)
|
||||
|
||||
|
||||
def commonRoot(self, order, label, tagname, roottype=1):
|
||||
|
||||
if roottype == 0:
|
||||
# Index
|
||||
root = etree.Element('node', attrib={'order': "%s" % order})
|
||||
elif roottype == 1:
|
||||
# Filter
|
||||
root = etree.Element('node', attrib={'order': "%s" % order, 'type': "filter"})
|
||||
etree.SubElement(root, 'match').text = "all"
|
||||
# Add tag rule
|
||||
rule = etree.SubElement(root, 'rule', attrib={'field': "tag", 'operator': "is"})
|
||||
etree.SubElement(rule, 'value').text = tagname
|
||||
else:
|
||||
# Folder
|
||||
root = etree.Element('node', attrib={'order': "%s" % order, 'type': "folder"})
|
||||
|
||||
etree.SubElement(root, 'label').text = label
|
||||
etree.SubElement(root, 'icon').text = "special://home/addons/plugin.video.emby/icon.png"
|
||||
|
||||
return root
|
||||
|
||||
def viewNode(self, indexnumber, tagname, mediatype, viewtype, viewid, delete=False):
|
||||
|
||||
window = utils.window
|
||||
kodiversion = self.kodiversion
|
||||
|
||||
if viewtype == "mixed":
|
||||
dirname = "%s - %s" % (viewid, mediatype)
|
||||
else:
|
||||
dirname = viewid
|
||||
|
||||
path = xbmc.translatePath("special://profile/library/video/").decode('utf-8')
|
||||
nodepath = xbmc.translatePath(
|
||||
"special://profile/library/video/Emby - %s/" % dirname).decode('utf-8')
|
||||
|
||||
# Verify the video directory
|
||||
if not xbmcvfs.exists(path):
|
||||
shutil.copytree(
|
||||
src=xbmc.translatePath("special://xbmc/system/library/video").decode('utf-8'),
|
||||
dst=xbmc.translatePath("special://profile/library/video").decode('utf-8'))
|
||||
xbmcvfs.exists(path)
|
||||
|
||||
# Create the node directory
|
||||
if not xbmcvfs.exists(nodepath) and not mediatype == "photos":
|
||||
# We need to copy over the default items
|
||||
xbmcvfs.mkdirs(nodepath)
|
||||
else:
|
||||
if delete:
|
||||
dirs, files = xbmcvfs.listdir(nodepath)
|
||||
for file in files:
|
||||
xbmcvfs.delete(nodepath + file)
|
||||
|
||||
self.logMsg("Sucessfully removed videonode: %s." % tagname, 1)
|
||||
return
|
||||
|
||||
# Create index entry
|
||||
nodeXML = "%sindex.xml" % nodepath
|
||||
# Set windows property
|
||||
path = "library://video/Emby - %s/" % dirname
|
||||
for i in range(1, indexnumber):
|
||||
# Verify to make sure we don't create duplicates
|
||||
if window('Emby.nodes.%s.index' % i) == path:
|
||||
return
|
||||
|
||||
if mediatype == "photos":
|
||||
path = "plugin://plugin.video.emby/?id=%s&mode=getsubfolders" % indexnumber
|
||||
|
||||
window('Emby.nodes.%s.index' % indexnumber, value=path)
|
||||
|
||||
# Root
|
||||
if not mediatype == "photos":
|
||||
if viewtype == "mixed":
|
||||
specialtag = "%s - %s" % (tagname, mediatype)
|
||||
root = self.commonRoot(order=0, label=specialtag, tagname=tagname, roottype=0)
|
||||
else:
|
||||
root = self.commonRoot(order=0, label=tagname, tagname=tagname, roottype=0)
|
||||
try:
|
||||
utils.indent(root)
|
||||
except: pass
|
||||
etree.ElementTree(root).write(nodeXML)
|
||||
|
||||
nodetypes = {
|
||||
|
||||
'1': "all",
|
||||
'2': "recent",
|
||||
'3': "recentepisodes",
|
||||
'4': "inprogress",
|
||||
'5': "inprogressepisodes",
|
||||
'6': "unwatched",
|
||||
'7': "nextepisodes",
|
||||
'8': "sets",
|
||||
'9': "genres",
|
||||
'10': "random",
|
||||
'11': "recommended",
|
||||
}
|
||||
mediatypes = {
|
||||
# label according to nodetype per mediatype
|
||||
'movies':
|
||||
{
|
||||
'1': tagname,
|
||||
'2': 30174,
|
||||
'4': 30177,
|
||||
'6': 30189,
|
||||
'8': 20434,
|
||||
'9': 135,
|
||||
'10': 30229,
|
||||
'11': 30230
|
||||
},
|
||||
|
||||
'tvshows':
|
||||
{
|
||||
'1': tagname,
|
||||
'2': 30170,
|
||||
'3': 30175,
|
||||
'4': 30171,
|
||||
'5': 30178,
|
||||
'7': 30179,
|
||||
'9': 135,
|
||||
'10': 30229,
|
||||
'11': 30230
|
||||
},
|
||||
|
||||
'homevideos':
|
||||
{
|
||||
'1': tagname,
|
||||
'2': 30251,
|
||||
'11': 30253
|
||||
},
|
||||
|
||||
'photos':
|
||||
{
|
||||
'1': tagname,
|
||||
'2': 30252,
|
||||
'8': 30255,
|
||||
'11': 30254
|
||||
},
|
||||
|
||||
'musicvideos':
|
||||
{
|
||||
'1': tagname,
|
||||
'2': 30256,
|
||||
'4': 30257,
|
||||
'6': 30258
|
||||
}
|
||||
}
|
||||
|
||||
nodes = mediatypes[mediatype]
|
||||
for node in nodes:
|
||||
|
||||
nodetype = nodetypes[node]
|
||||
nodeXML = "%s%s_%s.xml" % (nodepath, viewid, nodetype)
|
||||
# Get label
|
||||
stringid = nodes[node]
|
||||
if node != "1":
|
||||
label = utils.language(stringid)
|
||||
if not label:
|
||||
label = xbmc.getLocalizedString(stringid)
|
||||
else:
|
||||
label = stringid
|
||||
|
||||
# Set window properties
|
||||
if (mediatype == "homevideos" or mediatype == "photos") and nodetype == "all":
|
||||
# Custom query
|
||||
path = ("plugin://plugin.video.emby/?id=%s&mode=browsecontent&type=%s"
|
||||
% (tagname, mediatype))
|
||||
elif (mediatype == "homevideos" or mediatype == "photos"):
|
||||
# Custom query
|
||||
path = ("plugin://plugin.video.emby/?id=%s&mode=browsecontent&type=%s&folderid=%s"
|
||||
% (tagname, mediatype, nodetype))
|
||||
elif nodetype == "nextepisodes":
|
||||
# Custom query
|
||||
path = "plugin://plugin.video.emby/?id=%s&mode=nextup&limit=25" % tagname
|
||||
elif kodiversion == 14 and nodetype == "recentepisodes":
|
||||
# Custom query
|
||||
path = "plugin://plugin.video.emby/?id=%s&mode=recentepisodes&limit=25" % tagname
|
||||
elif kodiversion == 14 and nodetype == "inprogressepisodes":
|
||||
# Custom query
|
||||
path = "plugin://plugin.video.emby/?id=%s&mode=inprogressepisodes&limit=25"% tagname
|
||||
else:
|
||||
path = "library://video/Emby - %s/%s_%s.xml" % (dirname, viewid, nodetype)
|
||||
|
||||
if mediatype == "photos":
|
||||
windowpath = "ActivateWindow(Pictures,%s,return)" % path
|
||||
else:
|
||||
windowpath = "ActivateWindow(Video,%s,return)" % path
|
||||
|
||||
if nodetype == "all":
|
||||
|
||||
if viewtype == "mixed":
|
||||
templabel = "%s - %s" % (tagname, mediatype)
|
||||
else:
|
||||
templabel = label
|
||||
|
||||
embynode = "Emby.nodes.%s" % indexnumber
|
||||
window('%s.title' % embynode, value=templabel)
|
||||
window('%s.path' % embynode, value=windowpath)
|
||||
window('%s.content' % embynode, value=path)
|
||||
window('%s.type' % embynode, value=mediatype)
|
||||
else:
|
||||
embynode = "Emby.nodes.%s.%s" % (indexnumber, nodetype)
|
||||
window('%s.title' % embynode, value=label)
|
||||
window('%s.path' % embynode, value=windowpath)
|
||||
window('%s.content' % embynode, value=path)
|
||||
|
||||
if mediatype == "photos":
|
||||
# For photos, we do not create a node in videos but we do want the window props
|
||||
# to be created.
|
||||
# To do: add our photos nodes to kodi picture sources somehow
|
||||
continue
|
||||
|
||||
if xbmcvfs.exists(nodeXML):
|
||||
# Don't recreate xml if already exists
|
||||
continue
|
||||
|
||||
# Create the root
|
||||
if (nodetype == "nextepisodes" or mediatype == "homevideos" or
|
||||
(kodiversion == 14 and nodetype in ('recentepisodes', 'inprogressepisodes'))):
|
||||
# Folder type with plugin path
|
||||
root = self.commonRoot(order=node, label=label, tagname=tagname, roottype=2)
|
||||
etree.SubElement(root, 'path').text = path
|
||||
etree.SubElement(root, 'content').text = "episodes"
|
||||
else:
|
||||
root = self.commonRoot(order=node, label=label, tagname=tagname)
|
||||
if nodetype in ('recentepisodes', 'inprogressepisodes'):
|
||||
etree.SubElement(root, 'content').text = "episodes"
|
||||
else:
|
||||
etree.SubElement(root, 'content').text = mediatype
|
||||
|
||||
limit = "25"
|
||||
# Elements per nodetype
|
||||
if nodetype == "all":
|
||||
etree.SubElement(root, 'order', {'direction': "ascending"}).text = "sorttitle"
|
||||
|
||||
elif nodetype == "recent":
|
||||
etree.SubElement(root, 'order', {'direction': "descending"}).text = "dateadded"
|
||||
etree.SubElement(root, 'limit').text = limit
|
||||
rule = etree.SubElement(root, 'rule', {'field': "playcount", 'operator': "is"})
|
||||
etree.SubElement(rule, 'value').text = "0"
|
||||
|
||||
elif nodetype == "inprogress":
|
||||
etree.SubElement(root, 'rule', {'field': "inprogress", 'operator': "true"})
|
||||
etree.SubElement(root, 'limit').text = limit
|
||||
|
||||
elif nodetype == "genres":
|
||||
etree.SubElement(root, 'order', {'direction': "ascending"}).text = "sorttitle"
|
||||
etree.SubElement(root, 'group').text = "genres"
|
||||
|
||||
elif nodetype == "unwatched":
|
||||
etree.SubElement(root, 'order', {'direction': "ascending"}).text = "sorttitle"
|
||||
rule = etree.SubElement(root, "rule", {'field': "playcount", 'operator': "is"})
|
||||
etree.SubElement(rule, 'value').text = "0"
|
||||
|
||||
elif nodetype == "sets":
|
||||
etree.SubElement(root, 'order', {'direction': "ascending"}).text = "sorttitle"
|
||||
etree.SubElement(root, 'group').text = "sets"
|
||||
|
||||
elif nodetype == "random":
|
||||
etree.SubElement(root, 'order', {'direction': "ascending"}).text = "random"
|
||||
etree.SubElement(root, 'limit').text = limit
|
||||
|
||||
elif nodetype == "recommended":
|
||||
etree.SubElement(root, 'order', {'direction': "descending"}).text = "rating"
|
||||
etree.SubElement(root, 'limit').text = limit
|
||||
rule = etree.SubElement(root, 'rule', {'field': "playcount", 'operator': "is"})
|
||||
etree.SubElement(rule, 'value').text = "0"
|
||||
rule2 = etree.SubElement(root, 'rule',
|
||||
attrib={'field': "rating", 'operator': "greaterthan"})
|
||||
etree.SubElement(rule2, 'value').text = "7"
|
||||
|
||||
elif nodetype == "recentepisodes":
|
||||
# Kodi Isengard, Jarvis
|
||||
etree.SubElement(root, 'order', {'direction': "descending"}).text = "dateadded"
|
||||
etree.SubElement(root, 'limit').text = limit
|
||||
rule = etree.SubElement(root, 'rule', {'field': "playcount", 'operator': "is"})
|
||||
etree.SubElement(rule, 'value').text = "0"
|
||||
|
||||
elif nodetype == "inprogressepisodes":
|
||||
# Kodi Isengard, Jarvis
|
||||
etree.SubElement(root, 'limit').text = "25"
|
||||
rule = etree.SubElement(root, 'rule',
|
||||
attrib={'field': "inprogress", 'operator':"true"})
|
||||
|
||||
try:
|
||||
utils.indent(root)
|
||||
except: pass
|
||||
etree.ElementTree(root).write(nodeXML)
|
||||
|
||||
def singleNode(self, indexnumber, tagname, mediatype, itemtype):
|
||||
|
||||
window = utils.window
|
||||
|
||||
tagname = tagname.encode('utf-8')
|
||||
cleantagname = utils.normalize_nodes(tagname)
|
||||
nodepath = xbmc.translatePath("special://profile/library/video/").decode('utf-8')
|
||||
nodeXML = "%semby_%s.xml" % (nodepath, cleantagname)
|
||||
path = "library://video/emby_%s.xml" % cleantagname
|
||||
windowpath = "ActivateWindow(Video,%s,return)" % path
|
||||
|
||||
# Create the video node directory
|
||||
if not xbmcvfs.exists(nodepath):
|
||||
# We need to copy over the default items
|
||||
shutil.copytree(
|
||||
src=xbmc.translatePath("special://xbmc/system/library/video").decode('utf-8'),
|
||||
dst=xbmc.translatePath("special://profile/library/video").decode('utf-8'))
|
||||
xbmcvfs.exists(path)
|
||||
|
||||
labels = {
|
||||
|
||||
'Favorite movies': 30180,
|
||||
'Favorite tvshows': 30181,
|
||||
'channels': 30173
|
||||
}
|
||||
label = utils.language(labels[tagname])
|
||||
embynode = "Emby.nodes.%s" % indexnumber
|
||||
window('%s.title' % embynode, value=label)
|
||||
window('%s.path' % embynode, value=windowpath)
|
||||
window('%s.content' % embynode, value=path)
|
||||
window('%s.type' % embynode, value=itemtype)
|
||||
|
||||
if xbmcvfs.exists(nodeXML):
|
||||
# Don't recreate xml if already exists
|
||||
return
|
||||
|
||||
if itemtype == "channels":
|
||||
root = self.commonRoot(order=1, label=label, tagname=tagname, roottype=2)
|
||||
etree.SubElement(root, 'path').text = "plugin://plugin.video.emby/?id=0&mode=channels"
|
||||
else:
|
||||
root = self.commonRoot(order=1, label=label, tagname=tagname)
|
||||
etree.SubElement(root, 'order', {'direction': "ascending"}).text = "sorttitle"
|
||||
|
||||
etree.SubElement(root, 'content').text = mediatype
|
||||
|
||||
try:
|
||||
utils.indent(root)
|
||||
except: pass
|
||||
etree.ElementTree(root).write(nodeXML)
|
||||
|
||||
def clearProperties(self):
|
||||
|
||||
window = utils.window
|
||||
|
||||
self.logMsg("Clearing nodes properties.", 1)
|
||||
embyprops = window('Emby.nodes.total')
|
||||
propnames = [
|
||||
|
||||
"index","path","title","content",
|
||||
"inprogress.content","inprogress.title",
|
||||
"inprogress.content","inprogress.path",
|
||||
"nextepisodes.title","nextepisodes.content",
|
||||
"nextepisodes.path","unwatched.title",
|
||||
"unwatched.content","unwatched.path",
|
||||
"recent.title","recent.content","recent.path",
|
||||
"recentepisodes.title","recentepisodes.content",
|
||||
"recentepisodes.path","inprogressepisodes.title",
|
||||
"inprogressepisodes.content","inprogressepisodes.path"
|
||||
]
|
||||
|
||||
if embyprops:
|
||||
totalnodes = int(embyprops)
|
||||
for i in range(totalnodes):
|
||||
for prop in propnames:
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#################################################################################################
|
||||
|
||||
import shutil
|
||||
import xml.etree.ElementTree as etree
|
||||
|
||||
import xbmc
|
||||
import xbmcaddon
|
||||
import xbmcvfs
|
||||
|
||||
import clientinfo
|
||||
import utils
|
||||
|
||||
#################################################################################################
|
||||
|
||||
|
||||
class VideoNodes(object):
|
||||
|
||||
|
||||
def __init__(self):
|
||||
|
||||
clientInfo = clientinfo.ClientInfo()
|
||||
self.addonName = clientInfo.getAddonName()
|
||||
|
||||
self.kodiversion = int(xbmc.getInfoLabel('System.BuildVersion')[:2])
|
||||
|
||||
def logMsg(self, msg, lvl=1):
|
||||
|
||||
className = self.__class__.__name__
|
||||
utils.logMsg("%s %s" % (self.addonName, className), msg, lvl)
|
||||
|
||||
|
||||
def commonRoot(self, order, label, tagname, roottype=1):
|
||||
|
||||
if roottype == 0:
|
||||
# Index
|
||||
root = etree.Element('node', attrib={'order': "%s" % order})
|
||||
elif roottype == 1:
|
||||
# Filter
|
||||
root = etree.Element('node', attrib={'order': "%s" % order, 'type': "filter"})
|
||||
etree.SubElement(root, 'match').text = "all"
|
||||
# Add tag rule
|
||||
rule = etree.SubElement(root, 'rule', attrib={'field': "tag", 'operator': "is"})
|
||||
etree.SubElement(rule, 'value').text = tagname
|
||||
else:
|
||||
# Folder
|
||||
root = etree.Element('node', attrib={'order': "%s" % order, 'type': "folder"})
|
||||
|
||||
etree.SubElement(root, 'label').text = label
|
||||
etree.SubElement(root, 'icon').text = "special://home/addons/plugin.video.emby/icon.png"
|
||||
|
||||
return root
|
||||
|
||||
def viewNode(self, indexnumber, tagname, mediatype, viewtype, viewid, delete=False):
|
||||
|
||||
window = utils.window
|
||||
|
||||
if viewtype == "mixed":
|
||||
dirname = "%s - %s" % (viewid, mediatype)
|
||||
else:
|
||||
dirname = viewid
|
||||
|
||||
path = xbmc.translatePath("special://profile/library/video/").decode('utf-8')
|
||||
nodepath = xbmc.translatePath(
|
||||
"special://profile/library/video/Emby - %s/" % dirname).decode('utf-8')
|
||||
|
||||
# Verify the video directory
|
||||
if not xbmcvfs.exists(path):
|
||||
shutil.copytree(
|
||||
src=xbmc.translatePath("special://xbmc/system/library/video").decode('utf-8'),
|
||||
dst=xbmc.translatePath("special://profile/library/video").decode('utf-8'))
|
||||
xbmcvfs.exists(path)
|
||||
|
||||
# Create the node directory
|
||||
if not xbmcvfs.exists(nodepath) and not mediatype == "photos":
|
||||
# We need to copy over the default items
|
||||
xbmcvfs.mkdirs(nodepath)
|
||||
else:
|
||||
if delete:
|
||||
dirs, files = xbmcvfs.listdir(nodepath)
|
||||
for file in files:
|
||||
xbmcvfs.delete(nodepath + file)
|
||||
|
||||
self.logMsg("Sucessfully removed videonode: %s." % tagname, 1)
|
||||
return
|
||||
|
||||
# Create index entry
|
||||
nodeXML = "%sindex.xml" % nodepath
|
||||
# Set windows property
|
||||
path = "library://video/Emby - %s/" % dirname
|
||||
for i in range(1, indexnumber):
|
||||
# Verify to make sure we don't create duplicates
|
||||
if window('Emby.nodes.%s.index' % i) == path:
|
||||
return
|
||||
|
||||
if mediatype == "photos":
|
||||
path = "plugin://plugin.video.emby/?id=%s&mode=getsubfolders" % indexnumber
|
||||
|
||||
window('Emby.nodes.%s.index' % indexnumber, value=path)
|
||||
|
||||
# Root
|
||||
if not mediatype == "photos":
|
||||
if viewtype == "mixed":
|
||||
specialtag = "%s - %s" % (tagname, mediatype)
|
||||
root = self.commonRoot(order=0, label=specialtag, tagname=tagname, roottype=0)
|
||||
else:
|
||||
root = self.commonRoot(order=0, label=tagname, tagname=tagname, roottype=0)
|
||||
try:
|
||||
utils.indent(root)
|
||||
except: pass
|
||||
etree.ElementTree(root).write(nodeXML)
|
||||
|
||||
nodetypes = {
|
||||
|
||||
'1': "all",
|
||||
'2': "recent",
|
||||
'3': "recentepisodes",
|
||||
'4': "inprogress",
|
||||
'5': "inprogressepisodes",
|
||||
'6': "unwatched",
|
||||
'7': "nextepisodes",
|
||||
'8': "sets",
|
||||
'9': "genres",
|
||||
'10': "random",
|
||||
'11': "recommended",
|
||||
}
|
||||
mediatypes = {
|
||||
# label according to nodetype per mediatype
|
||||
'movies':
|
||||
{
|
||||
'1': tagname,
|
||||
'2': 30174,
|
||||
'4': 30177,
|
||||
'6': 30189,
|
||||
'8': 20434,
|
||||
'9': 135,
|
||||
'10': 30229,
|
||||
'11': 30230
|
||||
},
|
||||
|
||||
'tvshows':
|
||||
{
|
||||
'1': tagname,
|
||||
'2': 30170,
|
||||
'3': 30175,
|
||||
'4': 30171,
|
||||
'5': 30178,
|
||||
'7': 30179,
|
||||
'9': 135,
|
||||
'10': 30229,
|
||||
'11': 30230
|
||||
},
|
||||
|
||||
'homevideos':
|
||||
{
|
||||
'1': tagname,
|
||||
'2': 30251,
|
||||
'11': 30253
|
||||
},
|
||||
|
||||
'photos':
|
||||
{
|
||||
'1': tagname,
|
||||
'2': 30252,
|
||||
'8': 30255,
|
||||
'11': 30254
|
||||
},
|
||||
|
||||
'musicvideos':
|
||||
{
|
||||
'1': tagname,
|
||||
'2': 30256,
|
||||
'4': 30257,
|
||||
'6': 30258
|
||||
}
|
||||
}
|
||||
|
||||
nodes = mediatypes[mediatype]
|
||||
for node in nodes:
|
||||
|
||||
nodetype = nodetypes[node]
|
||||
nodeXML = "%s%s_%s.xml" % (nodepath, viewid, nodetype)
|
||||
# Get label
|
||||
stringid = nodes[node]
|
||||
if node != "1":
|
||||
label = utils.language(stringid)
|
||||
if not label:
|
||||
label = xbmc.getLocalizedString(stringid)
|
||||
else:
|
||||
label = stringid
|
||||
|
||||
# Set window properties
|
||||
if (mediatype == "homevideos" or mediatype == "photos") and nodetype == "all":
|
||||
# Custom query
|
||||
path = ("plugin://plugin.video.emby/?id=%s&mode=browsecontent&type=%s"
|
||||
% (tagname, mediatype))
|
||||
elif (mediatype == "homevideos" or mediatype == "photos"):
|
||||
# Custom query
|
||||
path = ("plugin://plugin.video.emby/?id=%s&mode=browsecontent&type=%s&folderid=%s"
|
||||
% (tagname, mediatype, nodetype))
|
||||
elif nodetype == "nextepisodes":
|
||||
# Custom query
|
||||
path = "plugin://plugin.video.emby/?id=%s&mode=nextup&limit=25" % tagname
|
||||
elif self.kodiversion == 14 and nodetype == "recentepisodes":
|
||||
# Custom query
|
||||
path = "plugin://plugin.video.emby/?id=%s&mode=recentepisodes&limit=25" % tagname
|
||||
elif self.kodiversion == 14 and nodetype == "inprogressepisodes":
|
||||
# Custom query
|
||||
path = "plugin://plugin.video.emby/?id=%s&mode=inprogressepisodes&limit=25"% tagname
|
||||
else:
|
||||
path = "library://video/Emby - %s/%s_%s.xml" % (dirname, viewid, nodetype)
|
||||
|
||||
if mediatype == "photos":
|
||||
windowpath = "ActivateWindow(Pictures,%s,return)" % path
|
||||
else:
|
||||
windowpath = "ActivateWindow(Video,%s,return)" % path
|
||||
|
||||
if nodetype == "all":
|
||||
|
||||
if viewtype == "mixed":
|
||||
templabel = "%s - %s" % (tagname, mediatype)
|
||||
else:
|
||||
templabel = label
|
||||
|
||||
embynode = "Emby.nodes.%s" % indexnumber
|
||||
window('%s.title' % embynode, value=templabel)
|
||||
window('%s.path' % embynode, value=windowpath)
|
||||
window('%s.content' % embynode, value=path)
|
||||
window('%s.type' % embynode, value=mediatype)
|
||||
else:
|
||||
embynode = "Emby.nodes.%s.%s" % (indexnumber, nodetype)
|
||||
window('%s.title' % embynode, value=label)
|
||||
window('%s.path' % embynode, value=windowpath)
|
||||
window('%s.content' % embynode, value=path)
|
||||
|
||||
if mediatype == "photos":
|
||||
# For photos, we do not create a node in videos but we do want the window props
|
||||
# to be created.
|
||||
# To do: add our photos nodes to kodi picture sources somehow
|
||||
continue
|
||||
|
||||
if xbmcvfs.exists(nodeXML):
|
||||
# Don't recreate xml if already exists
|
||||
continue
|
||||
|
||||
# Create the root
|
||||
if (nodetype == "nextepisodes" or mediatype == "homevideos" or
|
||||
(self.kodiversion == 14 and nodetype in ('recentepisodes', 'inprogressepisodes'))):
|
||||
# Folder type with plugin path
|
||||
root = self.commonRoot(order=node, label=label, tagname=tagname, roottype=2)
|
||||
etree.SubElement(root, 'path').text = path
|
||||
etree.SubElement(root, 'content').text = "episodes"
|
||||
else:
|
||||
root = self.commonRoot(order=node, label=label, tagname=tagname)
|
||||
if nodetype in ('recentepisodes', 'inprogressepisodes'):
|
||||
etree.SubElement(root, 'content').text = "episodes"
|
||||
else:
|
||||
etree.SubElement(root, 'content').text = mediatype
|
||||
|
||||
limit = "25"
|
||||
# Elements per nodetype
|
||||
if nodetype == "all":
|
||||
etree.SubElement(root, 'order', {'direction': "ascending"}).text = "sorttitle"
|
||||
|
||||
elif nodetype == "recent":
|
||||
etree.SubElement(root, 'order', {'direction': "descending"}).text = "dateadded"
|
||||
etree.SubElement(root, 'limit').text = limit
|
||||
rule = etree.SubElement(root, 'rule', {'field': "playcount", 'operator': "is"})
|
||||
etree.SubElement(rule, 'value').text = "0"
|
||||
|
||||
elif nodetype == "inprogress":
|
||||
etree.SubElement(root, 'rule', {'field': "inprogress", 'operator': "true"})
|
||||
etree.SubElement(root, 'limit').text = limit
|
||||
|
||||
elif nodetype == "genres":
|
||||
etree.SubElement(root, 'order', {'direction': "ascending"}).text = "sorttitle"
|
||||
etree.SubElement(root, 'group').text = "genres"
|
||||
|
||||
elif nodetype == "unwatched":
|
||||
etree.SubElement(root, 'order', {'direction': "ascending"}).text = "sorttitle"
|
||||
rule = etree.SubElement(root, "rule", {'field': "playcount", 'operator': "is"})
|
||||
etree.SubElement(rule, 'value').text = "0"
|
||||
|
||||
elif nodetype == "sets":
|
||||
etree.SubElement(root, 'order', {'direction': "ascending"}).text = "sorttitle"
|
||||
etree.SubElement(root, 'group').text = "sets"
|
||||
|
||||
elif nodetype == "random":
|
||||
etree.SubElement(root, 'order', {'direction': "ascending"}).text = "random"
|
||||
etree.SubElement(root, 'limit').text = limit
|
||||
|
||||
elif nodetype == "recommended":
|
||||
etree.SubElement(root, 'order', {'direction': "descending"}).text = "rating"
|
||||
etree.SubElement(root, 'limit').text = limit
|
||||
rule = etree.SubElement(root, 'rule', {'field': "playcount", 'operator': "is"})
|
||||
etree.SubElement(rule, 'value').text = "0"
|
||||
rule2 = etree.SubElement(root, 'rule',
|
||||
attrib={'field': "rating", 'operator': "greaterthan"})
|
||||
etree.SubElement(rule2, 'value').text = "7"
|
||||
|
||||
elif nodetype == "recentepisodes":
|
||||
# Kodi Isengard, Jarvis
|
||||
etree.SubElement(root, 'order', {'direction': "descending"}).text = "dateadded"
|
||||
etree.SubElement(root, 'limit').text = limit
|
||||
rule = etree.SubElement(root, 'rule', {'field': "playcount", 'operator': "is"})
|
||||
etree.SubElement(rule, 'value').text = "0"
|
||||
|
||||
elif nodetype == "inprogressepisodes":
|
||||
# Kodi Isengard, Jarvis
|
||||
etree.SubElement(root, 'limit').text = "25"
|
||||
rule = etree.SubElement(root, 'rule',
|
||||
attrib={'field': "inprogress", 'operator':"true"})
|
||||
|
||||
try:
|
||||
utils.indent(root)
|
||||
except: pass
|
||||
etree.ElementTree(root).write(nodeXML)
|
||||
|
||||
def singleNode(self, indexnumber, tagname, mediatype, itemtype):
|
||||
|
||||
window = utils.window
|
||||
|
||||
tagname = tagname.encode('utf-8')
|
||||
cleantagname = utils.normalize_nodes(tagname)
|
||||
nodepath = xbmc.translatePath("special://profile/library/video/").decode('utf-8')
|
||||
nodeXML = "%semby_%s.xml" % (nodepath, cleantagname)
|
||||
path = "library://video/emby_%s.xml" % cleantagname
|
||||
windowpath = "ActivateWindow(Video,%s,return)" % path
|
||||
|
||||
# Create the video node directory
|
||||
if not xbmcvfs.exists(nodepath):
|
||||
# We need to copy over the default items
|
||||
shutil.copytree(
|
||||
src=xbmc.translatePath("special://xbmc/system/library/video").decode('utf-8'),
|
||||
dst=xbmc.translatePath("special://profile/library/video").decode('utf-8'))
|
||||
xbmcvfs.exists(path)
|
||||
|
||||
labels = {
|
||||
|
||||
'Favorite movies': 30180,
|
||||
'Favorite tvshows': 30181,
|
||||
'channels': 30173
|
||||
}
|
||||
label = utils.language(labels[tagname])
|
||||
embynode = "Emby.nodes.%s" % indexnumber
|
||||
window('%s.title' % embynode, value=label)
|
||||
window('%s.path' % embynode, value=windowpath)
|
||||
window('%s.content' % embynode, value=path)
|
||||
window('%s.type' % embynode, value=itemtype)
|
||||
|
||||
if xbmcvfs.exists(nodeXML):
|
||||
# Don't recreate xml if already exists
|
||||
return
|
||||
|
||||
if itemtype == "channels":
|
||||
root = self.commonRoot(order=1, label=label, tagname=tagname, roottype=2)
|
||||
etree.SubElement(root, 'path').text = "plugin://plugin.video.emby/?id=0&mode=channels"
|
||||
else:
|
||||
root = self.commonRoot(order=1, label=label, tagname=tagname)
|
||||
etree.SubElement(root, 'order', {'direction': "ascending"}).text = "sorttitle"
|
||||
|
||||
etree.SubElement(root, 'content').text = mediatype
|
||||
|
||||
try:
|
||||
utils.indent(root)
|
||||
except: pass
|
||||
etree.ElementTree(root).write(nodeXML)
|
||||
|
||||
def clearProperties(self):
|
||||
|
||||
window = utils.window
|
||||
|
||||
self.logMsg("Clearing nodes properties.", 1)
|
||||
embyprops = window('Emby.nodes.total')
|
||||
propnames = [
|
||||
|
||||
"index","path","title","content",
|
||||
"inprogress.content","inprogress.title",
|
||||
"inprogress.content","inprogress.path",
|
||||
"nextepisodes.title","nextepisodes.content",
|
||||
"nextepisodes.path","unwatched.title",
|
||||
"unwatched.content","unwatched.path",
|
||||
"recent.title","recent.content","recent.path",
|
||||
"recentepisodes.title","recentepisodes.content",
|
||||
"recentepisodes.path","inprogressepisodes.title",
|
||||
"inprogressepisodes.content","inprogressepisodes.path"
|
||||
]
|
||||
|
||||
if embyprops:
|
||||
totalnodes = int(embyprops)
|
||||
for i in range(totalnodes):
|
||||
for prop in propnames:
|
||||
window('Emby.nodes.%s.%s' % (str(i), prop), clear=True)
|
File diff suppressed because it is too large
Load diff
|
@ -1,327 +1,319 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
#################################################################################################
|
||||
|
||||
import json
|
||||
import threading
|
||||
import websocket
|
||||
|
||||
import xbmc
|
||||
import xbmcgui
|
||||
|
||||
import clientinfo
|
||||
import downloadutils
|
||||
import librarysync
|
||||
import playlist
|
||||
import userclient
|
||||
import utils
|
||||
|
||||
import logging
|
||||
logging.basicConfig()
|
||||
|
||||
#################################################################################################
|
||||
|
||||
|
||||
class WebSocket_Client(threading.Thread):
|
||||
|
||||
_shared_state = {}
|
||||
|
||||
client = None
|
||||
stopWebsocket = False
|
||||
|
||||
|
||||
def __init__(self):
|
||||
|
||||
self.__dict__ = self._shared_state
|
||||
self.monitor = xbmc.Monitor()
|
||||
|
||||
self.doUtils = downloadutils.DownloadUtils()
|
||||
self.clientInfo = clientinfo.ClientInfo()
|
||||
self.addonName = self.clientInfo.getAddonName()
|
||||
self.deviceId = self.clientInfo.getDeviceId()
|
||||
self.librarySync = librarysync.LibrarySync()
|
||||
|
||||
threading.Thread.__init__(self)
|
||||
|
||||
def logMsg(self, msg, lvl=1):
|
||||
|
||||
self.className = self.__class__.__name__
|
||||
utils.logMsg("%s %s" % (self.addonName, self.className), msg, lvl)
|
||||
|
||||
|
||||
def sendProgressUpdate(self, data):
|
||||
|
||||
log = self.logMsg
|
||||
|
||||
log("sendProgressUpdate", 2)
|
||||
try:
|
||||
messageData = {
|
||||
|
||||
'MessageType': "ReportPlaybackProgress",
|
||||
'Data': data
|
||||
}
|
||||
messageString = json.dumps(messageData)
|
||||
self.client.send(messageString)
|
||||
log("Message data: %s" % messageString, 2)
|
||||
|
||||
except Exception as e:
|
||||
log("Exception: %s" % e, 1)
|
||||
|
||||
def on_message(self, ws, message):
|
||||
|
||||
log = self.logMsg
|
||||
window = utils.window
|
||||
lang = utils.language
|
||||
|
||||
result = json.loads(message)
|
||||
messageType = result['MessageType']
|
||||
data = result['Data']
|
||||
|
||||
if messageType not in ('SessionEnded'):
|
||||
# Mute certain events
|
||||
log("Message: %s" % message, 1)
|
||||
|
||||
if messageType == "Play":
|
||||
# A remote control play command has been sent from the server.
|
||||
itemIds = data['ItemIds']
|
||||
command = data['PlayCommand']
|
||||
|
||||
pl = playlist.Playlist()
|
||||
dialog = xbmcgui.Dialog()
|
||||
|
||||
if command == "PlayNow":
|
||||
dialog.notification(
|
||||
heading="Emby for Kodi",
|
||||
message="%s %s" % (len(itemIds), lang(33004)),
|
||||
icon="special://home/addons/plugin.video.emby/icon.png",
|
||||
sound=False)
|
||||
startat = data.get('StartPositionTicks', 0)
|
||||
pl.playAll(itemIds, startat)
|
||||
|
||||
elif command == "PlayNext":
|
||||
dialog.notification(
|
||||
heading="Emby for Kodi",
|
||||
message="%s %s" % (len(itemIds), lang(33005)),
|
||||
icon="special://home/addons/plugin.video.emby/icon.png",
|
||||
sound=False)
|
||||
newplaylist = pl.modifyPlaylist(itemIds)
|
||||
player = xbmc.Player()
|
||||
if not player.isPlaying():
|
||||
# Only start the playlist if nothing is playing
|
||||
player.play(newplaylist)
|
||||
|
||||
elif messageType == "Playstate":
|
||||
# A remote control update playstate command has been sent from the server.
|
||||
command = data['Command']
|
||||
player = xbmc.Player()
|
||||
|
||||
actions = {
|
||||
|
||||
'Stop': player.stop,
|
||||
'Unpause': player.pause,
|
||||
'Pause': player.pause,
|
||||
'NextTrack': player.playnext,
|
||||
'PreviousTrack': player.playprevious,
|
||||
'Seek': player.seekTime
|
||||
}
|
||||
action = actions[command]
|
||||
if command == "Seek":
|
||||
seekto = data['SeekPositionTicks']
|
||||
seektime = seekto / 10000000.0
|
||||
action(seektime)
|
||||
log("Seek to %s." % seektime, 1)
|
||||
else:
|
||||
action()
|
||||
log("Command: %s completed." % command, 1)
|
||||
|
||||
window('emby_command', value="true")
|
||||
|
||||
elif messageType == "UserDataChanged":
|
||||
# A user changed their personal rating for an item, or their playstate was updated
|
||||
userdata_list = data['UserDataList']
|
||||
self.librarySync.triage_items("userdata", userdata_list)
|
||||
|
||||
elif messageType == "LibraryChanged":
|
||||
|
||||
librarySync = self.librarySync
|
||||
processlist = {
|
||||
|
||||
'added': data['ItemsAdded'],
|
||||
'update': data['ItemsUpdated'],
|
||||
'remove': data['ItemsRemoved']
|
||||
}
|
||||
for action in processlist:
|
||||
librarySync.triage_items(action, processlist[action])
|
||||
|
||||
elif messageType == "GeneralCommand":
|
||||
|
||||
command = data['Name']
|
||||
arguments = data['Arguments']
|
||||
|
||||
if command in ('Mute', 'Unmute', 'SetVolume',
|
||||
'SetSubtitleStreamIndex', 'SetAudioStreamIndex'):
|
||||
|
||||
player = xbmc.Player()
|
||||
# These commands need to be reported back
|
||||
if command == "Mute":
|
||||
xbmc.executebuiltin('Mute')
|
||||
elif command == "Unmute":
|
||||
xbmc.executebuiltin('Mute')
|
||||
elif command == "SetVolume":
|
||||
volume = arguments['Volume']
|
||||
xbmc.executebuiltin('SetVolume(%s[,showvolumebar])' % volume)
|
||||
elif command == "SetAudioStreamIndex":
|
||||
index = int(arguments['Index'])
|
||||
player.setAudioStream(index - 1)
|
||||
elif command == "SetSubtitleStreamIndex":
|
||||
embyindex = int(arguments['Index'])
|
||||
currentFile = player.getPlayingFile()
|
||||
|
||||
mapping = window('emby_%s.indexMapping' % currentFile)
|
||||
if mapping:
|
||||
externalIndex = json.loads(mapping)
|
||||
# If there's external subtitles added via playbackutils
|
||||
for index in externalIndex:
|
||||
if externalIndex[index] == embyindex:
|
||||
player.setSubtitleStream(int(index))
|
||||
break
|
||||
else:
|
||||
# User selected internal subtitles
|
||||
external = len(externalIndex)
|
||||
audioTracks = len(player.getAvailableAudioStreams())
|
||||
player.setSubtitleStream(external + embyindex - audioTracks - 1)
|
||||
else:
|
||||
# Emby merges audio and subtitle index together
|
||||
audioTracks = len(player.getAvailableAudioStreams())
|
||||
player.setSubtitleStream(index - audioTracks - 1)
|
||||
|
||||
# Let service know
|
||||
window('emby_command', value="true")
|
||||
|
||||
elif command == "DisplayMessage":
|
||||
|
||||
header = arguments['Header']
|
||||
text = arguments['Text']
|
||||
xbmcgui.Dialog().notification(
|
||||
heading=header,
|
||||
message=text,
|
||||
icon="special://home/addons/plugin.video.emby/icon.png",
|
||||
time=4000)
|
||||
|
||||
elif command == "SendString":
|
||||
|
||||
string = arguments['String']
|
||||
text = {
|
||||
|
||||
'jsonrpc': "2.0",
|
||||
'id': 0,
|
||||
'method': "Input.SendText",
|
||||
'params': {
|
||||
|
||||
'text': "%s" % string,
|
||||
'done': False
|
||||
}
|
||||
}
|
||||
result = xbmc.executeJSONRPC(json.dumps(text))
|
||||
|
||||
else:
|
||||
builtin = {
|
||||
|
||||
'ToggleFullscreen': 'Action(FullScreen)',
|
||||
'ToggleOsdMenu': 'Action(OSD)',
|
||||
'ToggleContextMenu': 'Action(ContextMenu)',
|
||||
'MoveUp': 'Action(Up)',
|
||||
'MoveDown': 'Action(Down)',
|
||||
'MoveLeft': 'Action(Left)',
|
||||
'MoveRight': 'Action(Right)',
|
||||
'Select': 'Action(Select)',
|
||||
'Back': 'Action(back)',
|
||||
'GoHome': 'ActivateWindow(Home)',
|
||||
'PageUp': 'Action(PageUp)',
|
||||
'NextLetter': 'Action(NextLetter)',
|
||||
'GoToSearch': 'VideoLibrary.Search',
|
||||
'GoToSettings': 'ActivateWindow(Settings)',
|
||||
'PageDown': 'Action(PageDown)',
|
||||
'PreviousLetter': 'Action(PrevLetter)',
|
||||
'TakeScreenshot': 'TakeScreenshot',
|
||||
'ToggleMute': 'Mute',
|
||||
'VolumeUp': 'Action(VolumeUp)',
|
||||
'VolumeDown': 'Action(VolumeDown)',
|
||||
}
|
||||
action = builtin.get(command)
|
||||
if action:
|
||||
xbmc.executebuiltin(action)
|
||||
|
||||
elif messageType == "ServerRestarting":
|
||||
if utils.settings('supressRestartMsg') == "true":
|
||||
xbmcgui.Dialog().notification(
|
||||
heading="Emby for Kodi",
|
||||
message=lang(33006),
|
||||
icon="special://home/addons/plugin.video.emby/icon.png")
|
||||
|
||||
elif messageType == "UserConfigurationUpdated":
|
||||
# Update user data set in userclient
|
||||
userclient.UserClient().userSettings = data
|
||||
self.librarySync.refresh_views = True
|
||||
|
||||
def on_close(self, ws):
|
||||
self.logMsg("Closed.", 2)
|
||||
|
||||
def on_open(self, ws):
|
||||
self.doUtils.postCapabilities(self.deviceId)
|
||||
|
||||
def on_error(self, ws, error):
|
||||
if "10061" in str(error):
|
||||
# Server is offline
|
||||
pass
|
||||
else:
|
||||
self.logMsg("Error: %s" % error, 2)
|
||||
|
||||
def run(self):
|
||||
|
||||
log = self.logMsg
|
||||
window = utils.window
|
||||
monitor = self.monitor
|
||||
|
||||
loglevel = int(window('emby_logLevel'))
|
||||
# websocket.enableTrace(True)
|
||||
|
||||
userId = window('emby_currUser')
|
||||
server = window('emby_server%s' % userId)
|
||||
token = window('emby_accessToken%s' % userId)
|
||||
deviceId = self.deviceId
|
||||
|
||||
# Get the appropriate prefix for the websocket
|
||||
if "https" in server:
|
||||
server = server.replace('https', "wss")
|
||||
else:
|
||||
server = server.replace('http', "ws")
|
||||
|
||||
websocket_url = "%s?api_key=%s&deviceId=%s" % (server, token, deviceId)
|
||||
log("websocket url: %s" % websocket_url, 1)
|
||||
|
||||
self.client = websocket.WebSocketApp(websocket_url,
|
||||
on_message=self.on_message,
|
||||
on_error=self.on_error,
|
||||
on_close=self.on_close)
|
||||
|
||||
self.client.on_open = self.on_open
|
||||
log("----===## Starting WebSocketClient ##===----", 0)
|
||||
|
||||
while not monitor.abortRequested():
|
||||
|
||||
self.client.run_forever(ping_interval=10)
|
||||
if self.stopWebsocket:
|
||||
break
|
||||
|
||||
if monitor.waitForAbort(5):
|
||||
# Abort was requested, exit
|
||||
break
|
||||
|
||||
log("##===---- WebSocketClient Stopped ----===##", 0)
|
||||
|
||||
def stopClient(self):
|
||||
|
||||
self.stopWebsocket = True
|
||||
self.client.close()
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#################################################################################################
|
||||
|
||||
import json
|
||||
import threading
|
||||
import websocket
|
||||
|
||||
import xbmc
|
||||
import xbmcgui
|
||||
|
||||
import clientinfo
|
||||
import downloadutils
|
||||
import librarysync
|
||||
import playlist
|
||||
import userclient
|
||||
import utils
|
||||
|
||||
import logging
|
||||
logging.basicConfig()
|
||||
|
||||
#################################################################################################
|
||||
|
||||
|
||||
class WebSocket_Client(threading.Thread):
|
||||
|
||||
_shared_state = {}
|
||||
|
||||
client = None
|
||||
stopWebsocket = False
|
||||
|
||||
|
||||
def __init__(self):
|
||||
|
||||
self.__dict__ = self._shared_state
|
||||
self.monitor = xbmc.Monitor()
|
||||
|
||||
self.doUtils = downloadutils.DownloadUtils()
|
||||
self.clientInfo = clientinfo.ClientInfo()
|
||||
self.addonName = self.clientInfo.getAddonName()
|
||||
self.deviceId = self.clientInfo.getDeviceId()
|
||||
self.librarySync = librarysync.LibrarySync()
|
||||
|
||||
threading.Thread.__init__(self)
|
||||
|
||||
def logMsg(self, msg, lvl=1):
|
||||
|
||||
self.className = self.__class__.__name__
|
||||
utils.logMsg("%s %s" % (self.addonName, self.className), msg, lvl)
|
||||
|
||||
|
||||
def sendProgressUpdate(self, data):
|
||||
|
||||
self.logMsg("sendProgressUpdate", 2)
|
||||
try:
|
||||
messageData = {
|
||||
|
||||
'MessageType': "ReportPlaybackProgress",
|
||||
'Data': data
|
||||
}
|
||||
messageString = json.dumps(messageData)
|
||||
self.client.send(messageString)
|
||||
self.logMsg("Message data: %s" % messageString, 2)
|
||||
|
||||
except Exception as e:
|
||||
self.logMsg("Exception: %s" % e, 1)
|
||||
|
||||
def on_message(self, ws, message):
|
||||
|
||||
window = utils.window
|
||||
lang = utils.language
|
||||
|
||||
result = json.loads(message)
|
||||
messageType = result['MessageType']
|
||||
data = result['Data']
|
||||
|
||||
if messageType not in ('SessionEnded'):
|
||||
# Mute certain events
|
||||
self.logMsg("Message: %s" % message, 1)
|
||||
|
||||
if messageType == "Play":
|
||||
# A remote control play command has been sent from the server.
|
||||
itemIds = data['ItemIds']
|
||||
command = data['PlayCommand']
|
||||
|
||||
pl = playlist.Playlist()
|
||||
dialog = xbmcgui.Dialog()
|
||||
|
||||
if command == "PlayNow":
|
||||
dialog.notification(
|
||||
heading="Emby for Kodi",
|
||||
message="%s %s" % (len(itemIds), lang(33004)),
|
||||
icon="special://home/addons/plugin.video.emby/icon.png",
|
||||
sound=False)
|
||||
startat = data.get('StartPositionTicks', 0)
|
||||
pl.playAll(itemIds, startat)
|
||||
|
||||
elif command == "PlayNext":
|
||||
dialog.notification(
|
||||
heading="Emby for Kodi",
|
||||
message="%s %s" % (len(itemIds), lang(33005)),
|
||||
icon="special://home/addons/plugin.video.emby/icon.png",
|
||||
sound=False)
|
||||
newplaylist = pl.modifyPlaylist(itemIds)
|
||||
player = xbmc.Player()
|
||||
if not player.isPlaying():
|
||||
# Only start the playlist if nothing is playing
|
||||
player.play(newplaylist)
|
||||
|
||||
elif messageType == "Playstate":
|
||||
# A remote control update playstate command has been sent from the server.
|
||||
command = data['Command']
|
||||
player = xbmc.Player()
|
||||
|
||||
actions = {
|
||||
|
||||
'Stop': player.stop,
|
||||
'Unpause': player.pause,
|
||||
'Pause': player.pause,
|
||||
'NextTrack': player.playnext,
|
||||
'PreviousTrack': player.playprevious,
|
||||
'Seek': player.seekTime
|
||||
}
|
||||
action = actions[command]
|
||||
if command == "Seek":
|
||||
seekto = data['SeekPositionTicks']
|
||||
seektime = seekto / 10000000.0
|
||||
action(seektime)
|
||||
self.logMsg("Seek to %s." % seektime, 1)
|
||||
else:
|
||||
action()
|
||||
self.logMsg("Command: %s completed." % command, 1)
|
||||
|
||||
window('emby_command', value="true")
|
||||
|
||||
elif messageType == "UserDataChanged":
|
||||
# A user changed their personal rating for an item, or their playstate was updated
|
||||
userdata_list = data['UserDataList']
|
||||
self.librarySync.triage_items("userdata", userdata_list)
|
||||
|
||||
elif messageType == "LibraryChanged":
|
||||
|
||||
librarySync = self.librarySync
|
||||
processlist = {
|
||||
|
||||
'added': data['ItemsAdded'],
|
||||
'update': data['ItemsUpdated'],
|
||||
'remove': data['ItemsRemoved']
|
||||
}
|
||||
for action in processlist:
|
||||
librarySync.triage_items(action, processlist[action])
|
||||
|
||||
elif messageType == "GeneralCommand":
|
||||
|
||||
command = data['Name']
|
||||
arguments = data['Arguments']
|
||||
|
||||
if command in ('Mute', 'Unmute', 'SetVolume',
|
||||
'SetSubtitleStreamIndex', 'SetAudioStreamIndex'):
|
||||
|
||||
player = xbmc.Player()
|
||||
# These commands need to be reported back
|
||||
if command == "Mute":
|
||||
xbmc.executebuiltin('Mute')
|
||||
elif command == "Unmute":
|
||||
xbmc.executebuiltin('Mute')
|
||||
elif command == "SetVolume":
|
||||
volume = arguments['Volume']
|
||||
xbmc.executebuiltin('SetVolume(%s[,showvolumebar])' % volume)
|
||||
elif command == "SetAudioStreamIndex":
|
||||
index = int(arguments['Index'])
|
||||
player.setAudioStream(index - 1)
|
||||
elif command == "SetSubtitleStreamIndex":
|
||||
embyindex = int(arguments['Index'])
|
||||
currentFile = player.getPlayingFile()
|
||||
|
||||
mapping = window('emby_%s.indexMapping' % currentFile)
|
||||
if mapping:
|
||||
externalIndex = json.loads(mapping)
|
||||
# If there's external subtitles added via playbackutils
|
||||
for index in externalIndex:
|
||||
if externalIndex[index] == embyindex:
|
||||
player.setSubtitleStream(int(index))
|
||||
break
|
||||
else:
|
||||
# User selected internal subtitles
|
||||
external = len(externalIndex)
|
||||
audioTracks = len(player.getAvailableAudioStreams())
|
||||
player.setSubtitleStream(external + embyindex - audioTracks - 1)
|
||||
else:
|
||||
# Emby merges audio and subtitle index together
|
||||
audioTracks = len(player.getAvailableAudioStreams())
|
||||
player.setSubtitleStream(index - audioTracks - 1)
|
||||
|
||||
# Let service know
|
||||
window('emby_command', value="true")
|
||||
|
||||
elif command == "DisplayMessage":
|
||||
|
||||
header = arguments['Header']
|
||||
text = arguments['Text']
|
||||
xbmcgui.Dialog().notification(
|
||||
heading=header,
|
||||
message=text,
|
||||
icon="special://home/addons/plugin.video.emby/icon.png",
|
||||
time=4000)
|
||||
|
||||
elif command == "SendString":
|
||||
|
||||
string = arguments['String']
|
||||
text = {
|
||||
|
||||
'jsonrpc': "2.0",
|
||||
'id': 0,
|
||||
'method': "Input.SendText",
|
||||
'params': {
|
||||
|
||||
'text': "%s" % string,
|
||||
'done': False
|
||||
}
|
||||
}
|
||||
result = xbmc.executeJSONRPC(json.dumps(text))
|
||||
|
||||
else:
|
||||
builtin = {
|
||||
|
||||
'ToggleFullscreen': 'Action(FullScreen)',
|
||||
'ToggleOsdMenu': 'Action(OSD)',
|
||||
'ToggleContextMenu': 'Action(ContextMenu)',
|
||||
'MoveUp': 'Action(Up)',
|
||||
'MoveDown': 'Action(Down)',
|
||||
'MoveLeft': 'Action(Left)',
|
||||
'MoveRight': 'Action(Right)',
|
||||
'Select': 'Action(Select)',
|
||||
'Back': 'Action(back)',
|
||||
'GoHome': 'ActivateWindow(Home)',
|
||||
'PageUp': 'Action(PageUp)',
|
||||
'NextLetter': 'Action(NextLetter)',
|
||||
'GoToSearch': 'VideoLibrary.Search',
|
||||
'GoToSettings': 'ActivateWindow(Settings)',
|
||||
'PageDown': 'Action(PageDown)',
|
||||
'PreviousLetter': 'Action(PrevLetter)',
|
||||
'TakeScreenshot': 'TakeScreenshot',
|
||||
'ToggleMute': 'Mute',
|
||||
'VolumeUp': 'Action(VolumeUp)',
|
||||
'VolumeDown': 'Action(VolumeDown)',
|
||||
}
|
||||
action = builtin.get(command)
|
||||
if action:
|
||||
xbmc.executebuiltin(action)
|
||||
|
||||
elif messageType == "ServerRestarting":
|
||||
if utils.settings('supressRestartMsg') == "true":
|
||||
xbmcgui.Dialog().notification(
|
||||
heading="Emby for Kodi",
|
||||
message=lang(33006),
|
||||
icon="special://home/addons/plugin.video.emby/icon.png")
|
||||
|
||||
elif messageType == "UserConfigurationUpdated":
|
||||
# Update user data set in userclient
|
||||
userclient.UserClient().userSettings = data
|
||||
self.librarySync.refresh_views = True
|
||||
|
||||
def on_close(self, ws):
|
||||
self.logMsg("Closed.", 2)
|
||||
|
||||
def on_open(self, ws):
|
||||
self.doUtils.postCapabilities(self.deviceId)
|
||||
|
||||
def on_error(self, ws, error):
|
||||
if "10061" in str(error):
|
||||
# Server is offline
|
||||
pass
|
||||
else:
|
||||
self.logMsg("Error: %s" % error, 2)
|
||||
|
||||
def run(self):
|
||||
|
||||
window = utils.window
|
||||
loglevel = int(window('emby_logLevel'))
|
||||
# websocket.enableTrace(True)
|
||||
|
||||
userId = window('emby_currUser')
|
||||
server = window('emby_server%s' % userId)
|
||||
token = window('emby_accessToken%s' % userId)
|
||||
# Get the appropriate prefix for the websocket
|
||||
if "https" in server:
|
||||
server = server.replace('https', "wss")
|
||||
else:
|
||||
server = server.replace('http', "ws")
|
||||
|
||||
websocket_url = "%s?api_key=%s&deviceId=%s" % (server, token, self.deviceId)
|
||||
self.logMsg("websocket url: %s" % websocket_url, 1)
|
||||
|
||||
self.client = websocket.WebSocketApp(websocket_url,
|
||||
on_message=self.on_message,
|
||||
on_error=self.on_error,
|
||||
on_close=self.on_close)
|
||||
|
||||
self.client.on_open = self.on_open
|
||||
self.logMsg("----===## Starting WebSocketClient ##===----", 0)
|
||||
|
||||
while not self.monitor.abortRequested():
|
||||
|
||||
self.client.run_forever(ping_interval=10)
|
||||
if self.stopWebsocket:
|
||||
break
|
||||
|
||||
if self.monitor.waitForAbort(5):
|
||||
# Abort was requested, exit
|
||||
break
|
||||
|
||||
self.logMsg("##===---- WebSocketClient Stopped ----===##", 0)
|
||||
|
||||
def stopClient(self):
|
||||
|
||||
self.stopWebsocket = True
|
||||
self.client.close()
|
||||
self.logMsg("Stopping thread.", 1)
|
Loading…
Reference in a new issue