Merge remote-tracking branch 'MediaBrowser/master' into develop

This commit is contained in:
tomkat83 2016-02-19 18:47:09 +01:00
commit 50d142ad0b
18 changed files with 414 additions and 227 deletions

View file

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<addon id="plugin.video.plexkodiconnect"
name="PlexKodiConnect"
version="2.1.0"
version="2.2.1"
provider-name="croneter">
<requires>
<import addon="xbmc.python" version="2.1.0"/>

View file

@ -1,3 +1,23 @@
version 2.2.1
- Fix artist/album link for music videos
- Fix progress dialog when the manual sync runs at start up
- Fix encoding error for special characters in emby username
- Offer delete dialog after playback now times out after 2 mins
version 2.1.4
- Removed Emby delete via the Kodi context menu. It is exclusively offered via the extended emby context menu which is available for Isengard or higher. This change was necessary, because there was a risk of wiping the entire library if Kodi decides to run a clean database task and paths were set as plugin paths.
version 2.1.3
- Fix Live TV to terminate ffmpeg processes.
version 2.1.2
- Fix to repair entries if they are deleted by Kodi, but still exists in the Emby database.
version 2.1.1
- Update setting - skip emby delete confirmation, it is now under the extras tab.
- Update setting - new content notification, it's now disables the notification if the time is set to 0.
- Prevent manual sync from running if the add-on is not yet connected to the emby server.
version 2.1.0
- Add a throttle (automatically adjust the number of items requested at once) to prevent crashing during the initial sync
- Do not update the video library when there's a music-only update

View file

@ -27,7 +27,7 @@ import musicutils as musicutils
import api
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
@ -127,11 +127,32 @@ if __name__ == '__main__':
if options[ret] == utils.language(30409):
#delete item from the server
if xbmcgui.Dialog().yesno("Do you really want to delete this item ?", "This will delete the item from the server and the file(s) from disk!"):
delete = True
if utils.settings('skipContextMenu') != "true":
resp = xbmcgui.Dialog().yesno(
heading="Confirm delete",
line1=("Delete file from Emby Server? This will "
"also delete the file(s) from disk!"))
if not resp:
logMsg("User skipped deletion for: %s." % embyid, 1)
delete = False
if delete:
import downloadutils
doUtils = downloadutils.DownloadUtils()
url = "{server}/emby/Items/%s?format=json" % embyid
logMsg("Deleting request: %s" % embyid, 0)
doUtils.downloadUrl(url, type="DELETE")
'''if utils.settings('skipContextMenu') != "true":
if xbmcgui.Dialog().yesno(
heading="Confirm delete",
line1=("Delete file on Emby Server? This will "
"also delete the file(s) from disk!")):
import downloadutils
doUtils = downloadutils.DownloadUtils()
url = "{server}/emby/Items/%s?format=json" % embyid
doUtils.downloadUrl(url, type="DELETE")'''
xbmc.sleep(500)
xbmc.executebuiltin("Container.Update")

View file

@ -112,7 +112,7 @@ class Main:
import librarysync
lib = librarysync.LibrarySync()
if mode == "manualsync":
librarysync.ManualSync()
librarysync.ManualSync(dialog=True)
else:
lib.fullSync(repair=True)
else:

View file

@ -18,7 +18,6 @@
<string id="30022">Advanced</string>
<string id="30024">Username</string><!-- Verified -->
<string id="30026">Use SIMPLEJSON instead of JSON</string>
<string id="30030">Port Number</string><!-- Verified -->
<string id="30036">Number of recent Movies to show:</string>
@ -75,7 +74,7 @@
<string id="30089">Western</string>
<string id="30090">Genre Filter</string>
<string id="30091">Confirm file delete?</string>
<string id="30091">Confirm file deletion</string><!-- Verified -->
<string id="30092">Delete this item? This action will delete media and associated data files.</string>
<string id="30093">Mark Watched</string>
@ -105,11 +104,8 @@
<string id="30120">Show Load Progress</string>
<string id="30121">Loading Content</string>
<string id="30122">Retrieving Data</string>
<string id="30123">Parsing Jason Data</string>
<string id="30124">Downloading Jason Data</string>
<string id="30125">Done</string>
<string id="30126">Processing Item : </string>
<string id="30127">YOUCANUSETHIS</string>
<string id="30128">Play Error</string>
<string id="30129">This item is not playable</string>
<string id="30130">Local path detected</string>
@ -279,14 +275,14 @@
<string id="30510">Direct stream music library</string>
<string id="30511">Playback Mode</string>
<string id="30512">Force artwork caching</string>
<string id="30513">Limit artwork cache threads</string>
<string id="30513">Limit artwork cache threads (recommended for rpi)</string>
<string id="30514">Enable fast startup (requires server plugin)</string>
<string id="30515">Maximum items to request from the server at once</string>
<string id="30516">Playback</string>
<string id="30517">Network credentials</string>
<string id="30518">Enable Emby cinema mode</string>
<string id="30519">Ask to play trailers</string>
<string id="30520">Skip delete confirmation</string>
<string id="30520">Skip Emby delete confirmation for the context menu (use at your own risk)</string>
<string id="30521">Jump back on resume (in seconds)</string>
<string id="30522">Force transcode H265</string>
<string id="30523">Music metadata options (not compatible with direct stream)</string>
@ -300,6 +296,7 @@
<string id="30531">Enable new content notification</string>
<string id="30532">Duration of the video library pop up (in seconds)</string>
<string id="30533">Duration of the music library pop up (in seconds)</string>
<string id="30534">Server messages</string>
<!-- service add-on -->
<string id="33000">Welcome</string>
@ -309,5 +306,30 @@
<string id="33004">items added to playlist</string>
<string id="33005">items queued to playlist</string>
<string id="33006">Server is restarting</string>
<string id="33007">Access is enabled</string>
<string id="33008">Enter password for user:</string>
<string id="33009">Invalid username or password</string>
<string id="33010">Failed to authenticate too many times</string>
<string id="33011">Unable to direct play</string>
<string id="33012">Direct play failed 3 times. Enabled play from HTTP.</string>
<string id="33013">Choose the audio stream</string>
<string id="33014">Choose the subtitles stream</string>
<string id="33015">Delete file from your Emby server?</string>
<string id="33016">Play trailers?</string>
<string id="33017">Gathering movies from:</string>
<string id="33018">Gathering boxsets</string>
<string id="33019">Gathering music videos from:</string>
<string id="33020">Gathering tv shows from:</string>
<string id="33021">Gathering:</string>
<string id="33022">Detected the database needs to be recreated for this version of Emby for Kodi. Proceed?</string>
<string id="33023">Emby for Kod may not work correctly until the database is reset.</string>
<string id="33024">Cancelling the database syncing process. The current Kodi version is unsupported.</string>
<string id="33025">completed in:</string>
<string id="33026">Comparing movies from:</string>
<string id="33027">Comparing boxsets</string>
<string id="33028">Comparing music videos from:</string>
<string id="33029">Comparing tv shows from:</string>
<string id="33030">Comparing episodes from:</string>
<string id="33031">Comparing:</string>
</strings>

View file

@ -314,6 +314,17 @@ class Movies(Items):
kodicursor.execute("select coalesce(max(idMovie),0) from movie")
movieid = kodicursor.fetchone()[0] + 1
else:
# Verification the item is still in Kodi
query = "SELECT * FROM movie WHERE idMovie = ?"
kodicursor.execute(query, (movieid,))
try:
kodicursor.fetchone()[0]
except TypeError:
# item is not found, let's recreate it.
update_item = False
self.logMsg("movieid: %s missing from Kodi, repairing the entry." % movieid, 1)
if not viewtag or not viewid:
# Get view tag from emby
viewtag, viewid, mediatype = self.emby.getView_embyId(itemid)
@ -577,6 +588,17 @@ class MusicVideos(Items):
kodicursor.execute("select coalesce(max(idMVideo),0) from musicvideo")
mvideoid = kodicursor.fetchone()[0] + 1
else:
# Verification the item is still in Kodi
query = "SELECT * FROM musicvideo WHERE idMVideo = ?"
kodicursor.execute(query, (mvideoid,))
try:
kodicursor.fetchone()[0]
except TypeError:
# item is not found, let's recreate it.
update_item = False
self.logMsg("mvideoid: %s missing from Kodi, repairing the entry." % mvideoid, 1)
if not viewtag or not viewid:
# Get view tag from emby
viewtag, viewid, mediatype = self.emby.getView_embyId(itemid)
@ -896,12 +918,38 @@ class TVShows(Items):
if not itemid:
self.logMsg("Cannot parse XML data for TV show", -1)
return
# If the item already exist in the local Kodi DB we'll perform a full item update
# If the item doesn't exist, we'll add it to the database
update_item = True
force_episodes = False
itemid = item['Id']
emby_dbitem = emby_db.getItem_byId(itemid)
try:
showid = emby_dbitem[0]
pathid = emby_dbitem[2]
except TypeError:
update_item = False
kodicursor.execute("select coalesce(max(idShow),0) from tvshow")
showid = kodicursor.fetchone()[0] + 1
else:
# Verification the item is still in Kodi
query = "SELECT * FROM tvshow WHERE idShow = ?"
kodicursor.execute(query, (showid,))
try:
kodicursor.fetchone()[0]
except TypeError:
# item is not found, let's recreate it.
update_item = False
self.logMsg("showid: %s missing from Kodi, repairing the entry." % showid, 1)
# Force re-add episodes after the show is re-created.
force_episodes = True
if viewtag is None or viewid is None:
# Get view tag from emby
viewtag, viewid, mediatype = emby.getView_embyId(itemid)
self.logMsg("View tag found: %s" % viewtag, 2)
# fileId information
checksum = API.getChecksum()
@ -991,8 +1039,6 @@ class TVShows(Items):
pathid = kodi_db.addPath(path)
# Create the tvshow entry
kodicursor.execute("select coalesce(max(idShow),0) from tvshow")
showid = kodicursor.fetchone()[0] + 1
query = (
'''
INSERT INTO tvshow(
@ -1035,15 +1081,13 @@ class TVShows(Items):
tags = [viewtag]
kodi_db.addTags(showid, tags, "tvshow")
def refreshSeasonEntry(self, item, showid):
API = PlexAPI.API(item)
kodicursor = self.kodicursor
kodi_db = self.kodi_db
# Finally, refresh the all season entry
seasonid = kodi_db.addSeason(showid, -1)
# Process artwork for season
allartworks = API.getAllArtwork()
artwork.addArtwork(allartworks, seasonid, "season", kodicursor)
if force_episodes:
# We needed to recreate the show entry. Re-add episodes now.
self.logMsg("Repairing episodes for showid: %s %s" % (showid, title), 1)
all_episodes = emby.getEpisodesbyShow(itemid)
self.added_episode(all_episodes['Items'], None)
def add_updateSeason(self, item, showid=None):
def add_updateSeason(self, item, viewid=None, viewtag=None):
API = PlexAPI.API(item)
@ -1109,6 +1153,17 @@ class TVShows(Items):
kodicursor.execute("select coalesce(max(idEpisode),0) from episode")
episodeid = kodicursor.fetchone()[0] + 1
else:
# Verification the item is still in Kodi
query = "SELECT * FROM episode WHERE idEpisode = ?"
kodicursor.execute(query, (episodeid,))
try:
kodicursor.fetchone()[0]
except TypeError:
# item is not found, let's recreate it.
update_item = False
self.logMsg("episodeid: %s missing from Kodi, repairing the entry." % episodeid, 1)
# fileId information
checksum = API.getChecksum()
dateadded = API.getDateCreated()

View file

@ -299,6 +299,17 @@ class Kodidb_Functions():
'''
)
cursor.execute(query, (actorid, kodiid, mediatype))
elif "Artist" in type:
query = (
'''
INSERT OR REPLACE INTO actor_link(
actor_id, media_id, media_type)
VALUES (?, ?, ?)
'''
)
cursor.execute(query, (actorid, kodiid, mediatype))
# Kodi Helix
else:
query = ' '.join((
@ -423,6 +434,17 @@ class Kodidb_Functions():
cursor.execute(query, (actorid, kodiid))
elif "Artist" in type:
query = (
'''
INSERT OR REPLACE INTO artistlinkmusicvideo(
idArtist, idMVideo)
VALUES (?, ?)
'''
)
cursor.execute(query, (actorid, kodiid))
# Add person image to art table
if thumb:
arttype = type.lower()

View file

@ -76,7 +76,7 @@ class KodiMonitor(xbmc.Monitor):
kodiid = item['id']
type = item['type']
except (KeyError, TypeError):
self.logMsg("Properties already set for item.", 1)
self.logMsg("Item is invalid for playstate update.", 1)
else:
if ((utils.settings('useDirectPaths') == "1" and not type == "song") or
(type == "song" and utils.settings('enableMusic') == "true")):
@ -159,8 +159,11 @@ class KodiMonitor(xbmc.Monitor):
elif method == "VideoLibrary.OnRemove":
try:
# Removed function, because with plugin paths + clean library, it will wipe
# entire library if user has permissions. Instead, use the emby context menu available
# in Isengard and higher version
pass
'''try:
kodiid = data['id']
type = data['type']
except (KeyError, TypeError):
@ -176,7 +179,7 @@ class KodiMonitor(xbmc.Monitor):
except TypeError:
self.logMsg("Could not find itemid in emby database.", 1)
else:
if utils.settings('skipConfirmDelete') != "true":
if utils.settings('skipContextMenu') != "true":
resp = xbmcgui.Dialog().yesno(
heading="Confirm delete",
line1="Delete file on Emby Server?")
@ -184,11 +187,12 @@ class KodiMonitor(xbmc.Monitor):
self.logMsg("User skipped deletion.", 1)
embycursor.close()
return
url = "{server}/emby/Items/%s?format=json" % itemid
self.logMsg("Deleting request: %s" % itemid)
doUtils.downloadUrl(url, type="DELETE")
finally:
embycursor.close()
embycursor.close()'''
elif method == "System.OnWake":

View file

@ -213,7 +213,6 @@ class LibrarySync(Thread):
self.__dict__ = self._shared_state
self.clientInfo = clientinfo.ClientInfo()
self.doUtils = downloadutils.DownloadUtils()
self.user = userclient.UserClient()
self.emby = embyserver.Read_EmbyServer()
self.vnodes = videonodes.VideoNodes()
@ -369,7 +368,6 @@ class LibrarySync(Thread):
message = "Repair sync"
else:
message = "Initial sync"
utils.window('emby_initialScan', value="true")
# Set new timestamp NOW because sync might take a while
self.saveLastSync()
starttotal = datetime.now()
@ -393,8 +391,6 @@ class LibrarySync(Thread):
return False
else:
elapsedTime = datetime.now() - startTime
self.logMsg(
"SyncDatabase (finished %s in: %s)"
% (itemtype, str(elapsedTime).split('.')[0]), 1)
# Let kodi update the views in any case
@ -803,7 +799,6 @@ class LibrarySync(Thread):
mvideos = itemtypes.MusicVideos(embycursor, kodicursor)
views = emby_db.getView_byType('musicvideos')
self.logMsg("Media folders: %s" % views, 1)
for view in views:
@ -817,7 +812,6 @@ class LibrarySync(Thread):
if pdialog:
pdialog.update(
heading="Emby for Kodi",
message="Gathering musicvideos from view: %s..." % viewName)
# Initial or repair sync
all_embymvideos = emby.getMusicVideos(viewId, dialog=pdialog)
@ -840,7 +834,6 @@ class LibrarySync(Thread):
count += 1
mvideos.add_update(embymvideo, viewName, viewId)
else:
self.logMsg("MusicVideos finished.", 2)
return True
@ -1023,13 +1016,6 @@ class LibrarySync(Thread):
viewName,
viewId)
# Remove items from Kodi db?
if self.compare:
# Manual sync, process deletes
with itemtypes.Music() as Music:
for item in self.allKodiElementsId:
if item not in self.allPlexElementsId:
Music.remove(item)
def compareDBVersion(self, current, minimum):
# It returns True is database is up to date. False otherwise.
@ -1080,13 +1066,9 @@ class LibrarySync(Thread):
if (utils.window('emby_dbCheck') != "true" and
self.installSyncDone):
# Verify the validity of the database
currentVersion = utils.settings('dbCreatedWithVersion')
minVersion = utils.window('emby_minDBVersion')
uptoDate = self.compareDBVersion(currentVersion, minVersion)
if not uptoDate:
self.logMsg(
"Db version out of date: %s minimum version required: %s"
% (currentVersion, minVersion), 0)
resp = xbmcgui.Dialog().yesno(
@ -1103,7 +1085,6 @@ class LibrarySync(Thread):
else:
utils.reset()
utils.window('emby_dbCheck', value="true")
if not startupComplete:
# Also runs when installed first
@ -1113,8 +1094,6 @@ class LibrarySync(Thread):
# Database does not exists
self.logMsg(
"The current Kodi version is incompatible "
"with the" + self.addonName + " add-on. Please visit "
"https://github.com/croneter/PlexKodiConnect "
"to know which Kodi versions are supported.", 0)
xbmcgui.Dialog().ok(
@ -1132,8 +1111,6 @@ class LibrarySync(Thread):
startTime = datetime.now()
librarySync = self.fullSync(manualrun=True)
elapsedTime = datetime.now() - startTime
self.logMsg(
"SyncDatabase (finished in: %s) %s"
% (str(elapsedTime).split('.')[0], librarySync), 1)
# Only try the initial sync once per kodi session regardless
# This will prevent an infinite loop in case something goes wrong.

View file

@ -34,6 +34,7 @@ class PlaybackUtils():
self.clientInfo = clientinfo.ClientInfo()
self.addonName = self.clientInfo.getAddonName()
self.doUtils = downloadutils.DownloadUtils().downloadUrl
self.userid = utils.window('emby_currUser')
self.server = utils.window('emby_server%s' % self.userid)
@ -44,7 +45,9 @@ class PlaybackUtils():
def play(self, itemid, dbid=None):
self.logMsg("Play called.", 1)
log = self.logMsg
window = utils.window
settings = utils.settings
item = self.item
# Hack to get only existing entry in PMS response for THIS instance of
@ -54,6 +57,7 @@ class PlaybackUtils():
listitem = xbmcgui.ListItem()
playutils = putils.PlayUtils(item[0])
log("Play called.", 1)
playurl = playutils.getPlayUrl()
if not playurl:
return xbmcplugin.setResolvedUrl(int(sys.argv[1]), False, listitem)
@ -80,7 +84,7 @@ class PlaybackUtils():
sizePlaylist = playlist.size()
self.currentPosition = startPos
propertiesPlayback = utils.window('emby_playbackProps') == "true"
propertiesPlayback = window('emby_playbackProps') == "true"
introsPlaylist = False
dummyPlaylist = False
@ -96,13 +100,13 @@ class PlaybackUtils():
# Otherwise we get a loop.
if not propertiesPlayback:
utils.window('emby_playbackProps', value="true")
self.logMsg("Setting up properties in playlist.", 1)
window('emby_playbackProps', value="true")
log("Setting up properties in playlist.", 1)
if (not homeScreen and not seektime and
utils.window('emby_customPlaylist') != "true"):
window('emby_customPlaylist') != "true"):
self.logMsg("Adding dummy file to playlist.", 2)
log("Adding dummy file to playlist.", 2)
dummyPlaylist = True
playlist.add(playurl, listitem, index=startPos)
# Remove the original item from playlist
@ -116,8 +120,8 @@ class PlaybackUtils():
############### -- CHECK FOR INTROS ################
if (utils.settings('enableCinema') == "true" and not seektime and
not utils.window('emby_customPlaylist') == "true"):
if (settings('enableCinema') == "true" and not seektime and
not window('emby_customPlaylist') == "true"):
# if we have any play them when the movie/show is not being resumed
xml = PF.GetPlexPlaylist(
itemid,
@ -130,7 +134,7 @@ class PlaybackUtils():
if homeScreen and not seektime and not sizePlaylist:
# Extend our current playlist with the actual item to play
# only if there's no playlist first
self.logMsg("Adding main item to playlist.", 1)
log("Adding main item to playlist.", 1)
self.pl.addtoPlaylist(
dbid,
PF.GetKodiTypeFromPlex(API.getType()))
@ -146,14 +150,14 @@ class PlaybackUtils():
# Playlist items don't fail on their first call - skip them
# here, otherwise we'll get two 1st parts
if (counter == 0 and
utils.window('emby_customPlaylist') == "true"):
window('emby_customPlaylist') == "true"):
continue
# Set listitem and properties for each additional parts
API.setPartNumber(counter)
additionalListItem = xbmcgui.ListItem()
additionalPlayurl = playutils.getPlayUrl(
partNumber=counter)
self.logMsg("Adding additional part: %s" % counter, 1)
log("Adding additional part: %s" % counter, 1)
self.setProperties(additionalPlayurl, additionalListItem)
self.setArtwork(additionalListItem)
@ -168,42 +172,42 @@ class PlaybackUtils():
if dummyPlaylist:
# Added a dummy file to the playlist,
# because the first item is going to fail automatically.
self.logMsg("Processed as a playlist. First item is skipped.", 1)
log("Processed as a playlist. First item is skipped.", 1)
return xbmcplugin.setResolvedUrl(int(sys.argv[1]), False, listitem)
# We just skipped adding properties. Reset flag for next time.
elif propertiesPlayback:
self.logMsg("Resetting properties playback flag.", 2)
utils.window('emby_playbackProps', clear=True)
log("Resetting properties playback flag.", 2)
window('emby_playbackProps', clear=True)
#self.pl.verifyPlaylist()
########## SETUP MAIN ITEM ##########
# For transcoding only, ask for audio/subs pref
if utils.window('emby_%s.playmethod' % playurl) == "Transcode":
utils.window('emby_%s.playmethod' % playurl, clear=True)
if window('emby_%s.playmethod' % playurl) == "Transcode":
window('emby_%s.playmethod' % playurl, clear=True)
playurl = playutils.audioSubsPref(playurl, listitem)
utils.window('emby_%s.playmethod' % playurl, value="Transcode")
window('emby_%s.playmethod' % playurl, value="Transcode")
listitem.setPath(playurl)
self.setProperties(playurl, listitem)
############### PLAYBACK ################
if homeScreen and seektime and utils.window('emby_customPlaylist') != "true":
self.logMsg("Play as a widget item.", 1)
if homeScreen and seektime and window('emby_customPlaylist') != "true":
log("Play as a widget item.", 1)
self.setListItem(listitem)
xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, listitem)
elif ((introsPlaylist and utils.window('emby_customPlaylist') == "true") or
elif ((introsPlaylist and window('emby_customPlaylist') == "true") or
(homeScreen and not sizePlaylist)):
# Playlist was created just now, play it.
self.logMsg("Play playlist.", 1)
log("Play playlist.", 1)
xbmc.Player().play(playlist, startpos=startPos)
else:
self.logMsg("Play as a regular item.", 1)
log("Play as a regular item.", 1)
xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, listitem)
def AddTrailers(self, xml):
@ -250,29 +254,31 @@ class PlaybackUtils():
return True
def setProperties(self, playurl, listitem):
window = utils.window
# Set all properties necessary for plugin path playback
itemid = self.API.getRatingKey()
itemtype = self.API.getType()
resume, runtime = self.API.getRuntime()
embyitem = "emby_%s" % playurl
utils.window('%s.runtime' % embyitem, value=str(runtime))
utils.window('%s.type' % embyitem, value=itemtype)
utils.window('%s.itemid' % embyitem, value=itemid)
window('%s.runtime' % embyitem, value=str(runtime))
window('%s.type' % embyitem, value=itemtype)
window('%s.itemid' % embyitem, value=itemid)
# We need to keep track of playQueueItemIDs for Plex Companion
utils.window(
window(
'plex_%s.playQueueItemID'
% playurl, self.API.GetPlayQueueItemID())
utils.window(
window(
'plex_%s.guid'
% playurl, self.API.getGuid())
if itemtype == "episode":
utils.window('%s.refreshid' % embyitem,
window('%s.refreshid' % embyitem,
value=self.API.getParentRatingKey())
else:
utils.window('%s.refreshid' % embyitem, value=itemid)
window('%s.refreshid' % embyitem, value=itemid)
def externalSubs(self, playurl):

View file

@ -31,7 +31,7 @@ class Player(xbmc.Player):
self.__dict__ = self._shared_state
self.clientInfo = clientinfo.ClientInfo()
self.doUtils = downloadutils.DownloadUtils()
self.doUtils = downloadutils.DownloadUtils().downloadUrl
self.xbmcplayer = xbmc.Player()
self.logMsg("Starting playback monitor.", 2)
@ -48,6 +48,9 @@ class Player(xbmc.Player):
return self.playStats
def onPlayBackStarted(self):
log = self.logMsg
window = utils.window
# Will be called when xbmc starts playing a file
xbmcplayer = self.xbmcplayer
self.stopAll()
@ -66,7 +69,7 @@ class Player(xbmc.Player):
except: pass
if count == 5: # try 5 times
self.logMsg("Cancelling playback report...", 1)
log("Cancelling playback report...", 1)
break
else: count += 1
@ -77,42 +80,37 @@ class Player(xbmc.Player):
# Save currentFile for cleanup later
utils.window('plex_lastPlayedFiled', value=currentFile)
# We may need to wait for info to be set in kodi monitor
itemId = utils.window("emby_%s.itemid" % currentFile)
itemId = window("emby_%s.itemid" % currentFile)
tryCount = 0
while not itemId:
xbmc.sleep(200)
itemId = utils.window("emby_%s.itemid" % currentFile)
itemId = window("emby_%s.itemid" % currentFile)
if tryCount == 20: # try 20 times or about 10 seconds
self.logMsg("Could not find itemId, cancelling playback report...", 1)
log("Could not find itemId, cancelling playback report...", 1)
break
else: tryCount += 1
else:
utils.window('Plex_currently_playing_itemid', value=itemId)
self.logMsg("ONPLAYBACK_STARTED: %s itemid: %s" % (currentFile, itemId), 0)
window('Plex_currently_playing_itemid', value=itemId)
log("ONPLAYBACK_STARTED: %s itemid: %s" % (currentFile, itemId), 0)
# Only proceed if an itemId was found.
embyitem = "emby_%s" % currentFile
runtime = utils.window("%s.runtime" % embyitem)
refresh_id = utils.window("%s.refreshid" % embyitem)
playMethod = utils.window("%s.playmethod" % embyitem)
itemType = utils.window("%s.type" % embyitem)
utils.window('emby_skipWatched%s' % itemId, value="true")
customseek = window('emby_customPlaylist.seektime')
# Suspend library sync thread while movie is playing
self.logMsg("Playing itemtype is: %s" % itemType, 1)
log("Playing itemtype is: %s" % itemType, 1)
if itemType in ['movie', 'audio']:
self.logMsg("Suspending library sync while playing", 1)
utils.window('suspend_LibraryThread', value='true')
log("Suspending library sync while playing", 1)
window('suspend_LibraryThread', value='true')
if (utils.window('emby_customPlaylist') == "true" and
utils.window('emby_customPlaylist.seektime')):
if (window('emby_customPlaylist') == "true" and
customseek)):
# Start at, when using custom playlist (play to Kodi from webclient)
seektime = utils.window('emby_customPlaylist.seektime')
self.logMsg("Seeking to: %s" % seektime, 1)
xbmcplayer.seekTime(int(seektime))
utils.window('emby_customPlaylist.seektime', clear=True)
log("Seeking to: %s" % customseek, 1)
xbmcplayer.seekTime(int(customseek))
window('emby_customPlaylist.seektime', clear=True)
seekTime = xbmcplayer.getTime()
@ -151,9 +149,8 @@ class Player(xbmc.Player):
# Get the current audio track and subtitles
if playMethod == "Transcode":
# property set in PlayUtils.py
postdata['AudioStreamIndex'] = utils.window("%sAudioStreamIndex" % currentFile)
postdata['SubtitleStreamIndex'] = utils.window("%sSubtitleStreamIndex"
% currentFile)
postdata['AudioStreamIndex'] = window("%sAudioStreamIndex" % currentFile)
postdata['SubtitleStreamIndex'] = window("%sSubtitleStreamIndex" % currentFile)
else:
# Get the current kodi audio and subtitles and convert to Emby equivalent
tracks_query = {
@ -194,11 +191,11 @@ class Player(xbmc.Player):
# Number of audiotracks to help get Emby Index
audioTracks = len(xbmc.Player().getAvailableAudioStreams())
mapping = utils.window("%s.indexMapping" % embyitem)
mapping = window("%s.indexMapping" % embyitem)
if mapping: # Set in playbackutils.py
self.logMsg("Mapping for external subtitles index: %s" % mapping, 2)
log("Mapping for external subtitles index: %s" % mapping, 2)
externalIndex = json.loads(mapping)
if externalIndex.get(str(indexSubs)):
@ -216,15 +213,15 @@ class Player(xbmc.Player):
# Post playback to server
# self.logMsg("Sending POST play started: %s." % postdata, 2)
# self.doUtils.downloadUrl(url, postBody=postdata, type="POST")
# log("Sending POST play started: %s." % postdata, 2)
# self.doUtils(url, postBody=postdata, type="POST")
# Ensure we do have a runtime
try:
runtime = int(runtime)
except ValueError:
runtime = xbmcplayer.getTotalTime()
self.logMsg("Runtime is missing, Kodi runtime: %s" % runtime, 1)
log("Runtime is missing, Kodi runtime: %s" % runtime, 1)
playQueueVersion = utils.window(
'playQueueVersion')
@ -249,7 +246,7 @@ class Player(xbmc.Player):
}
self.played_info[currentFile] = data
self.logMsg("ADDING_FILE: %s" % self.played_info, 1)
log("ADDING_FILE: %s" % self.played_info, 1)
# log some playback stats
'''if(itemType != None):
@ -271,7 +268,7 @@ class Player(xbmc.Player):
if not self.doNotify:
return
self.logMsg("reportPlayback Called", 2)
log("reportPlayback Called", 2)
xbmcplayer = self.xbmcplayer
# Get current file
@ -389,7 +386,7 @@ class Player(xbmc.Player):
if mapping: # Set in PlaybackUtils.py
self.logMsg("Mapping for external subtitles index: %s" % mapping, 2)
log("Mapping for external subtitles index: %s" % mapping, 2)
externalIndex = json.loads(mapping)
if externalIndex.get(str(indexSubs)):
@ -410,7 +407,7 @@ class Player(xbmc.Player):
# Report progress via websocketclient
# postdata = json.dumps(postdata)
# self.ws.sendProgressUpdate(postdata)
self.doUtils.downloadUrl(
self.doUtils(
"{server}/:/timeline?" + urlencode(postdata), type="GET")
def onPlayBackPaused( self ):
@ -445,13 +442,16 @@ class Player(xbmc.Player):
self.reportPlayback()
def onPlayBackStopped( self ):
log = self.logMsg
window = utils.window
# Will be called when user stops xbmc playing a file
self.logMsg("ONPLAYBACK_STOPPED", 2)
utils.window('emby_customPlaylist', clear=True)
utils.window('emby_customPlaylist.seektime', clear=True)
utils.window('emby_playbackProps', clear=True)
self.logMsg("Clear playlist properties.", 1)
utils.window('suspend_LibraryThread', clear=True)
log("ONPLAYBACK_STOPPED", 2)
window('emby_customPlaylist', clear=True)
window('emby_customPlaylist.seektime', clear=True)
window('emby_playbackProps', clear=True)
window('suspend_LibraryThread', clear=True)
log("Clear playlist properties.", 1)
self.stopAll()
def onPlayBackEnded( self ):
@ -463,20 +463,24 @@ class Player(xbmc.Player):
def stopAll(self):
log = self.logMsg
lang = utils.language
settings = utils.settings
doUtils = self.doUtils
if not self.played_info:
return
self.logMsg("Played_information: %s" % self.played_info, 1)
log("Played_information: %s" % self.played_info, 1)
# Process each items
for item in self.played_info:
data = self.played_info.get(item)
if data:
self.logMsg("Item path: %s" % item, 2)
self.logMsg("Item data: %s" % data, 2)
log("Item path: %s" % item, 2)
log("Item data: %s" % data, 2)
runtime = data['runtime']
currentPosition = data['currentPosition']
@ -493,9 +497,8 @@ class Player(xbmc.Player):
# Runtime is 0.
percentComplete = 0
markPlayedAt = float(utils.settings('markPlayed')) / 100
self.logMsg(
"Percent complete: %s Mark played at: %s"
markPlayedAt = float(settings('markPlayed')) / 100
log("Percent complete: %s Mark played at: %s"
% (percentComplete, markPlayedAt), 1)
# Prevent manually mark as watched in Kodi monitor
@ -504,36 +507,33 @@ class Player(xbmc.Player):
self.stopPlayback(data)
# Stop transcoding
if playMethod == "Transcode":
self.logMsg("Transcoding for %s terminated." % itemid, 1)
log("Transcoding for %s terminated." % itemid, 1)
url = "{server}/video/:/transcode/universal/stop?session=%s" % self.clientInfo.getDeviceId()
doUtils.downloadUrl(url, type="GET")
doUtils(url, type="GET")
# Send the delete action to the server.
offerDelete = False
if type == "Episode" and utils.settings('deleteTV') == "true":
if type == "Episode" and settings('deleteTV') == "true":
offerDelete = True
elif type == "Movie" and utils.settings('deleteMovies') == "true":
elif type == "Movie" and settings('deleteMovies') == "true":
offerDelete = True
if utils.settings('offerDelete') != "true":
if settings('offerDelete') != "true":
# Delete could be disabled, even if the subsetting is enabled.
offerDelete = False
# Plex: never delete
offerDelete = False
if percentComplete >= markPlayedAt and offerDelete:
if utils.settings('skipConfirmDelete') != "true":
resp = xbmcgui.Dialog().yesno(
heading="Confirm delete",
line1="Delete file on Emby Server?")
resp = xbmcgui.Dialog().yesno(lang(30091), lang(33015), autoclose=120000)
if not resp:
self.logMsg("User skipped deletion.", 1)
log("User skipped deletion.", 1)
continue
url = "{server}/emby/Items/%s?format=json" % itemid
self.logMsg("Deleting request: %s" % itemid)
doUtils.downloadUrl(url, type="DELETE")
log("Deleting request: %s" % itemid, 1)
doUtils(url, type="DELETE")
# Clean the WINDOW properties
for filename in self.played_info:
@ -567,4 +567,4 @@ class Player(xbmc.Player):
'duration': int(duration)
}
url = url + urlencode(args)
self.doUtils.downloadUrl(url, type="GET")
self.doUtils(url, type="GET")

View file

@ -28,19 +28,26 @@ class Playlist():
self.emby = embyserver.Read_EmbyServer()
def playAll(self, itemids, startat):
log = self.logMsg
window = utils.window
embyconn = utils.kodiSQL('emby')
embycursor = embyconn.cursor()
emby_db = embydb.Embydb_Functions(embycursor)
player = xbmc.Player()
playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
playlist.clear()
self.logMsg("---*** PLAY ALL ***---", 1)
self.logMsg("Items: %s and start at: %s" % (itemids, startat))
log("---*** PLAY ALL ***---", 1)
log("Items: %s and start at: %s" % (itemids, startat), 1)
started = False
utils.window('emby_customplaylist', value="true")
window('emby_customplaylist', value="true")
if startat != 0:
# Seek to the starting position
utils.window('emby_customplaylist.seektime', str(startat))
window('emby_customplaylist.seektime', str(startat))
with embydb.GetEmbyDB() as emby_db:
for itemid in itemids:
@ -67,12 +74,14 @@ class Playlist():
def modifyPlaylist(self, itemids):
log = self.logMsg
embyconn = utils.kodiSQL('emby')
embycursor = embyconn.cursor()
emby_db = embydb.Embydb_Functions(embycursor)
self.logMsg("---*** ADD TO PLAYLIST ***---", 1)
self.logMsg("Items: %s" % itemids, 1)
log("---*** ADD TO PLAYLIST ***---", 1)
log("Items: %s" % itemids, 1)
player = xbmc.Player()
playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
@ -90,7 +99,7 @@ class Playlist():
# Add to playlist
self.addtoPlaylist(dbid, mediatype)
self.logMsg("Adding %s to playlist." % itemid, 1)
log("Adding %s to playlist." % itemid, 1)
self.verifyPlaylist()
embycursor.close()

View file

@ -37,11 +37,14 @@ class PlayUtils():
Returns the playurl for the part with number partNumber
(movie might consist of several files)
"""
log = self.logMsg
window = utils.window
self.API.setPartNumber(partNumber)
playurl = None
if self.isDirectPlay():
self.logMsg("File is direct playing.", 1)
log("File is direct playing.", 1)
playurl = self.API.getTranscodeVideoPath('DirectPlay')
playurl = playurl.encode('utf-8')
# Set playmethod property
@ -55,7 +58,7 @@ class PlayUtils():
# utils.window('emby_%s.playmethod' % playurl, "DirectStream")
elif self.isTranscoding():
self.logMsg("File is transcoding.", 1)
log("File is transcoding.", 1)
quality = {
'maxVideoBitrate': self.getBitrate(),
'videoResolution': self.getResolution(),
@ -64,7 +67,7 @@ class PlayUtils():
playurl = self.API.getTranscodeVideoPath('Transcode',
quality=quality)
# Set playmethod property
utils.window('emby_%s.playmethod' % playurl, value="Transcode")
window('emby_%s.playmethod' % playurl, value="Transcode")
self.logMsg("The playurl is: %s" % playurl, 1)
return playurl
@ -128,24 +131,26 @@ class PlayUtils():
def fileExists(self):
log = self.logMsg
if 'Path' not in self.item:
# File has no path defined in server
return False
# Convert path to direct play
path = self.directPlay()
self.logMsg("Verifying path: %s" % path, 1)
log("Verifying path: %s" % path, 1)
if xbmcvfs.exists(path):
self.logMsg("Path exists.", 1)
log("Path exists.", 1)
return True
elif ":" not in path:
self.logMsg("Can't verify path, assumed linux. Still try to direct play.", 1)
log("Can't verify path, assumed linux. Still try to direct play.", 1)
return True
else:
self.logMsg("Failed to find file.")
log("Failed to find file.", 1)
return False
def h265enabled(self):
@ -186,7 +191,7 @@ class PlayUtils():
# Verify the bitrate
if not self.isNetworkSufficient():
self.logMsg("The network speed is insufficient to direct stream file.", 1)
log("The network speed is insufficient to direct stream file.", 1)
return False
return True
@ -298,6 +303,9 @@ class PlayUtils():
return res[chosen]
def audioSubsPref(self, listitem, url, part=None):
log = self.logMsg
lang = utils.language
dialog = xbmcgui.Dialog()
# For transcoding only
# Present the list of audio to select from
audioStreamsList = []
@ -365,7 +373,7 @@ class PlayUtils():
subNum += 1
if audioNum > 1:
resp = xbmcgui.Dialog().select("Choose the audio stream", audioStreams)
resp = dialog.select(lang(33013), audioStreams)
if resp > -1:
# User selected audio
playurlprefs['audioStreamID'] = audioStreamsList[resp]
@ -378,7 +386,7 @@ class PlayUtils():
playurlprefs['audioBoost'] = utils.settings('audioBoost')
if subNum > 1:
resp = xbmcgui.Dialog().select("Choose the subtitle stream", subtitleStreams)
resp = dialog.select(lang(33014), subtitleStreams)
if resp == 0:
# User selected no subtitles
playurlprefs["skipSubtitles"] = 1

View file

@ -174,6 +174,8 @@ class Read_EmbyServer():
def getSection(self, parentid, itemtype=None, sortby="SortName", basic=False, dialog=None):
log = self.logMsg
doUtils = self.doUtils
items = {
@ -199,7 +201,7 @@ class Read_EmbyServer():
items['TotalRecordCount'] = total
except TypeError: # Failed to retrieve
self.logMsg("%s:%s Failed to retrieve the server response." % (url, params), 2)
log("%s:%s Failed to retrieve the server response." % (url, params), 2)
else:
index = 0
@ -241,27 +243,27 @@ class Read_EmbyServer():
# Something happened to the connection
if not throttled:
throttled = True
self.logMsg("Throttle activated.", 1)
log("Throttle activated.", 1)
if jump == highestjump:
# We already tried with the highestjump, but it failed. Reset value.
self.logMsg("Reset highest value.", 1)
log("Reset highest value.", 1)
highestjump = 0
# Lower the number by half
if highestjump:
throttled = False
jump = highestjump
self.logMsg("Throttle deactivated.", 1)
log("Throttle deactivated.", 1)
else:
jump = int(jump/4)
self.logMsg("Set jump limit to recover: %s" % jump, 1)
log("Set jump limit to recover: %s" % jump, 2)
retry = 0
while utils.window('emby_online') != "true":
# Wait server to come back online
if retry == 3:
self.logMsg("Unable to reconnect to server. Abort process.", 1)
log("Unable to reconnect to server. Abort process.", 1)
return
retry += 1
@ -289,7 +291,7 @@ class Read_EmbyServer():
increment = 10
jump += increment
self.logMsg("Increase jump limit to: %s" % jump, 1)
log("Increase jump limit to: %s" % jump, 1)
return items
def getViews(self, type, root=False):

View file

@ -73,42 +73,46 @@ class UserClient(threading.Thread):
def getUserId(self):
log = self.logMsg
window = utils.window
settings = utils.settings
username = self.getUsername()
w_userId = utils.window('emby_userId%s' % username)
s_userId = utils.settings('userId%s' % username)
w_userId = window('emby_currUser')
s_userId = settings('userId%s' % username)
# Verify the window property
if w_userId:
if not s_userId:
# Save access token if it's missing from settings
utils.settings('userId%s' % username, value=w_userId)
self.logMsg(
"Returning userId from WINDOW for username: %s UserId: %s"
settings('userId%s' % username, value=w_userId)
log("Returning userId from WINDOW for username: %s UserId: %s"
% (username, w_userId), 2)
return w_userId
# Verify the settings
elif s_userId:
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)
return s_userId
# No userId found
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):
alternate = utils.settings('altip') == "true"
settings = utils.settings
alternate = settings('altip') == "true"
if alternate:
# Alternate host
HTTPS = utils.settings('secondhttps') == "true"
host = utils.settings('secondipaddress')
port = utils.settings('secondport')
HTTPS = settings('secondhttps') == "true"
host = settings('secondipaddress')
port = settings('secondport')
else:
# Original host
HTTPS = utils.settings('https') == "true"
host = utils.settings('ipaddress')
port = utils.settings('port')
HTTPS = settings('https') == "true"
host = settings('ipaddress')
port = settings('port')
server = host + ":" + port
@ -134,33 +138,40 @@ class UserClient(threading.Thread):
def getToken(self):
log = self.logMsg
window = utils.window
settings = utils.settings
username = self.getUsername()
w_token = utils.window('emby_accessToken%s' % username)
s_token = utils.settings('accessToken')
userId = self.getUserId()
w_token = window('emby_accessToken%s' % userId)
s_token = settings('accessToken')
# Verify the window property
if w_token:
if not s_token:
# Save access token if it's missing from settings
utils.settings('accessToken', value=w_token)
self.logMsg("Returning accessToken from WINDOW for username: %s "
"accessToken: xxxxx" % username, 2)
settings('accessToken', value=w_token)
log("Returning accessToken from WINDOW for username: %s accessToken: %s"
% (username, w_token), 2)
return w_token
# Verify the settings
elif s_token:
self.logMsg("Returning accessToken from SETTINGS for username: %s "
"accessToken: xxxxx" % username, 2)
utils.window('emby_accessToken%s' % username, value=s_token)
log("Returning accessToken from SETTINGS for username: %s accessToken: %s"
% (username, s_token), 2)
window('emby_accessToken%s' % username, value=s_token)
return s_token
else:
self.logMsg("No token found.", 1)
log("No token found.", 1)
return ""
def getSSLverify(self):
# Verify host certificate
s_sslverify = utils.settings('sslverify')
if utils.settings('altip') == "true":
s_sslverify = utils.settings('secondsslverify')
settings = utils.settings
s_sslverify = settings('sslverify')
if settings('altip') == "true":
s_sslverify = settings('secondsslverify')
if s_sslverify == "true":
return True
@ -169,9 +180,11 @@ class UserClient(threading.Thread):
def getSSL(self):
# Client side certificate
s_cert = utils.settings('sslcert')
if utils.settings('altip') == "true":
s_cert = utils.settings('secondsslcert')
settings = utils.settings
s_cert = settings('sslcert')
if settings('altip') == "true":
s_cert = settings('secondsslcert')
if s_cert == "None":
return None
@ -205,9 +218,32 @@ class UserClient(threading.Thread):
def hasAccess(self):
return True
# hasAccess is verified in service.py
log = self.logMsg
window = utils.window
url = "{server}/emby/Users?format=json"
result = self.doUtils.downloadUrl(url)
if result == False:
# Access is restricted, set in downloadutils.py via exception
log("Access is restricted.", 1)
self.HasAccess = False
elif window('emby_online') != "true":
# Server connection failed
pass
elif window('emby_serverStatus') == "restricted":
log("Access is granted.", 1)
self.HasAccess = True
window('emby_serverStatus', clear=True)
xbmcgui.Dialog().notification("Emby for Kodi", utils.language(33007))
def loadCurrUser(self, authenticated=False):
window = utils.window
doUtils = self.doUtils
username = self.getUsername()
userId = self.getUserId()
@ -259,6 +295,13 @@ class UserClient(threading.Thread):
return True
def authenticate(self):
log = self.logMsg
lang = utils.language
window = utils.window
settings = utils.settings
dialog = xbmcgui.Dialog()
# Get /profile/addon_data
plx = PlexAPI.PlexAPI()
addondir = xbmc.translatePath(self.addon.getAddonInfo('profile')).decode('utf-8')
@ -270,7 +313,7 @@ class UserClient(threading.Thread):
# If there's no settings.xml
if not hasSettings:
self.logMsg("No settings.xml found.", 0)
log("No settings.xml found.", 1)
self.auth = False
return
# If no user information
@ -332,22 +375,20 @@ class UserClient(threading.Thread):
self.logMsg("Error: user authentication failed.", -1)
utils.settings('accessToken', value="")
utils.settings('userId%s' % username, value="")
log("Too many retries. "
"You can retry by resetting attempts in the addon settings.", 1)
window('emby_serverStatus', value="Stop")
dialog.ok(lang(33001), lang(33010))
# Give 3 attempts at entering password / selecting user
if self.retry == 3:
utils.window('emby_serverStatus', value="Stop")
xbmcgui.Dialog().ok(heading=self.addonName,
line1="Failed to authenticate too many"
"times.",
line2="You can retry by resetting attempts"
" in the addon settings.")
self.retry += 1
self.auth = False
def resetClient(self):
self.logMsg("Reset UserClient authentication.", 1)
username = self.getUsername()
log = self.logMsg
utils.settings('accessToken', value="")
utils.window('emby_accessToken%s' % username, clear=True)
@ -359,7 +400,7 @@ class UserClient(threading.Thread):
def run(self):
self.logMsg("----===## Starting UserClient ##===----", 0)
log("----===## Starting UserClient ##===----", 0)
while not self.threadStopped():
while self.threadSuspended():
@ -367,7 +408,7 @@ class UserClient(threading.Thread):
break
xbmc.sleep(3000)
status = utils.window('emby_serverStatus')
status = window('emby_serverStatus')
if status:
# Verify the connection status to server
if status == "restricted":
@ -376,12 +417,12 @@ class UserClient(threading.Thread):
elif status == "401":
# Unauthorized access, revoke token
utils.window('emby_serverStatus', value="Auth")
window('emby_serverStatus', value="Auth")
self.resetClient()
if self.auth and (self.currUser is None):
# Try to authenticate user
status = utils.window('emby_serverStatus')
status = window('emby_serverStatus')
if not status or status == "Auth":
# Set auth flag because we no longer need
# to authenticate the user
@ -393,13 +434,13 @@ class UserClient(threading.Thread):
# If authenticate failed.
server = self.getServer()
username = self.getUsername()
status = utils.window('emby_serverStatus')
status = window('emby_serverStatus')
# The status Stop is for when user cancelled password dialog.
if server and username and status != "Stop":
# Only if there's information found to login
self.logMsg("Server found: %s" % server, 2)
self.logMsg("Username found: %s" % username, 2)
log("Server found: %s" % server, 2)
log("Username found: %s" % username, 2)
self.auth = True
self.doUtils.stopSession()

View file

@ -17,7 +17,7 @@ import utils
class VideoNodes(object):
def __init__(self):
self.kodiversion = int(xbmc.getInfoLabel("System.BuildVersion")[:2])
self.kodiversion = int(xbmc.getInfoLabel('System.BuildVersion')[:2])
def commonRoot(self, order, label, tagname, roottype=1):
@ -169,7 +169,7 @@ class VideoNodes(object):
nodeXML = "%s%s_%s.xml" % (nodepath, cleantagname, nodetype)
# Get label
stringid = nodes[node]
if node != '1':
if node != "1":
label = utils.language(stringid)
if not label:
label = xbmc.getLocalizedString(stringid)

View file

@ -56,7 +56,6 @@
<setting id="offerDelete" type="bool" label="30114" default="false" />
<setting id="deleteTV" type="bool" label="30115" visible="eq(-1,true)" default="false" subsetting="true" />
<setting id="deleteMovies" type="bool" label="30116" visible="eq(-2,true)" default="false" subsetting="true" />
<setting id="skipConfirmDelete" type="bool" label="30520" visible="eq(-3,true)" default="false" subsetting="true" />
<setting id="resumeJumpBack" type="slider" label="30521" default="10" range="0,1,120" option="int" />
<setting type="sep" />
<setting id="playFromStream" type="bool" label="30002" default="false" />
@ -72,8 +71,9 @@
<category label="30235"><!-- Extras -->
<setting id="enableCoverArt" type="bool" label="30157" default="true" />
<setting id="ignoreSpecialsNextEpisodes" type="bool" label="30527" default="false" />
<setting id="skipContextMenu" type="bool" label="30520" default="false" />
<setting id="additionalUsers" type="text" label="30528" default="" />
<setting type="sep" />
<setting type="lsep" label="30534" />
<setting id="connectMsg" type="bool" label="30249" default="true" />
<setting id="restartMsg" type="bool" label="30530" default="false" />
<setting id="newContent" type="bool" label="30531" default="false" />

View file

@ -181,10 +181,10 @@ class Service():
add = ""
xbmcgui.Dialog().notification(
heading=self.addonName,
message="%s %s%s!"
% (lang(33000), user.currUser, add),
icon="special://home/addons/plugin.video."
"plexkodiconnect/icon.png",
message=("%s %s%s!"
% (lang(33000), user.currUser.decode('utf-8'),
add.decode('utf-8'))),
icon="special://home/addons/plugin.video.emby/icon.png",
time=2000,
sound=False)