Groundworks for plex.tv Watch Later
This commit is contained in:
parent
7de61f6f47
commit
93cd265e8f
8 changed files with 123 additions and 20 deletions
|
@ -71,7 +71,8 @@ class Main:
|
||||||
'delete': entrypoint.deleteItem,
|
'delete': entrypoint.deleteItem,
|
||||||
'browseplex': entrypoint.BrowsePlexContent,
|
'browseplex': entrypoint.BrowsePlexContent,
|
||||||
'ondeck': entrypoint.getOnDeck,
|
'ondeck': entrypoint.getOnDeck,
|
||||||
'chooseServer': entrypoint.chooseServer
|
'chooseServer': entrypoint.chooseServer,
|
||||||
|
'watchlater': entrypoint.watchlater
|
||||||
}
|
}
|
||||||
|
|
||||||
if "/extrafanart" in sys.argv[0]:
|
if "/extrafanart" in sys.argv[0]:
|
||||||
|
|
|
@ -414,6 +414,7 @@
|
||||||
<string id="39208">Failed to reset PMS and plex.tv connects. Try to restart Kodi.</string>
|
<string id="39208">Failed to reset PMS and plex.tv connects. Try to restart Kodi.</string>
|
||||||
<string id="39209">[COLOR yellow]Log-in to plex.tv[/COLOR]</string>
|
<string id="39209">[COLOR yellow]Log-in to plex.tv[/COLOR]</string>
|
||||||
<string id="39210">Not yet connected to Plex Server</string>
|
<string id="39210">Not yet connected to Plex Server</string>
|
||||||
|
<string id="39211">Watch later</string>
|
||||||
|
|
||||||
|
|
||||||
<!-- Plex Artwork.py -->
|
<!-- Plex Artwork.py -->
|
||||||
|
|
|
@ -354,6 +354,7 @@
|
||||||
<string id="39208">PMS und plex.tv Verbindungen konnten nicht zurückgesetzt werden. Bitte versuchen Sie, Kodi neu zu starten, um das Problem zu beheben.</string>
|
<string id="39208">PMS und plex.tv Verbindungen konnten nicht zurückgesetzt werden. Bitte versuchen Sie, Kodi neu zu starten, um das Problem zu beheben.</string>
|
||||||
<string id="39209">[COLOR yellow]Bei plex.tv einloggen[/COLOR]</string>
|
<string id="39209">[COLOR yellow]Bei plex.tv einloggen[/COLOR]</string>
|
||||||
<string id="39210">Noch nicht mit Plex Server verbunden</string>
|
<string id="39210">Noch nicht mit Plex Server verbunden</string>
|
||||||
|
<string id="39211">Später ansehen</string>
|
||||||
|
|
||||||
<!-- Plex Artwork.py -->
|
<!-- Plex Artwork.py -->
|
||||||
<string id="39250">Alle Plex Bilder in Kodi zwischenzuspeichern kann sehr lange dauern. Möchten Sie wirklich fortfahren?</string>
|
<string id="39250">Alle Plex Bilder in Kodi zwischenzuspeichern kann sehr lange dauern. Möchten Sie wirklich fortfahren?</string>
|
||||||
|
|
|
@ -916,12 +916,15 @@ class PlexAPI():
|
||||||
|
|
||||||
username = answer.attrib.get('title', '')
|
username = answer.attrib.get('title', '')
|
||||||
token = answer.attrib.get('authenticationToken', '')
|
token = answer.attrib.get('authenticationToken', '')
|
||||||
userid = answer.attrib.get('id', '')
|
|
||||||
|
|
||||||
# Write to settings file
|
# Write to settings file
|
||||||
utils.settings('username', username)
|
utils.settings('username', username)
|
||||||
utils.settings('userid', userid)
|
|
||||||
utils.settings('accessToken', token)
|
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
|
# Get final token to the PMS we've chosen
|
||||||
url = 'https://plex.tv/api/resources?includeHttps=1'
|
url = 'https://plex.tv/api/resources?includeHttps=1'
|
||||||
|
@ -1476,8 +1479,11 @@ class API():
|
||||||
"""
|
"""
|
||||||
res = self.item.attrib.get('audienceRating')
|
res = self.item.attrib.get('audienceRating')
|
||||||
if res is None:
|
if res is None:
|
||||||
res = self.item.attrib.get('rating', 0.0)
|
res = self.item.attrib.get('rating')
|
||||||
res = float(res)
|
try:
|
||||||
|
res = float(res)
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
res = 0.0
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def getYear(self):
|
def getYear(self):
|
||||||
|
@ -1499,11 +1505,11 @@ class API():
|
||||||
|
|
||||||
try:
|
try:
|
||||||
runtime = float(item['duration'])
|
runtime = float(item['duration'])
|
||||||
except KeyError:
|
except (KeyError, ValueError):
|
||||||
runtime = 0.0
|
runtime = 0.0
|
||||||
try:
|
try:
|
||||||
resume = float(item['viewOffset'])
|
resume = float(item['viewOffset'])
|
||||||
except KeyError:
|
except (KeyError, ValueError):
|
||||||
resume = 0.0
|
resume = 0.0
|
||||||
|
|
||||||
runtime = int(runtime * PlexToKodiTimefactor())
|
runtime = int(runtime * PlexToKodiTimefactor())
|
||||||
|
@ -1837,16 +1843,22 @@ class API():
|
||||||
# Get background artwork URL
|
# Get background artwork URL
|
||||||
try:
|
try:
|
||||||
background = item['art']
|
background = item['art']
|
||||||
background = "%s%s" % (self.server, background)
|
if background.startswith('http'):
|
||||||
background = self.addPlexCredentialsToUrl(background)
|
pass
|
||||||
|
else:
|
||||||
|
background = "%s%s" % (self.server, background)
|
||||||
|
background = self.addPlexCredentialsToUrl(background)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
background = ""
|
background = ""
|
||||||
allartworks['Backdrop'].append(background)
|
allartworks['Backdrop'].append(background)
|
||||||
# Get primary "thumb" pictures:
|
# Get primary "thumb" pictures:
|
||||||
try:
|
try:
|
||||||
primary = item['thumb']
|
primary = item['thumb']
|
||||||
primary = "%s%s" % (self.server, primary)
|
if primary.startswith('http'):
|
||||||
primary = self.addPlexCredentialsToUrl(primary)
|
pass
|
||||||
|
else:
|
||||||
|
primary = "%s%s" % (self.server, primary)
|
||||||
|
primary = self.addPlexCredentialsToUrl(primary)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
primary = ""
|
primary = ""
|
||||||
allartworks['Primary'] = primary
|
allartworks['Primary'] = primary
|
||||||
|
@ -2012,6 +2024,14 @@ class API():
|
||||||
self.logMsg('No extra artwork found')
|
self.logMsg('No extra artwork found')
|
||||||
return allartworks
|
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={}):
|
def getTranscodeVideoPath(self, action, quality={}):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@ -2033,7 +2053,6 @@ class API():
|
||||||
|
|
||||||
TODO: mediaIndex
|
TODO: mediaIndex
|
||||||
"""
|
"""
|
||||||
|
|
||||||
xargs = clientinfo.ClientInfo().getXArgsDeviceInfo()
|
xargs = clientinfo.ClientInfo().getXArgsDeviceInfo()
|
||||||
# For DirectPlay, path/key of PART is needed
|
# For DirectPlay, path/key of PART is needed
|
||||||
if action == "DirectStream":
|
if action == "DirectStream":
|
||||||
|
@ -2151,7 +2170,7 @@ class API():
|
||||||
'aired': self.getPremiereDate()
|
'aired': self.getPremiereDate()
|
||||||
}
|
}
|
||||||
|
|
||||||
if "episode" in self.getType():
|
if self.getType() == "episode":
|
||||||
# Only for tv shows
|
# Only for tv shows
|
||||||
key, show, season, episode = self.getEpisodeDetails()
|
key, show, season, episode = self.getEpisodeDetails()
|
||||||
metadata['episode'] = episode
|
metadata['episode'] = episode
|
||||||
|
@ -2204,6 +2223,7 @@ class API():
|
||||||
'album': 'music',
|
'album': 'music',
|
||||||
'song': 'music',
|
'song': 'music',
|
||||||
'track': 'music',
|
'track': 'music',
|
||||||
|
'clip': 'clip'
|
||||||
}
|
}
|
||||||
typus = types[typus]
|
typus = types[typus]
|
||||||
if utils.window('remapSMB') == 'true':
|
if utils.window('remapSMB') == 'true':
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
import urllib
|
||||||
|
|
||||||
import xbmc
|
import xbmc
|
||||||
import xbmcaddon
|
import xbmcaddon
|
||||||
|
@ -235,6 +236,16 @@ def doPlayback(itemid, dbid):
|
||||||
|
|
||||||
Always to return with a "setResolvedUrl"
|
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":
|
if utils.window('plex_authenticated') != "true":
|
||||||
utils.logMsg('doPlayback', 'Not yet authenticated for a PMS, abort '
|
utils.logMsg('doPlayback', 'Not yet authenticated for a PMS, abort '
|
||||||
'starting playback', -1)
|
'starting playback', -1)
|
||||||
|
@ -249,12 +260,12 @@ def doPlayback(itemid, dbid):
|
||||||
return xbmcplugin.setResolvedUrl(
|
return xbmcplugin.setResolvedUrl(
|
||||||
int(sys.argv[1]), False, xbmcgui.ListItem())
|
int(sys.argv[1]), False, xbmcgui.ListItem())
|
||||||
|
|
||||||
item = PlexFunctions.GetPlexMetadata(itemid)
|
xml = PlexFunctions.GetPlexMetadata(itemid)
|
||||||
if item is None or item == 401:
|
if xml in (None, 401):
|
||||||
return xbmcplugin.setResolvedUrl(
|
return xbmcplugin.setResolvedUrl(
|
||||||
int(sys.argv[1]), False, xbmcgui.ListItem())
|
int(sys.argv[1]), False, xbmcgui.ListItem())
|
||||||
# Everything OK
|
# 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"
|
# utils.logMsg(title, "doPlayback called with itemid=%s, dbid=%s"
|
||||||
# % (itemid, dbid), 1)
|
# % (itemid, dbid), 1)
|
||||||
|
@ -327,6 +338,9 @@ def doMainListing():
|
||||||
elif path and not xbmc.getCondVisibility("Window.IsActive(VideoLibrary) | Window.IsActive(Pictures) | Window.IsActive(MusicLibrary)"):
|
elif path and not xbmc.getCondVisibility("Window.IsActive(VideoLibrary) | Window.IsActive(Pictures) | Window.IsActive(MusicLibrary)"):
|
||||||
addDirectoryItem(label, path)
|
addDirectoryItem(label, path)
|
||||||
|
|
||||||
|
# Plex Watch later
|
||||||
|
addDirectoryItem(string(39211),
|
||||||
|
"plugin://plugin.video.plexkodiconnect/?mode=watchlater")
|
||||||
# Plex user switch
|
# Plex user switch
|
||||||
addDirectoryItem(string(39200) + utils.window('plex_username'),
|
addDirectoryItem(string(39200) + utils.window('plex_username'),
|
||||||
"plugin://plugin.video.plexkodiconnect/"
|
"plugin://plugin.video.plexkodiconnect/"
|
||||||
|
@ -1582,3 +1596,47 @@ def getOnDeck(viewid, mediatype, tagname, limit):
|
||||||
break
|
break
|
||||||
|
|
||||||
xbmcplugin.endOfDirectory(handle=int(sys.argv[1]))
|
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)
|
||||||
|
|
|
@ -16,6 +16,7 @@ import playutils as putils
|
||||||
import playlist
|
import playlist
|
||||||
import read_embyserver as embyserver
|
import read_embyserver as embyserver
|
||||||
import utils
|
import utils
|
||||||
|
import downloadutils
|
||||||
|
|
||||||
import PlexAPI
|
import PlexAPI
|
||||||
import PlexFunctions as PF
|
import PlexFunctions as PF
|
||||||
|
@ -60,8 +61,24 @@ class PlaybackUtils():
|
||||||
if not playurl:
|
if not playurl:
|
||||||
return xbmcplugin.setResolvedUrl(int(sys.argv[1]), False, listitem)
|
return xbmcplugin.setResolvedUrl(int(sys.argv[1]), False, listitem)
|
||||||
|
|
||||||
if dbid in (None, '999999999'):
|
if dbid in (None, '999999999', 'plexnode'):
|
||||||
# Item is not in Kodi database or is a trailer
|
# 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)
|
playmethod = window('emby_%s.playmethod' % playurl)
|
||||||
if playmethod == "Transcode":
|
if playmethod == "Transcode":
|
||||||
window('emby_%s.playmethod' % playurl, clear=True)
|
window('emby_%s.playmethod' % playurl, clear=True)
|
||||||
|
@ -69,8 +86,6 @@ class PlaybackUtils():
|
||||||
listitem, playurl.decode('utf-8')).encode('utf-8')
|
listitem, playurl.decode('utf-8')).encode('utf-8')
|
||||||
window('emby_%s.playmethod' % playurl, "Transcode")
|
window('emby_%s.playmethod' % playurl, "Transcode")
|
||||||
listitem.setPath(playurl)
|
listitem.setPath(playurl)
|
||||||
self.setArtwork(listitem)
|
|
||||||
API.CreateListItemFromPlexItem(listitem)
|
|
||||||
self.setProperties(playurl, listitem)
|
self.setProperties(playurl, listitem)
|
||||||
return xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, listitem)
|
return xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, listitem)
|
||||||
|
|
||||||
|
|
|
@ -90,6 +90,10 @@ class PlayUtils():
|
||||||
"""
|
"""
|
||||||
Returns the path/playurl if successful, False otherwise
|
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'
|
# set to either 'Direct Stream=1' or 'Transcode=2'
|
||||||
if utils.settings('playType') != "0":
|
if utils.settings('playType') != "0":
|
||||||
|
|
|
@ -120,6 +120,9 @@
|
||||||
<setting id="deviceNameOpt" label="30504" type="bool" default="false" />
|
<setting id="deviceNameOpt" label="30504" type="bool" default="false" />
|
||||||
<setting id="deviceName" label="30016" type="text" visible="eq(-1,true)" default="Kodi" />
|
<setting id="deviceName" label="30016" type="text" visible="eq(-1,true)" default="Kodi" />
|
||||||
<setting id="companionPort" label="39005" type="number" default="3005" option="int" visible="eq(-3,true)"/>
|
<setting id="companionPort" label="39005" type="number" default="3005" option="int" visible="eq(-3,true)"/>
|
||||||
|
|
||||||
|
<setting id="plex_restricteduser" type="bool" default="false" visible="false"/>
|
||||||
|
|
||||||
</category>
|
</category>
|
||||||
|
|
||||||
<category label="39045"><!-- Appearance Tweaks -->
|
<category label="39045"><!-- Appearance Tweaks -->
|
||||||
|
|
Loading…
Reference in a new issue