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