From 9d0622522894e8e37fe0bd40afba232bcb3d1ecc Mon Sep 17 00:00:00 2001 From: tomkat83 Date: Tue, 12 Apr 2016 17:18:32 +0200 Subject: [PATCH] Centralize Direct Play and Direct Paths --- resources/language/English/strings.xml | 12 +- resources/language/German/strings.xml | 7 +- resources/lib/PlexAPI.py | 172 +++++++++++++++---------- resources/lib/itemtypes.py | 35 ++--- resources/lib/playutils.py | 56 ++++---- resources/settings.xml | 45 ++++--- 6 files changed, 180 insertions(+), 147 deletions(-) diff --git a/resources/language/English/strings.xml b/resources/language/English/strings.xml index 69de6792..ec498e9b 100644 --- a/resources/language/English/strings.xml +++ b/resources/language/English/strings.xml @@ -2,7 +2,7 @@ Server Address (IP) - Deactivate Direct Play and enforce Transcoding + Prefered playback method Log level Username: Password: @@ -130,7 +130,7 @@ Enable Enhanced Images (eg CoverArt) Metadata Artwork - Video Quality + Video Quality for Transcoding Enable Suggested Loader (Requires Restart) Add Season Number @@ -298,7 +298,7 @@ Duration of the music library pop up (in seconds) Server messages [COLOR yellow]Generate a new unique device Id (e.g. when cloning Kodi)[/COLOR] - Users must log in every time when Kodi restarts + Users must log in every time Kodi restarts RESTART KODI IF YOU MAKE ANY CHANGES Complete Re-Sync necessary @@ -384,7 +384,7 @@ Replace Plex TV SHOWS with: Original Plex MUSIC path to replace: Replace Plex MUSIC with: - Go a step further and complete replace all original Plex library paths (/volume1/media) with custom SMB paths (smb://NAS/MyStuff)? + Go a step further and completely replace all original Plex library paths (/volume1/media) with custom SMB paths (smb://NAS/MyStuff)? Please enter your custom smb paths in the settings under "Sync Options" and then restart Kodi Appearance Tweaks @@ -398,8 +398,8 @@ Do a full library sync every x minutes remote Searching for Plex Server - - + Used by Sync and when attempting to Direct Play + Customize Paths Log-out Plex Home User diff --git a/resources/language/German/strings.xml b/resources/language/German/strings.xml index bdf1ab84..e99b8202 100644 --- a/resources/language/German/strings.xml +++ b/resources/language/German/strings.xml @@ -2,7 +2,7 @@ IP-Adresse des Servers Automatisches Öffnen von Ordnern mit einem Eintrag - Direct Play deaktivieren and Transkodieren erzwingen + Gewünschte Wiedergabe-Methode Log Level: Benutzername: Passwort: @@ -163,7 +163,7 @@ Deaktiviere erweiterte Bilder (z.B. CoverArt) Metadaten Grafiken - Videoqualität + Videoqualität für Transkodierung 'Empfohlen'-Loader aktivieren (Erfordert Neustart) Staffelnummer hinzufügen @@ -336,6 +336,9 @@ Kompletten Scan aller Bibliotheken alle x Minuten durchführen remote Suche Plex Server + Verwendet für Synchronisierung sowie beim Versuch, Direct Play zu nutzen + Pfade ändern + diff --git a/resources/lib/PlexAPI.py b/resources/lib/PlexAPI.py index 04f4cd9a..5b01ad5e 100644 --- a/resources/lib/PlexAPI.py +++ b/resources/lib/PlexAPI.py @@ -394,7 +394,7 @@ class PlexAPI(): name, scheme, ip, port, type, owned, token """ address = ip + ':' + port - baseURL = scheme+'://'+ip+':'+port + baseURL = scheme + '://' + ip + ':' + port self.g_PMS[uuid] = { 'name': name, 'scheme': scheme, @@ -485,7 +485,8 @@ class PlexAPI(): elif "Resource-Identifier:" in each: update['uuid'] = each.split(':')[1].strip() elif "Name:" in each: - update['serverName'] = each.split(':')[1].strip().decode('utf-8', 'replace') + update['serverName'] = each.split( + ':')[1].strip().decode('utf-8', 'replace') elif "Port:" in each: update['port'] = each.split(':')[1].strip() elif "Updated-At:" in each: @@ -582,8 +583,8 @@ class PlexAPI(): PMS = {} PMS['name'] = Dir.get('name') infoAge = time.time() - int(Dir.get('lastSeenAt')) - oneDayInSec = 60*60*24 - if infoAge > 2*oneDayInSec: + oneDayInSec = 60 * 60 * 24 + if infoAge > 2 * oneDayInSec: self.logMsg("Server %s not seen for 2 days - " "skipping." % PMS['name'], 0) continue @@ -628,22 +629,22 @@ class PlexAPI(): # declare new PMSs while not queue.empty(): - PMS = queue.get() - self.declarePMS(PMS['uuid'], PMS['name'], - PMS['protocol'], PMS['ip'], PMS['port']) - # dflt: token='', local, owned - updated later - self.updatePMSProperty( - PMS['uuid'], 'accesstoken', PMS['token']) - self.updatePMSProperty( - PMS['uuid'], 'owned', PMS['owned']) - self.updatePMSProperty( - PMS['uuid'], 'local', PMS['local']) - # set in declarePMS, overwrite for https encryption - self.updatePMSProperty( - PMS['uuid'], 'baseURL', PMS['baseURL']) - self.updatePMSProperty( - PMS['uuid'], 'ownername', PMS['ownername']) - queue.task_done() + PMS = queue.get() + self.declarePMS(PMS['uuid'], PMS['name'], + PMS['protocol'], PMS['ip'], PMS['port']) + # dflt: token='', local, owned - updated later + self.updatePMSProperty( + PMS['uuid'], 'accesstoken', PMS['token']) + self.updatePMSProperty( + PMS['uuid'], 'owned', PMS['owned']) + self.updatePMSProperty( + PMS['uuid'], 'local', PMS['local']) + # set in declarePMS, overwrite for https encryption + self.updatePMSProperty( + PMS['uuid'], 'baseURL', PMS['baseURL']) + self.updatePMSProperty( + PMS['uuid'], 'ownername', PMS['ownername']) + queue.task_done() def pokePMS(self, PMS, queue): # Ignore SSL certificates for now @@ -708,14 +709,15 @@ class PlexAPI(): """ # provide credentials - ### optional... when 'realm' is unknown + # optional... when 'realm' is unknown ##passmanager = urllib2.HTTPPasswordMgrWithDefaultRealm() - ##passmanager.add_password(None, address, username, password) # None: default "realm" + # passmanager.add_password(None, address, username, password) # None: + # default "realm" passmanager = urllib2.HTTPPasswordMgr() passmanager.add_password(MyPlexHost, MyPlexURL, username, password) authhandler = urllib2.HTTPBasicAuthHandler(passmanager) urlopener = urllib2.build_opener(authhandler) - + # sign in, get MyPlex response try: response = urlopener.open(request).read() @@ -744,14 +746,16 @@ class PlexAPI(): MyPlexHost = 'plex.tv' MyPlexSignOutPath = '/users/sign_out.xml' MyPlexURL = 'http://' + MyPlexHost + MyPlexSignOutPath - + # create POST request - xargs = { 'X-Plex-Token': authtoken } + xargs = {'X-Plex-Token': authtoken} request = urllib2.Request(MyPlexURL, None, xargs) - request.get_method = lambda: 'POST' # turn into 'POST' - done automatically with data!=None. But we don't have data. - + # turn into 'POST' - done automatically with data!=None. But we don't + # have data. + request.get_method = lambda: 'POST' + response = urllib2.urlopen(request).read() - + dprint(__name__, 1, "====== MyPlex sign out XML ======") dprint(__name__, 1, response) dprint(__name__, 1, "====== MyPlex sign out XML finished ======") @@ -1005,16 +1009,16 @@ class PlexAPI(): if key.startswith('http://') or key.startswith('https://'): # external address - keep path = key else: - if AuthToken=='': + if AuthToken == '': path = key else: xargs = dict() xargs['X-Plex-Token'] = AuthToken - if key.find('?')==-1: + if key.find('?') == -1: path = key + '?' + urlencode(xargs) else: path = key + '&' + urlencode(xargs) - + return path def getTranscodeImagePath(self, key, AuthToken, path, width, height): @@ -1037,19 +1041,20 @@ class PlexAPI(): else: # internal path, add-on path = 'http://127.0.0.1:32400' + path + '/' + key path = path.encode('utf8') - + # This is bogus (note the extra path component) but ATV is stupid when it comes to caching images, it doesn't use querystrings. # Fortunately PMS is lenient... - transcodePath = '/photo/:/transcode/' +str(width)+'x'+str(height)+ '/' + quote_plus(path) - + transcodePath = '/photo/:/transcode/' + \ + str(width) + 'x' + str(height) + '/' + quote_plus(path) + args = dict() args['width'] = width args['height'] = height args['url'] = path - - if not AuthToken=='': + + if not AuthToken == '': args['X-Plex-Token'] = AuthToken - + return transcodePath + '?' + urlencode(args) def getDirectImagePath(self, path, AuthToken): @@ -1062,14 +1067,14 @@ class PlexAPI(): result: final path to image file """ - if not AuthToken=='': + if not AuthToken == '': xargs = dict() xargs['X-Plex-Token'] = AuthToken - if path.find('?')==-1: + if path.find('?') == -1: path = path + '?' + urlencode(xargs) else: path = path + '&' + urlencode(xargs) - + return path def getTranscodeAudioPath(self, path, AuthToken, options, maxAudioBitrate): @@ -1085,19 +1090,19 @@ class PlexAPI(): final path to pull in PMS transcoder """ UDID = options['PlexConnectUDID'] - + transcodePath = '/music/:/transcode/universal/start.mp3?' - + args = dict() args['path'] = path args['session'] = UDID args['protocol'] = 'http' args['maxAudioBitrate'] = maxAudioBitrate - + xargs = clientinfo.ClientInfo().getXArgsDeviceInfo(options) - if not AuthToken=='': + if not AuthToken == '': xargs['X-Plex-Token'] = AuthToken - + return transcodePath + urlencode(args) + '&' + urlencode(xargs) def getDirectAudioPath(self, path, AuthToken): @@ -1110,14 +1115,14 @@ class PlexAPI(): result: final path to audio file """ - if not AuthToken=='': + if not AuthToken == '': xargs = dict() xargs['X-Plex-Token'] = AuthToken - if path.find('?')==-1: + if path.find('?') == -1: path = path + '?' + urlencode(xargs) else: path = path + '&' + urlencode(xargs) - + return path def returnServerList(self, data): @@ -1229,7 +1234,7 @@ class API(): res = self.item[0][self.part].attrib.get('file') except: res = None - if res: + if res is not None: res = unquote(res).decode('utf-8') return res @@ -1946,7 +1951,8 @@ class API(): kodiindex = 0 for stream in mediastreams: index = stream.attrib['id'] - # Since Emby returns all possible tracks together, have to pull only external subtitles. + # Since Emby returns all possible tracks together, have to pull + # only external subtitles. key = stream.attrib.get('key') # IsTextSubtitleStream if true, is available to download from emby. if stream.attrib.get('streamType') == "3" and key: @@ -2031,27 +2037,61 @@ class API(): listItem.addStreamInfo( "video", {'duration': self.getRuntime()[1]}) - def validatePlayurl(self, playurl, typus): + def validatePlayurl(self, path, typus, forceCheck=False): """ - Returns a valid url for Kodi, e.g. with substituted path + Returns a valid path for Kodi, e.g. with '\' substituted to '\\' in + Unicode. Returns None if this is not possible + + path : Unicode + typus : Plex type from PMS xml + forceCheck : Will always try to check validity of path + Will also skip confirmation dialog if path not found """ + if path is None: + return None + types = { + 'movie': 'movie', + 'show': 'tv', + 'season': 'tv', + 'episode': 'tv', + 'artist': 'music', + 'album': 'music', + 'song': 'music', + 'track': 'music', + } + typus = types[typus] if utils.window('remapSMB') == 'true': - playurl = playurl.replace(utils.window('remapSMB%sOrg' % typus), - utils.window('remapSMB%sNew' % typus)) + path = path.replace(utils.window('remapSMB%sOrg' % typus), + utils.window('remapSMB%sNew' % typus)) # There might be backslashes left over: - playurl = playurl.replace('\\', '/') + path = path.replace('\\', '/') elif utils.window('replaceSMB') == 'true': - if playurl.startswith('\\\\'): - playurl = 'smb:' + playurl.replace('\\', '/') - if (utils.window('emby_pathverified') != "true" and - not xbmcvfs.exists(playurl.encode('utf-8'))): - # Validate the path is correct with user intervention - if self.askToValidate(playurl): - utils.window('emby_shouldStop', value="true") - playurl = False - utils.window('emby_pathverified', value='true') - utils.settings('emby_pathverified', value='true') - return playurl + if path.startswith('\\\\'): + path = 'smb:' + path.replace('\\', '/') + if utils.window('emby_pathverified') == 'true' and forceCheck is False: + return path + + check = xbmcvfs.exists(path.encode('utf-8')) + # exists() NEEDS either a '/' or '\\' at the end of a DIR name + if check is False: + check = xbmcvfs.exists((path + '/').encode('utf-8')) + if check is False: + check = xbmcvfs.exists((path + '\\').encode('utf-8')) + if check is False: + if forceCheck is False: + # Validate the path is correct with user intervention + if self.askToValidate(path): + utils.window('emby_shouldStop', value="true") + path = None + utils.window('emby_pathverified', value='true') + utils.settings('emby_pathverified', value='true') + else: + path = None + elif forceCheck is False: + if utils.window('emby_pathverified') != 'true': + utils.window('emby_pathverified', value='true') + utils.settings('emby_pathverified', value='true') + return path def askToValidate(self, url): """ diff --git a/resources/lib/itemtypes.py b/resources/lib/itemtypes.py index 6072e1b0..e3e5dd5e 100644 --- a/resources/lib/itemtypes.py +++ b/resources/lib/itemtypes.py @@ -440,8 +440,8 @@ class Movies(Items): # Something went wrong, trying to use non-direct paths doIndirect = True else: - playurl = API.validatePlayurl(playurl, 'movie') - if playurl is False: + playurl = API.validatePlayurl(playurl, API.getType()) + if playurl is None: return False if "\\" in playurl: # Local path @@ -450,7 +450,6 @@ class Movies(Items): # Network share filename = playurl.rsplit("/", 1)[1] path = playurl.replace(filename, "") - utils.window('emby_pathverified', value="true") if doIndirect: # Set plugin path and media flags using real filename path = "plugin://plugin.video.plexkodiconnect.movies/" @@ -1041,8 +1040,8 @@ class TVShows(Items): # Something went wrong, trying to use non-direct paths doIndirect = True else: - playurl = API.validatePlayurl(playurl, 'tv') - if playurl is False: + playurl = API.validatePlayurl(playurl, API.getType()) + if playurl is None: return False if "\\" in playurl: # Local path @@ -1052,12 +1051,6 @@ class TVShows(Items): # Network path path = "%s/" % playurl toplevelpath = "%s/" % dirname(dirname(path)) - if (utils.window('emby_pathverified') != "true" and - not xbmcvfs.exists(path.encode('utf-8'))): - # Validate the path is correct with user intervention - if self.askToValidate(playurl): - return False - utils.window('emby_pathverified', value="true") if doIndirect: # Set plugin path toplevelpath = "plugin://plugin.video.plexkodiconnect.tvshows/" @@ -1331,14 +1324,9 @@ class TVShows(Items): # Something went wrong, trying to use non-direct paths doIndirect = True else: - playurl = API.validatePlayurl(playurl, 'tv') - if playurl is False: + playurl = API.validatePlayurl(playurl, API.getType()) + if playurl is None: return False - if (utils.window('emby_pathverified') != "true" and - not xbmcvfs.exists(playurl.encode('utf-8'))): - # Validate the path is correct with user intervention - if self.askToValidate(playurl): - return False if "\\" in playurl: # Local path filename = playurl.rsplit("\\", 1)[1] @@ -1346,7 +1334,6 @@ class TVShows(Items): # Network share filename = playurl.rsplit("/", 1)[1] path = playurl.replace(filename, "") - utils.window('emby_pathverified', value="true") if doIndirect: # Set plugin path and media flags using real filename path = "plugin://plugin.video.plexkodiconnect.movies/" @@ -2085,14 +2072,9 @@ class Music(Items): # Something went wrong, trying to use non-direct paths doIndirect = True else: - playurl = API.validatePlayurl(playurl, 'music') - if playurl is False: + playurl = API.validatePlayurl(playurl, API.getType()) + if playurl is None: return False - if (utils.window('emby_pathverified') != "true" and - not xbmcvfs.exists(playurl.encode('utf-8'))): - # Validate the path is correct with user intervention - if self.askToValidate(playurl): - return False if "\\" in playurl: # Local path filename = playurl.rsplit("\\", 1)[1] @@ -2100,7 +2082,6 @@ class Music(Items): # Network share filename = playurl.rsplit("/", 1)[1] path = playurl.replace(filename, "") - utils.window('emby_pathverified', value="true") if doIndirect: # Plex works a bit differently path = "%s%s" % (self.server, item[0][0].attrib.get('key')) diff --git a/resources/lib/playutils.py b/resources/lib/playutils.py index 4bb491cd..3ecf58e3 100644 --- a/resources/lib/playutils.py +++ b/resources/lib/playutils.py @@ -91,34 +91,25 @@ class PlayUtils(): Returns the path/playurl if successful, False otherwise """ - # Requirement: Filesystem, Accessible path - if utils.settings('playFromStream') == "true": + # set to either 'Direct Stream=1' or 'Transcode=2' + if utils.settings('playType') != "0": # User forcing to play via HTTP - self.logMsg("Can't direct play, user enabled play from HTTP.", 1) + self.logMsg("User chose to not direct play", 1) return False if self.h265enabled(): return False - path = self.API.getFilePath() + path = self.API.validatePlayurl(self.API.getFilePath(), + self.API.getType(), + forceCheck=True) if path is None: - self.logMsg('PMS item does not have a filepath', 2) - return False - # Assign network protocol - if path.startswith('\\\\'): - path = path.replace('\\\\', 'smb://') - path = path.replace('\\', '/') - # Plex returns Windows paths as e.g. 'c:\slfkjelf\slfje\file.mkv' - elif '\\' in path: - path = path.replace('\\', '\\\\') - - if xbmcvfs.exists(path): - self.logMsg('Kodi can access file %s - direct playing' % path, 2) - return path - else: self.logMsg('Kodi cannot access file %s - no direct play' - % path, 2) + % path, 1) return False + else: + self.logMsg('Kodi can access file %s - direct playing' % path, 1) + return path def directPlay(self): @@ -198,13 +189,17 @@ class PlayUtils(): return False def isDirectStream(self): - if not self.h265enabled(): + # set to 'Transcode=2' + if utils.settings('playType') == "2": + # User forcing to play via HTTP + self.logMsg("User chose to transcode", 1) + return False + if self.h265enabled(): return False - # Verify the bitrate if not self.isNetworkSufficient(): - self.logMsg( - "The network speed is insufficient to direct stream file.", 1) + self.logMsg("The network speed is insufficient to direct stream " + "file. Transcoding", 1) return False return True @@ -228,11 +223,18 @@ class PlayUtils(): return playurl def isNetworkSufficient(self): - + """ + Returns True if the network is sufficient (set in file settings) + """ + try: + sourceBitrate = int(self.API.getDataFromPartOrMedia('bitrate')) + except: + self.logMsg('Could not detect source bitrate. It is assumed to be' + 'sufficient', 1) + return True settings = self.getBitrate() - - sourceBitrate = int(self.API.getDataFromPartOrMedia('bitrate')) - self.logMsg("The add-on settings bitrate is: %s, the video bitrate required is: %s" % (settings, sourceBitrate), 1) + self.logMsg("The add-on settings bitrate is: %s, the video bitrate" + "required is: %s" % (settings, sourceBitrate), 1) if settings < sourceBitrate: return False return True diff --git a/resources/settings.xml b/resources/settings.xml index a01716ac..c578e3c8 100644 --- a/resources/settings.xml +++ b/resources/settings.xml @@ -37,13 +37,7 @@ - - - - - - - + @@ -63,14 +57,7 @@ - - - - - - - - + @@ -81,6 +68,18 @@ + + + + + + + + + + + + @@ -92,15 +91,16 @@ - - - + + + + + + + + + + + + @@ -123,7 +131,6 @@ -