diff --git a/default.py b/default.py index 467a1f2b..948d82eb 100644 --- a/default.py +++ b/default.py @@ -71,7 +71,8 @@ class Main: 'delete': entrypoint.deleteItem, 'browseplex': entrypoint.BrowsePlexContent, 'ondeck': entrypoint.getOnDeck, - 'chooseServer': entrypoint.chooseServer + 'chooseServer': entrypoint.chooseServer, + 'watchlater': entrypoint.watchlater } if "/extrafanart" in sys.argv[0]: diff --git a/resources/language/English/strings.xml b/resources/language/English/strings.xml index ad91dfff..ba964ae4 100644 --- a/resources/language/English/strings.xml +++ b/resources/language/English/strings.xml @@ -414,6 +414,7 @@ Failed to reset PMS and plex.tv connects. Try to restart Kodi. [COLOR yellow]Log-in to plex.tv[/COLOR] Not yet connected to Plex Server + Watch later diff --git a/resources/language/German/strings.xml b/resources/language/German/strings.xml index 7a2ae659..599716e5 100644 --- a/resources/language/German/strings.xml +++ b/resources/language/German/strings.xml @@ -354,6 +354,7 @@ PMS und plex.tv Verbindungen konnten nicht zurückgesetzt werden. Bitte versuchen Sie, Kodi neu zu starten, um das Problem zu beheben. [COLOR yellow]Bei plex.tv einloggen[/COLOR] Noch nicht mit Plex Server verbunden + Später ansehen Alle Plex Bilder in Kodi zwischenzuspeichern kann sehr lange dauern. Möchten Sie wirklich fortfahren? diff --git a/resources/lib/PlexAPI.py b/resources/lib/PlexAPI.py index 23568753..7b285466 100644 --- a/resources/lib/PlexAPI.py +++ b/resources/lib/PlexAPI.py @@ -916,12 +916,15 @@ class PlexAPI(): username = answer.attrib.get('title', '') token = answer.attrib.get('authenticationToken', '') - userid = answer.attrib.get('id', '') # Write to settings file utils.settings('username', username) - utils.settings('userid', userid) utils.settings('accessToken', token) + utils.settings('userid', + answer.attrib.get('id', '')) + utils.settings('plex_restricteduser', + 'true' if answer.attrib.get('restricted', '0') == '1' + else 'false') # Get final token to the PMS we've chosen url = 'https://plex.tv/api/resources?includeHttps=1' @@ -1476,8 +1479,11 @@ class API(): """ res = self.item.attrib.get('audienceRating') if res is None: - res = self.item.attrib.get('rating', 0.0) - res = float(res) + res = self.item.attrib.get('rating') + try: + res = float(res) + except (ValueError, TypeError): + res = 0.0 return res def getYear(self): @@ -1499,11 +1505,11 @@ class API(): try: runtime = float(item['duration']) - except KeyError: + except (KeyError, ValueError): runtime = 0.0 try: resume = float(item['viewOffset']) - except KeyError: + except (KeyError, ValueError): resume = 0.0 runtime = int(runtime * PlexToKodiTimefactor()) @@ -1837,16 +1843,22 @@ class API(): # Get background artwork URL try: background = item['art'] - background = "%s%s" % (self.server, background) - background = self.addPlexCredentialsToUrl(background) + if background.startswith('http'): + pass + else: + background = "%s%s" % (self.server, background) + background = self.addPlexCredentialsToUrl(background) except KeyError: background = "" allartworks['Backdrop'].append(background) # Get primary "thumb" pictures: try: primary = item['thumb'] - primary = "%s%s" % (self.server, primary) - primary = self.addPlexCredentialsToUrl(primary) + if primary.startswith('http'): + pass + else: + primary = "%s%s" % (self.server, primary) + primary = self.addPlexCredentialsToUrl(primary) except KeyError: primary = "" allartworks['Primary'] = primary @@ -2012,6 +2024,14 @@ class API(): self.logMsg('No extra artwork found') return allartworks + def shouldStream(self): + """ + Returns True if the item's 'optimizedForStreaming' is set, False other- + wise + """ + return (True if self.item[0].attrib.get('optimizedForStreaming') == '1' + else False) + def getTranscodeVideoPath(self, action, quality={}): """ @@ -2033,7 +2053,6 @@ class API(): TODO: mediaIndex """ - xargs = clientinfo.ClientInfo().getXArgsDeviceInfo() # For DirectPlay, path/key of PART is needed if action == "DirectStream": @@ -2151,7 +2170,7 @@ class API(): 'aired': self.getPremiereDate() } - if "episode" in self.getType(): + if self.getType() == "episode": # Only for tv shows key, show, season, episode = self.getEpisodeDetails() metadata['episode'] = episode @@ -2204,6 +2223,7 @@ class API(): 'album': 'music', 'song': 'music', 'track': 'music', + 'clip': 'clip' } typus = types[typus] if utils.window('remapSMB') == 'true': diff --git a/resources/lib/entrypoint.py b/resources/lib/entrypoint.py index c043cbba..840598a5 100644 --- a/resources/lib/entrypoint.py +++ b/resources/lib/entrypoint.py @@ -5,6 +5,7 @@ import json import os import sys +import urllib import xbmc import xbmcaddon @@ -235,6 +236,16 @@ def doPlayback(itemid, dbid): Always to return with a "setResolvedUrl" """ + if dbid == 'plexnode': + # Plex redirect, e.g. watch later. Need to get actual URLs + xml = downloadutils.DownloadUtils().downloadUrl(itemid, + authenticate=False) + if xml in (None, 401): + utils.logMsg(title, "Could not resolve url %s" % itemid, -1) + return xbmcplugin.setResolvedUrl( + int(sys.argv[1]), False, xbmcgui.ListItem()) + return pbutils.PlaybackUtils(xml).play(None, dbid) + if utils.window('plex_authenticated') != "true": utils.logMsg('doPlayback', 'Not yet authenticated for a PMS, abort ' 'starting playback', -1) @@ -249,12 +260,12 @@ def doPlayback(itemid, dbid): return xbmcplugin.setResolvedUrl( int(sys.argv[1]), False, xbmcgui.ListItem()) - item = PlexFunctions.GetPlexMetadata(itemid) - if item is None or item == 401: + xml = PlexFunctions.GetPlexMetadata(itemid) + if xml in (None, 401): return xbmcplugin.setResolvedUrl( int(sys.argv[1]), False, xbmcgui.ListItem()) # Everything OK - return pbutils.PlaybackUtils(item).play(itemid, dbid) + return pbutils.PlaybackUtils(xml).play(itemid, dbid) # utils.logMsg(title, "doPlayback called with itemid=%s, dbid=%s" # % (itemid, dbid), 1) @@ -327,6 +338,9 @@ def doMainListing(): elif path and not xbmc.getCondVisibility("Window.IsActive(VideoLibrary) | Window.IsActive(Pictures) | Window.IsActive(MusicLibrary)"): addDirectoryItem(label, path) + # Plex Watch later + addDirectoryItem(string(39211), + "plugin://plugin.video.plexkodiconnect/?mode=watchlater") # Plex user switch addDirectoryItem(string(39200) + utils.window('plex_username'), "plugin://plugin.video.plexkodiconnect/" @@ -1582,3 +1596,47 @@ def getOnDeck(viewid, mediatype, tagname, limit): break xbmcplugin.endOfDirectory(handle=int(sys.argv[1])) + + +def watchlater(): + """ + Listing for plex.tv Watch Later section (if signed in to plex.tv) + """ + if utils.window('plex_token') == '': + utils.logMsg(title, 'No watch later - not signed in to plex.tv', -1) + return xbmcplugin.endOfDirectory(int(sys.argv[1]), False) + if utils.settings('plex_restricteduser') == 'true': + utils.logMsg(title, 'No watch later - restricted user', -1) + return xbmcplugin.endOfDirectory(int(sys.argv[1]), False) + + xml = downloadutils.DownloadUtils().downloadUrl( + 'https://plex.tv/pms/playlists/queue/all', + authenticate=False, + headerOptions={'X-Plex-Token': utils.window('plex_token')}) + if xml in (None, 401): + utils.logMsg(title, + 'Could not download watch later list from plex.tv', -1) + return xbmcplugin.endOfDirectory(int(sys.argv[1]), False) + + utils.logMsg(title, 'Displaying watch later plex.tv items', 1) + xbmcplugin.setContent(int(sys.argv[1]), 'movies') + url = "plugin://plugin.video.plexkodiconnect.movies/" + params = { + 'mode': "play", + 'dbid': 'plexnode' + } + for item in xml: + API = PlexAPI.API(item) + listitem = API.CreateListItemFromPlexItem() + API.AddStreamInfo(listitem) + pbutils.PlaybackUtils(item).setArtwork(listitem) + params['id'] = item.attrib.get('key') + xbmcplugin.addDirectoryItem( + handle=int(sys.argv[1]), + url="%s?%s" % (url, urllib.urlencode(params)), + listitem=listitem) + + xbmcplugin.endOfDirectory( + handle=int(sys.argv[1]), + cacheToDisc=True if utils.settings('enableTextureCache') == 'true' + else False) diff --git a/resources/lib/playbackutils.py b/resources/lib/playbackutils.py index a076fad4..eb023e45 100644 --- a/resources/lib/playbackutils.py +++ b/resources/lib/playbackutils.py @@ -16,6 +16,7 @@ import playutils as putils import playlist import read_embyserver as embyserver import utils +import downloadutils import PlexAPI import PlexFunctions as PF @@ -60,8 +61,24 @@ class PlaybackUtils(): if not playurl: return xbmcplugin.setResolvedUrl(int(sys.argv[1]), False, listitem) - if dbid in (None, '999999999'): - # Item is not in Kodi database or is a trailer + if dbid in (None, '999999999', 'plexnode'): + # Item is not in Kodi database, is a trailer or plex redirect + # e.g. plex.tv watch later + API.CreateListItemFromPlexItem(listitem) + self.setArtwork(listitem) + if dbid == 'plexnode': + # Need to get yet another xml to get final url + window('emby_%s.playmethod' % playurl, clear=True) + xml = downloadutils.DownloadUtils().downloadUrl( + '{server}%s' % item[0][0][0].attrib.get('key')) + if xml in (None, 401): + log('Could not download %s' + % item[0][0][0].attrib.get('key'), -1) + return xbmcplugin.setResolvedUrl( + int(sys.argv[1]), False, listitem) + playurl = xml[0].attrib.get('key').encode('utf-8') + window('emby_%s.playmethod' % playurl, value='DirectStream') + playmethod = window('emby_%s.playmethod' % playurl) if playmethod == "Transcode": window('emby_%s.playmethod' % playurl, clear=True) @@ -69,8 +86,6 @@ class PlaybackUtils(): listitem, playurl.decode('utf-8')).encode('utf-8') window('emby_%s.playmethod' % playurl, "Transcode") listitem.setPath(playurl) - self.setArtwork(listitem) - API.CreateListItemFromPlexItem(listitem) self.setProperties(playurl, listitem) return xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, listitem) diff --git a/resources/lib/playutils.py b/resources/lib/playutils.py index 3ecf58e3..f5e5dd34 100644 --- a/resources/lib/playutils.py +++ b/resources/lib/playutils.py @@ -90,6 +90,10 @@ class PlayUtils(): """ Returns the path/playurl if successful, False otherwise """ + # True for e.g. plex.tv watch later + if self.API.shouldStream() is True: + self.logMsg("Plex item optimized for direct streaming", 1) + return False # set to either 'Direct Stream=1' or 'Transcode=2' if utils.settings('playType') != "0": diff --git a/resources/settings.xml b/resources/settings.xml index 6d8d18ff..964fca42 100644 --- a/resources/settings.xml +++ b/resources/settings.xml @@ -120,6 +120,9 @@ + + +