Merge remote-tracking branch 'MediaBrowser/master' into develop
This commit is contained in:
commit
50d142ad0b
18 changed files with 414 additions and 227 deletions
|
@ -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"/>
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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")
|
|
@ -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:
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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":
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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):
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
@ -47,7 +47,10 @@ class Player(xbmc.Player):
|
||||||
def GetPlayStats(self):
|
def GetPlayStats(self):
|
||||||
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")
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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):
|
||||||
|
|
||||||
|
@ -63,7 +63,7 @@ class VideoNodes(object):
|
||||||
xbmcvfs.exists(path)
|
xbmcvfs.exists(path)
|
||||||
|
|
||||||
# Create the node directory
|
# Create the node directory
|
||||||
if not xbmcvfs.exists(nodepath) and not mediatype=="photos":
|
if not xbmcvfs.exists(nodepath) and not mediatype == "photos":
|
||||||
# We need to copy over the default items
|
# We need to copy over the default items
|
||||||
xbmcvfs.mkdirs(nodepath)
|
xbmcvfs.mkdirs(nodepath)
|
||||||
else:
|
else:
|
||||||
|
@ -90,7 +90,7 @@ class VideoNodes(object):
|
||||||
window('Emby.nodes.%s.index' % indexnumber, value=path)
|
window('Emby.nodes.%s.index' % indexnumber, value=path)
|
||||||
|
|
||||||
# Root
|
# Root
|
||||||
if not mediatype=="photos":
|
if not mediatype == "photos":
|
||||||
root = self.commonRoot(order=0, label=tagname, tagname=tagname, roottype=0)
|
root = self.commonRoot(order=0, label=tagname, tagname=tagname, roottype=0)
|
||||||
try:
|
try:
|
||||||
utils.indent(root)
|
utils.indent(root)
|
||||||
|
@ -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)
|
||||||
|
|
|
@ -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" />
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue