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

This commit is contained in:
tomkat83 2016-03-01 10:10:11 +01:00
commit 4704d8e983
16 changed files with 195 additions and 90 deletions

View file

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

View file

@ -1,3 +1,14 @@
version 2.2.4
- Fix external subs being appended to direct play (via add-on playback)
- First attempt at keeping Kodi awake during the initial sync
version 2.2.3
- Fix resume
version 2.2.2
- Fix dialog crash in the manual sync
- Fix view duplicate views appearing via launching the emby add-on, when grouping views in emby
version 2.2.1
- Fix artist/album link for music videos
- Fix progress dialog when the manual sync runs at start up

View file

@ -65,7 +65,8 @@ class Main:
'recentepisodes': entrypoint.getRecentEpisodes,
'refreshplaylist': entrypoint.refreshPlaylist,
'companion': entrypoint.plexCompanion,
'switchuser': entrypoint.switchPlexUser
'switchuser': entrypoint.switchPlexUser,
'deviceid': entrypoint.resetDeviceId
}
if "extrafanart" in sys.argv[0]:

View file

@ -297,6 +297,7 @@
<string id="30532">Duration of the video library pop up (in seconds)</string>
<string id="30533">Duration of the music library pop up (in seconds)</string>
<string id="30534">Server messages</string>
<string id="30535">Generate a new device Id</string>
<!-- service add-on -->
<string id="33000">Welcome</string>
@ -331,5 +332,7 @@
<string id="33029">Comparing tv shows from:</string>
<string id="33030">Comparing episodes from:</string>
<string id="33031">Comparing:</string>
<string id="33032">Failed to generate a new device Id. See your logs for more information.</string>
<string id="33033">A new device Id has been generated. Kodi will now restart.</string>
</strings>

View file

@ -6,6 +6,7 @@ import json
import requests
import os
import urllib
from sqlite3 import OperationalError
import xbmc
import xbmcgui
@ -442,13 +443,16 @@ class Artwork():
connection = utils.kodiSQL('texture')
cursor = connection.cursor()
cursor.execute("SELECT cachedurl FROM texture WHERE url = ?", (url,))
try:
cursor.execute("SELECT cachedurl FROM texture WHERE url = ?", (url,))
cachedurl = cursor.fetchone()[0]
except TypeError:
self.logMsg("Could not find cached url.", 1)
except OperationalError:
self.logMsg("Database is locked. Skip deletion process.", 1)
else: # Delete thumbnail as well as the entry
thumbnails = xbmc.translatePath("special://thumbnails/%s" % cachedurl).decode('utf-8')
self.logMsg("Deleting cached thumbnail: %s" % thumbnails, 1)
@ -457,7 +461,7 @@ class Artwork():
try:
cursor.execute("DELETE FROM texture WHERE url = ?", (url,))
connection.commit()
except:
except OperationalError:
self.logMsg("Issue deleting url from cache. Skipping.", 2)
finally:

View file

@ -144,31 +144,32 @@ class DownloadUtils():
def startSession(self):
log = self.logMsg
self.deviceId = self.clientInfo.getDeviceId()
# User is identified from this point
# Attach authenticated header to the session
verify = None
cert = None
verify = False
header = self.getHeader()
# If user enabled host certificate verification
try:
verify = self.sslverify
cert = self.sslclient
if self.sslclient is not None:
verify = self.sslclient
except:
self.logMsg("Could not load SSL settings.", 1)
log("Could not load SSL settings.", 1)
# Start session
self.s = requests.Session()
self.s.headers = header
self.s.verify = verify
self.s.cert = cert
# Retry connections to the server
self.s.mount("http://", requests.adapters.HTTPAdapter(max_retries=1))
self.s.mount("https://", requests.adapters.HTTPAdapter(max_retries=1))
self.logMsg("Requests session started on: %s" % self.server, 1)
log("Requests session started on: %s" % self.server, 1)
def stopSession(self):
try:
@ -232,7 +233,7 @@ class DownloadUtils():
if utils.settings('sslverify') == "true":
verifyssl = True
if utils.settings('sslcert') != "None":
cert = utils.settings('sslcert')
verifyssl = utils.settings('sslcert')
# Replace for the real values
url = url.replace("{server}", self.server)
@ -245,7 +246,6 @@ class DownloadUtils():
params=parameters,
headers=header,
timeout=timeout,
cert=cert,
verify=verifyssl)
elif type == "POST":
@ -253,7 +253,6 @@ class DownloadUtils():
json=postBody,
headers=header,
timeout=timeout,
cert=cert,
verify=verifyssl)
elif type == "DELETE":
@ -261,7 +260,6 @@ class DownloadUtils():
json=postBody,
headers=header,
timeout=timeout,
cert=cert,
verify=verifyssl)
elif type == "OPTIONS":
@ -287,6 +285,8 @@ class DownloadUtils():
# If user enables ssl verification
try:
verifyssl = self.sslverify
if self.sslclient is not None:
verifyssl = self.sslclient
except AttributeError:
if utils.settings('sslverify') == "true":
verifyssl = True

View file

@ -143,6 +143,15 @@ class Embydb_Functions():
))
self.embycursor.execute(query, (name, tagid, mediafolderid))
def removeView(self, viewid):
query = ' '.join((
"DELETE FROM view",
"WHERE view_id = ?"
))
self.embycursor.execute(query, (viewid,))
def getItem_byId(self, embyid):
embycursor = self.embycursor

View file

@ -203,6 +203,30 @@ def doMainListing():
xbmcplugin.endOfDirectory(int(sys.argv[1]))
##### Generate a new deviceId
def resetDeviceId():
dialog = xbmcgui.Dialog()
language = utils.language
deviceId_old = utils.window('emby_deviceId')
try:
utils.window('emby_deviceId', clear=True)
deviceId = clientinfo.ClientInfo().getDeviceId(reset=True)
except Exception as e:
utils.logMsg("EMBY", "Failed to generate a new device Id: %s" % e, 1)
dialog.ok(
heading="Emby for Kodi",
line1=language(33032))
else:
utils.logMsg("EMBY", "Successfully removed old deviceId: %s New deviceId: %s"
% (deviceId_old, deviceId), 1)
dialog.ok(
heading="Emby for Kodi",
line1=language(33033))
xbmc.executebuiltin('RestartApp')
##### ADD ADDITIONAL USERS #####
def addUser():

View file

@ -1191,12 +1191,12 @@ class TVShows(Items):
season = -1
# Specials ordering within season
# if item.get('AirsAfterSeasonNumber'):
# airsBeforeSeason = item['AirsAfterSeasonNumber']
# airsBeforeEpisode = 4096 # Kodi default number for afterseason ordering
# else:
# airsBeforeSeason = item.get('AirsBeforeSeasonNumber', "-1")
# airsBeforeEpisode = item.get('AirsBeforeEpisodeNumber', "-1")
if item.get('AirsAfterSeasonNumber'):
airsBeforeSeason = item['AirsAfterSeasonNumber']
airsBeforeEpisode = 4096 # Kodi default number for afterseason ordering
else:
airsBeforeSeason = item.get('AirsBeforeSeasonNumber')
airsBeforeEpisode = item.get('AirsBeforeEpisodeNumber')
airsBeforeSeason = "-1"
airsBeforeEpisode = "-1"

View file

@ -38,7 +38,9 @@ class KodiMonitor(xbmc.Monitor):
def onSettingsChanged(self):
# Monitor emby settings
currentPath = utils.settings('useDirectPaths')
# Review reset setting at a later time, need to be adjusted to account for initial setup
# changes.
'''currentPath = utils.settings('useDirectPaths')
if utils.window('emby_pluginpath') != currentPath:
# Plugin path value changed. Offer to reset
self.logMsg("Changed to playback mode detected", 1)
@ -50,7 +52,7 @@ class KodiMonitor(xbmc.Monitor):
"needs to be recreated for the change to be applied. "
"Proceed?"))
if resp:
utils.reset()
utils.reset()'''
currentLog = utils.settings('logLevel')
if utils.window('emby_logLevel') != currentLog:

View file

@ -398,7 +398,6 @@ class LibrarySync(Thread):
xbmc.executebuiltin('UpdateLibrary(video)')
if self.enableMusic:
xbmc.executebuiltin('UpdateLibrary(music)')
elapsedtotal = datetime.now() - starttotal
utils.window('emby_initialScan', clear=True)
self.showKodiNote("%s completed in: %s"
@ -522,29 +521,6 @@ class LibrarySync(Thread):
vnodes.clearProperties()
totalnodes = 0
with embydb.GetEmbyDB() as emby_db:
with kodidb.GetKodiDB('video') as kodi_db:
for folderItem in result:
self.processView(folderItem, kodi_db, emby_db, totalnodes)
else:
# Add video nodes listings
vnodes.singleNode(totalnodes,
"Favorite movies",
"movies",
"favourites")
totalnodes += 1
vnodes.singleNode(totalnodes,
"Favorite tvshows",
"tvshows",
"favourites")
totalnodes += 1
vnodes.singleNode(totalnodes,
"channels",
"movies",
"channels")
totalnodes += 1
# Save total
utils.window('Emby.nodes.total', str(totalnodes))
# update views for all:
self.views = emby_db.getAllViewInfo()
@ -558,6 +534,8 @@ class LibrarySync(Thread):
'itemtype': 'artist'
}
self.views.append(entry)
nodes = [] # Prevent duplicate for nodes of the same type
playlists = [] # Prevent duplicate for playlists of the same type
self.logMsg("views saved: %s" % self.views, 1)
@ -645,6 +623,17 @@ class LibrarySync(Thread):
itemNumber = len(self.updatelist)
if itemNumber == 0:
return True
# Validate the playlist exists or recreate it
if (foldername not in playlists and
mediatype in ('movies', 'tvshows', 'musicvideos')):
utils.playlistXSP(mediatype, foldername, folderid, viewtype)
playlists.append(foldername)
if foldername not in nodes and mediatype != "musicvideos":
vnodes.viewNode(sorted_views.index(foldername), foldername,
mediatype, viewtype, folderid)
if viewtype == "mixed": # Change the value
sorted_views[sorted_views.index(foldername)] = "%ss" % foldername
nodes.append(foldername)
# Run through self.updatelist, get XML metadata per item
# Initiate threads
@ -693,6 +682,10 @@ class LibrarySync(Thread):
thread.start()
threads.append(thread)
self.logMsg("Kodi Infobox thread spawned", 1)
# Remove any old referenced views
log("Removing views: %s" % current_views, 1)
for view in current_views:
emby_db.removeView(view)
# Wait until finished
getMetadataQueue.join()

View file

@ -274,6 +274,16 @@ class PlaybackUtils():
else:
window('%s.refreshid' % embyitem, value=itemid)
# Append external subtitles to stream
playmethod = utils.window('%s.playmethod' % embyitem)
# Only for direct stream
if playmethod in ("DirectStream"):
# Direct play automatically appends external
subtitles = self.externalSubs(playurl)
listitem.setSubtitles(subtitles)
self.setArtwork(listitem)
def externalSubs(self, playurl):
externalsubs = []

View file

@ -414,7 +414,7 @@ class Player(xbmc.Player):
self.doUtils(
"{server}/:/timeline?" + urlencode(postdata), type="GET")
def onPlayBackPaused( self ):
def onPlayBackPaused(self):
currentFile = self.currentFile
self.logMsg("PLAYBACK_PAUSED: %s" % currentFile, 2)
@ -424,7 +424,7 @@ class Player(xbmc.Player):
self.reportPlayback()
def onPlayBackResumed( self ):
def onPlayBackResumed(self):
currentFile = self.currentFile
self.logMsg("PLAYBACK_RESUMED: %s" % currentFile, 2)
@ -434,7 +434,7 @@ class Player(xbmc.Player):
self.reportPlayback()
def onPlayBackSeek( self, time, seekOffset ):
def onPlayBackSeek(self, time, seekOffset):
# Make position when seeking a bit more accurate
currentFile = self.currentFile
self.logMsg("PLAYBACK_SEEK: %s" % currentFile, 2)
@ -445,7 +445,7 @@ class Player(xbmc.Player):
self.reportPlayback()
def onPlayBackStopped( self ):
def onPlayBackStopped(self):
log = self.logMsg
window = utils.window
@ -458,7 +458,7 @@ class Player(xbmc.Player):
log("Clear playlist properties.", 1)
self.stopAll()
def onPlayBackEnded( self ):
def onPlayBackEnded(self):
# Will be called when xbmc stops playing a file
self.logMsg("ONPLAYBACK_ENDED", 2)
utils.window('emby_customPlaylist.seektime', clear=True)
@ -494,6 +494,9 @@ class Player(xbmc.Player):
type = data['Type']
playMethod = data['playmethod']
# Prevent manually mark as watched in Kodi monitor
utils.window('emby_skipWatched%s' % itemid, value="true")
if currentPosition and runtime:
try:
percentComplete = currentPosition / int(runtime)
@ -505,16 +508,6 @@ class Player(xbmc.Player):
log("Percent complete: %s Mark played at: %s"
% (percentComplete, markPlayedAt), 1)
# Prevent manually mark as watched in Kodi monitor
utils.window('emby_skipWatched%s' % itemid, value="true")
self.stopPlayback(data)
# Stop transcoding
if playMethod == "Transcode":
log("Transcoding for %s terminated." % itemid, 1)
url = "{server}/video/:/transcode/universal/stop?session=%s" % self.clientInfo.getDeviceId()
doUtils(url, type="GET")
# Send the delete action to the server.
offerDelete = False
@ -553,6 +546,14 @@ class Player(xbmc.Player):
)
for item in cleanup:
utils.window(item, clear=True)
self.stopPlayback(data)
# Stop transcoding
if playMethod == "Transcode":
log("Transcoding for %s terminated." % itemid, 1)
deviceId = self.clientInfo.getDeviceId()
url = "{server}/emby/Videos/ActiveEncodings?DeviceId=%s" % deviceId
doUtils(url, type="DELETE")
self.played_info.clear()

View file

@ -294,11 +294,11 @@ class Read_EmbyServer():
log("Increase jump limit to: %s" % jump, 1)
return items
def getViews(self, type, root=False):
def getViews(self, mediatype="", root=False, sortedlist=False):
# Build a list of user views
doUtils = self.doUtils
views = []
type = type.lower()
mediatype = mediatype.lower()
if not root:
url = "{server}/emby/Users/{UserId}/Views?format=json"
@ -308,10 +308,8 @@ class Read_EmbyServer():
result = doUtils(url)
try:
items = result['Items']
except TypeError:
self.logMsg("Error retrieving views for type: %s" % type, 2)
self.logMsg("Error retrieving views for type: %s" % mediatype, 2)
else:
for item in items:
@ -336,15 +334,25 @@ class Read_EmbyServer():
if itemId == folder['Id']:
itemtype = folder.get('CollectionType', "mixed")
if (name not in ('Collections', 'Trailers') and (itemtype == type or
(itemtype == "mixed" and type in ("movies", "tvshows")))):
if name not in ('Collections', 'Trailers'):
views.append({
if sortedlist:
views.append({
'name': name,
'type': itemtype,
'id': itemId
})
'name': name,
'type': itemtype,
'id': itemId
})
elif (itemtype == mediatype or
(itemtype == "mixed" and mediatype in ("movies", "tvshows"))):
views.append({
'name': name,
'type': itemtype,
'id': itemId
})
return views

View file

@ -296,7 +296,7 @@ def reset():
deleteNodes()
# Wipe the kodi databases
logMsg("EMBY", "Resetting the Kodi video database.")
logMsg("EMBY", "Resetting the Kodi video database.", 0)
connection = kodiSQL('video')
cursor = connection.cursor()
cursor.execute('SELECT tbl_name FROM sqlite_master WHERE type="table"')
@ -322,7 +322,7 @@ def reset():
cursor.close()
# Wipe the emby database
logMsg("EMBY", "Resetting the Emby database.")
logMsg("EMBY", "Resetting the Emby database.", 0)
connection = kodiSQL('emby')
cursor = connection.cursor()
cursor.execute('SELECT tbl_name FROM sqlite_master WHERE type="table"')
@ -331,15 +331,45 @@ def reset():
tablename = row[0]
if tablename != "version":
cursor.execute("DELETE FROM " + tablename)
cursor.execute('DROP table IF EXISTS emby')
cursor.execute('DROP table IF EXISTS view')
connection.commit()
cursor.close()
# Offer to wipe cached thumbnails
resp = dialog.yesno("Warning", "Removed all cached artwork?")
if resp:
logMsg("EMBY", "Resetting all cached artwork.", 0)
# Remove all existing textures first
path = xbmc.translatePath("special://thumbnails/").decode('utf-8')
if xbmcvfs.exists(path):
allDirs, allFiles = xbmcvfs.listdir(path)
for dir in allDirs:
allDirs, allFiles = xbmcvfs.listdir(path+dir)
for file in allFiles:
if os.path.supports_unicode_filenames:
xbmcvfs.delete(os.path.join(path+dir.decode('utf-8'),file.decode('utf-8')))
else:
xbmcvfs.delete(os.path.join(path.encode('utf-8')+dir,file))
# remove all existing data from texture DB
connection = kodiSQL('texture')
cursor = connection.cursor()
cursor.execute('SELECT tbl_name FROM sqlite_master WHERE type="table"')
rows = cursor.fetchall()
for row in rows:
tableName = row[0]
if(tableName != "version"):
cursor.execute("DELETE FROM " + tableName)
connection.commit()
cursor.close()
# reset the install run flag
settings('SyncInstallRunDone', value="false")
# Remove emby info
resp = dialog.yesno("Warning", "Reset all Emby Addon settings?")
if resp == 1:
if resp:
# Delete the settings
addon = xbmcaddon.Addon()
addondir = xbmc.translatePath(addon.getAddonInfo('profile')).decode('utf-8')
@ -601,17 +631,17 @@ def passwordsXML():
time=1000,
sound=False)
def playlistXSP(mediatype, tagname, viewtype="", delete=False):
def playlistXSP(mediatype, tagname, viewid, viewtype="", delete=False):
# Tagname is in unicode - actions: add or delete
tagname = tagname.encode('utf-8')
cleantagname = normalize_nodes(tagname)
path = xbmc.translatePath("special://profile/playlists/video/").decode('utf-8')
if viewtype == "mixed":
plname = "%s - %s" % (tagname, mediatype)
xsppath = "%sEmby %s - %s.xsp" % (path, cleantagname, mediatype)
xsppath = "%sEmby %s - %s.xsp" % (path, viewid, mediatype)
else:
plname = tagname
xsppath = "%sEmby %s.xsp" % (path, cleantagname)
xsppath = "%sEmby %s.xsp" % (path, viewid)
# Create the playlist directory
if not xbmcvfs.exists(path):
@ -668,7 +698,13 @@ def deleteNodes():
dirs, files = xbmcvfs.listdir(path)
for dir in dirs:
if dir.decode('utf-8').startswith('Emby'):
shutil.rmtree("%s%s" % (path, dir.decode('utf-8')))
try:
shutil.rmtree("%s%s" % (path, dir.decode('utf-8')))
except:
logMsg("EMBY", "Failed to delete directory: %s" % dir.decode('utf-8'))
for file in files:
if file.decode('utf-8').startswith('emby'):
xbmcvfs.delete("%s%s" % (path, file.decode('utf-8')))
try:
xbmcvfs.delete("%s%s" % (path, file.decode('utf-8')))
except:
logMsg("EMBY", "Failed to file: %s" % file.decode('utf-8'))

View file

@ -40,16 +40,15 @@ class VideoNodes(object):
return root
def viewNode(self, indexnumber, tagname, mediatype, viewtype, delete=False):
def viewNode(self, indexnumber, tagname, mediatype, viewtype, viewid, delete=False):
window = utils.window
kodiversion = self.kodiversion
cleantagname = utils.normalize_nodes(tagname.encode('utf-8'))
if viewtype == "mixed":
dirname = "%s - %s" % (cleantagname, mediatype)
dirname = "%s - %s" % (viewid, mediatype)
else:
dirname = cleantagname
dirname = viewid
path = xbmc.translatePath("special://profile/library/video/").decode('utf-8')
nodepath = xbmc.translatePath(
@ -91,7 +90,11 @@ class VideoNodes(object):
# Root
if not mediatype == "photos":
root = self.commonRoot(order=0, label=tagname, tagname=tagname, roottype=0)
if viewtype == "mixed":
specialtag = "%s - %s" % (tagname, mediatype)
root = self.commonRoot(order=0, label=specialtag, tagname=tagname, roottype=0)
else:
root = self.commonRoot(order=0, label=tagname, tagname=tagname, roottype=0)
try:
utils.indent(root)
except: pass
@ -166,7 +169,7 @@ class VideoNodes(object):
for node in nodes:
nodetype = nodetypes[node]
nodeXML = "%s%s_%s.xml" % (nodepath, cleantagname, nodetype)
nodeXML = "%s%s_%s.xml" % (nodepath, viewid, nodetype)
# Get label
stringid = nodes[node]
if node != "1":
@ -193,7 +196,7 @@ class VideoNodes(object):
# Custom query
path = "plugin://plugin.video.plexkodiconnect/?id=%s&mode=inprogressepisodes&limit=25"% tagname
else:
path = "library://video/plex%s/%s_%s.xml" % (dirname, cleantagname, nodetype)
path = "library://video/plex%s/%s_%s.xml" % (dirname, viewid, nodetype)
if mediatype == "photos":
windowpath = "ActivateWindow(Pictures,%s,return)" % path
@ -203,7 +206,7 @@ class VideoNodes(object):
if nodetype == "all":
if viewtype == "mixed":
templabel = dirname
templabel = "%s - %s" % (tagname, mediatype)
else:
templabel = label