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"?> <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<addon id="plugin.video.plexkodiconnect" <addon id="plugin.video.plexkodiconnect"
name="PlexKodiConnect" name="PlexKodiConnect"
version="2.1.0" version="2.2.1"
provider-name="croneter"> provider-name="croneter">
<requires> <requires>
<import addon="xbmc.python" version="2.1.0"/> <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 version 2.1.0
- Add a throttle (automatically adjust the number of items requested at once) to prevent crashing during the initial sync - 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 - 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 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
@ -127,11 +127,32 @@ if __name__ == '__main__':
if options[ret] == utils.language(30409): if options[ret] == utils.language(30409):
#delete item from the server #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 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)
doUtils.downloadUrl(url, type="DELETE") 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.sleep(500)
xbmc.executebuiltin("Container.Update") xbmc.executebuiltin("Container.Update")

View file

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

View file

@ -18,7 +18,6 @@
<string id="30022">Advanced</string> <string id="30022">Advanced</string>
<string id="30024">Username</string><!-- Verified --> <string id="30024">Username</string><!-- Verified -->
<string id="30026">Use SIMPLEJSON instead of JSON</string>
<string id="30030">Port Number</string><!-- Verified --> <string id="30030">Port Number</string><!-- Verified -->
<string id="30036">Number of recent Movies to show:</string> <string id="30036">Number of recent Movies to show:</string>
@ -75,7 +74,7 @@
<string id="30089">Western</string> <string id="30089">Western</string>
<string id="30090">Genre Filter</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="30092">Delete this item? This action will delete media and associated data files.</string>
<string id="30093">Mark Watched</string> <string id="30093">Mark Watched</string>
@ -105,11 +104,8 @@
<string id="30120">Show Load Progress</string> <string id="30120">Show Load Progress</string>
<string id="30121">Loading Content</string> <string id="30121">Loading Content</string>
<string id="30122">Retrieving Data</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="30125">Done</string>
<string id="30126">Processing Item : </string> <string id="30126">Processing Item : </string>
<string id="30127">YOUCANUSETHIS</string>
<string id="30128">Play Error</string> <string id="30128">Play Error</string>
<string id="30129">This item is not playable</string> <string id="30129">This item is not playable</string>
<string id="30130">Local path detected</string> <string id="30130">Local path detected</string>
@ -279,14 +275,14 @@
<string id="30510">Direct stream music library</string> <string id="30510">Direct stream music library</string>
<string id="30511">Playback Mode</string> <string id="30511">Playback Mode</string>
<string id="30512">Force artwork caching</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="30514">Enable fast startup (requires server plugin)</string>
<string id="30515">Maximum items to request from the server at once</string> <string id="30515">Maximum items to request from the server at once</string>
<string id="30516">Playback</string> <string id="30516">Playback</string>
<string id="30517">Network credentials</string> <string id="30517">Network credentials</string>
<string id="30518">Enable Emby cinema mode</string> <string id="30518">Enable Emby cinema mode</string>
<string id="30519">Ask to play trailers</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="30521">Jump back on resume (in seconds)</string>
<string id="30522">Force transcode H265</string> <string id="30522">Force transcode H265</string>
<string id="30523">Music metadata options (not compatible with direct stream)</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="30531">Enable new content notification</string>
<string id="30532">Duration of the video library pop up (in seconds)</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="30533">Duration of the music library pop up (in seconds)</string>
<string id="30534">Server messages</string>
<!-- service add-on --> <!-- service add-on -->
<string id="33000">Welcome</string> <string id="33000">Welcome</string>
@ -309,5 +306,30 @@
<string id="33004">items added to playlist</string> <string id="33004">items added to playlist</string>
<string id="33005">items queued to playlist</string> <string id="33005">items queued to playlist</string>
<string id="33006">Server is restarting</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> </strings>

View file

@ -314,6 +314,17 @@ class Movies(Items):
kodicursor.execute("select coalesce(max(idMovie),0) from movie") kodicursor.execute("select coalesce(max(idMovie),0) from movie")
movieid = kodicursor.fetchone()[0] + 1 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: if not viewtag or not viewid:
# Get view tag from emby # Get view tag from emby
viewtag, viewid, mediatype = self.emby.getView_embyId(itemid) viewtag, viewid, mediatype = self.emby.getView_embyId(itemid)
@ -577,6 +588,17 @@ class MusicVideos(Items):
kodicursor.execute("select coalesce(max(idMVideo),0) from musicvideo") kodicursor.execute("select coalesce(max(idMVideo),0) from musicvideo")
mvideoid = kodicursor.fetchone()[0] + 1 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: if not viewtag or not viewid:
# Get view tag from emby # Get view tag from emby
viewtag, viewid, mediatype = self.emby.getView_embyId(itemid) viewtag, viewid, mediatype = self.emby.getView_embyId(itemid)
@ -896,12 +918,38 @@ class TVShows(Items):
if not itemid: if not itemid:
self.logMsg("Cannot parse XML data for TV show", -1) self.logMsg("Cannot parse XML data for TV show", -1)
return 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) emby_dbitem = emby_db.getItem_byId(itemid)
try: try:
showid = emby_dbitem[0] showid = emby_dbitem[0]
pathid = emby_dbitem[2] pathid = emby_dbitem[2]
except TypeError: except TypeError:
update_item = False 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 # fileId information
checksum = API.getChecksum() checksum = API.getChecksum()
@ -991,8 +1039,6 @@ class TVShows(Items):
pathid = kodi_db.addPath(path) pathid = kodi_db.addPath(path)
# Create the tvshow entry # Create the tvshow entry
kodicursor.execute("select coalesce(max(idShow),0) from tvshow")
showid = kodicursor.fetchone()[0] + 1
query = ( query = (
''' '''
INSERT INTO tvshow( INSERT INTO tvshow(
@ -1035,15 +1081,13 @@ class TVShows(Items):
tags = [viewtag] tags = [viewtag]
kodi_db.addTags(showid, tags, "tvshow") kodi_db.addTags(showid, tags, "tvshow")
def refreshSeasonEntry(self, item, showid): if force_episodes:
API = PlexAPI.API(item) # We needed to recreate the show entry. Re-add episodes now.
kodicursor = self.kodicursor self.logMsg("Repairing episodes for showid: %s %s" % (showid, title), 1)
kodi_db = self.kodi_db all_episodes = emby.getEpisodesbyShow(itemid)
# Finally, refresh the all season entry self.added_episode(all_episodes['Items'], None)
seasonid = kodi_db.addSeason(showid, -1)
# Process artwork for season def add_updateSeason(self, item, showid=None):
allartworks = API.getAllArtwork()
artwork.addArtwork(allartworks, seasonid, "season", kodicursor)
def add_updateSeason(self, item, viewid=None, viewtag=None): def add_updateSeason(self, item, viewid=None, viewtag=None):
API = PlexAPI.API(item) API = PlexAPI.API(item)
@ -1109,6 +1153,17 @@ class TVShows(Items):
kodicursor.execute("select coalesce(max(idEpisode),0) from episode") kodicursor.execute("select coalesce(max(idEpisode),0) from episode")
episodeid = kodicursor.fetchone()[0] + 1 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 # fileId information
checksum = API.getChecksum() checksum = API.getChecksum()
dateadded = API.getDateCreated() dateadded = API.getDateCreated()

View file

@ -299,6 +299,17 @@ class Kodidb_Functions():
''' '''
) )
cursor.execute(query, (actorid, kodiid, mediatype)) 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 # Kodi Helix
else: else:
query = ' '.join(( query = ' '.join((
@ -423,6 +434,17 @@ class Kodidb_Functions():
cursor.execute(query, (actorid, kodiid)) 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 # Add person image to art table
if thumb: if thumb:
arttype = type.lower() arttype = type.lower()

View file

@ -76,7 +76,7 @@ class KodiMonitor(xbmc.Monitor):
kodiid = item['id'] kodiid = item['id']
type = item['type'] type = item['type']
except (KeyError, TypeError): except (KeyError, TypeError):
self.logMsg("Properties already set for item.", 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 type == "song") or
(type == "song" and utils.settings('enableMusic') == "true")): (type == "song" and utils.settings('enableMusic') == "true")):
@ -159,8 +159,11 @@ class KodiMonitor(xbmc.Monitor):
elif method == "VideoLibrary.OnRemove": elif method == "VideoLibrary.OnRemove":
# Removed function, because with plugin paths + clean library, it will wipe
try: # entire library if user has permissions. Instead, use the emby context menu available
# in Isengard and higher version
pass
'''try:
kodiid = data['id'] kodiid = data['id']
type = data['type'] type = data['type']
except (KeyError, TypeError): except (KeyError, TypeError):
@ -176,7 +179,7 @@ class KodiMonitor(xbmc.Monitor):
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('skipConfirmDelete') != "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?")
@ -184,11 +187,12 @@ class KodiMonitor(xbmc.Monitor):
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, type="DELETE")
finally: finally:
embycursor.close() embycursor.close()'''
elif method == "System.OnWake": elif method == "System.OnWake":

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -17,7 +17,7 @@ import utils
class VideoNodes(object): class VideoNodes(object):
def __init__(self): 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): def commonRoot(self, order, label, tagname, roottype=1):
@ -169,7 +169,7 @@ class VideoNodes(object):
nodeXML = "%s%s_%s.xml" % (nodepath, cleantagname, nodetype) nodeXML = "%s%s_%s.xml" % (nodepath, cleantagname, 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)

View file

@ -56,7 +56,6 @@
<setting id="offerDelete" type="bool" label="30114" default="false" /> <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="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="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 id="resumeJumpBack" type="slider" label="30521" default="10" range="0,1,120" option="int" />
<setting type="sep" /> <setting type="sep" />
<setting id="playFromStream" type="bool" label="30002" default="false" /> <setting id="playFromStream" type="bool" label="30002" default="false" />
@ -72,8 +71,9 @@
<category label="30235"><!-- Extras --> <category label="30235"><!-- Extras -->
<setting id="enableCoverArt" type="bool" label="30157" default="true" /> <setting id="enableCoverArt" type="bool" label="30157" default="true" />
<setting id="ignoreSpecialsNextEpisodes" type="bool" label="30527" default="false" /> <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 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="connectMsg" type="bool" label="30249" default="true" />
<setting id="restartMsg" type="bool" label="30530" default="false" /> <setting id="restartMsg" type="bool" label="30530" default="false" />
<setting id="newContent" type="bool" label="30531" default="false" /> <setting id="newContent" type="bool" label="30531" default="false" />

View file

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