PlexKodiConnect/resources/lib/playbackutils.py

404 lines
16 KiB
Python
Raw Normal View History

2015-12-25 07:07:00 +11:00
# -*- coding: utf-8 -*-
2016-09-02 03:20:09 +10:00
###############################################################################
2015-12-25 07:07:00 +11:00
2016-09-02 03:20:09 +10:00
import logging
2016-02-09 05:40:58 +11:00
from urllib import urlencode
2017-01-03 00:07:24 +11:00
from threading import Thread
2015-12-25 07:07:00 +11:00
2017-01-03 00:07:24 +11:00
from xbmc import getCondVisibility, Player
2015-12-25 07:07:00 +11:00
import xbmcgui
import playutils as putils
2016-09-02 03:20:09 +10:00
from utils import window, settings, tryEncode, tryDecode
2016-04-17 21:36:41 +10:00
import downloadutils
2016-01-02 00:40:40 +11:00
2017-01-03 00:07:24 +11:00
from PlexAPI import API
from PlexFunctions import GetPlexPlaylist, KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE, \
KODITYPE_FROM_PLEXTYPE, PLEX_TYPE_MOVIE
2017-01-03 00:07:24 +11:00
from PKC_listitem import PKC_ListItem as ListItem
from playlist_func import add_item_to_kodi_playlist, \
get_playlist_details_from_xml, add_listitem_to_Kodi_playlist, \
add_listitem_to_playlist, remove_from_Kodi_playlist
2017-01-03 01:41:38 +11:00
from playqueue import lock, Playqueue
2017-01-03 00:07:24 +11:00
from pickler import Playback_Successful
2016-02-09 05:40:58 +11:00
2016-09-02 03:20:09 +10:00
###############################################################################
log = logging.getLogger("PLEX."+__name__)
2016-09-02 03:44:15 +10:00
addonName = "PlexKodiConnect"
2016-09-02 03:20:09 +10:00
###############################################################################
2015-12-25 07:07:00 +11:00
class PlaybackUtils():
2016-02-20 06:03:06 +11:00
2017-01-03 01:41:38 +11:00
def __init__(self, item, callback=None, playlist_type=None):
self.item = item
self.api = API(item)
playlist_type = playlist_type if playlist_type else \
KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE[self.api.getType()]
2017-01-03 00:17:28 +11:00
if callback:
self.mgr = callback
self.playqueue = self.mgr.playqueue.get_playqueue_from_type(
2017-01-03 01:41:38 +11:00
playlist_type)
else:
self.playqueue = Playqueue().get_playqueue_from_type(playlist_type)
2017-01-03 00:07:24 +11:00
def play(self, plex_id, kodi_id=None, plex_lib_UUID=None):
"""
plex_lib_UUID: xml attribute 'librarySectionUUID', needed for posting
to the PMS
"""
log.info("Playbackutils called")
item = self.item
2017-01-03 00:07:24 +11:00
api = self.api
playqueue = self.playqueue
xml = None
result = Playback_Successful()
listitem = ListItem()
playutils = putils.PlayUtils(item)
playurl = playutils.getPlayUrl()
if not playurl:
2017-01-03 00:07:24 +11:00
log.error('No playurl found, aborting')
return
2017-01-03 00:07:24 +11:00
if kodi_id in (None, 'plextrailer', 'plexnode'):
# Item is not in Kodi database, is a trailer/clip or plex redirect
2016-04-17 21:36:41 +10:00
# e.g. plex.tv watch later
2017-01-03 00:07:24 +11:00
api.CreateListItemFromPlexItem(listitem)
2016-04-17 21:36:41 +10:00
self.setArtwork(listitem)
2017-01-03 00:07:24 +11:00
if kodi_id == 'plexnode':
2016-04-17 21:36:41 +10:00
# Need to get yet another xml to get final url
window('emby_%s.playmethod' % playurl, clear=True)
xml = downloadutils.DownloadUtils().downloadUrl(
2017-01-03 00:07:24 +11:00
'{server}%s' % item[0][0].attrib.get('key'))
try:
xml[0].attrib
except (TypeError, AttributeError):
2016-09-02 03:20:09 +10:00
log.error('Could not download %s'
2017-01-03 00:07:24 +11:00
% item[0][0].attrib.get('key'))
return
2016-09-02 03:20:09 +10:00
playurl = tryEncode(xml[0].attrib.get('key'))
2016-04-17 21:36:41 +10:00
window('emby_%s.playmethod' % playurl, value='DirectStream')
2016-02-20 06:03:06 +11:00
playmethod = window('emby_%s.playmethod' % playurl)
2016-02-09 05:40:58 +11:00
if playmethod == "Transcode":
2016-02-20 06:03:06 +11:00
window('emby_%s.playmethod' % playurl, clear=True)
2016-09-02 03:20:09 +10:00
playurl = tryEncode(playutils.audioSubsPref(
listitem, tryDecode(playurl)))
2016-02-20 06:03:06 +11:00
window('emby_%s.playmethod' % playurl, "Transcode")
2016-02-03 23:01:13 +11:00
listitem.setPath(playurl)
self.setProperties(playurl, listitem)
2017-01-03 00:07:24 +11:00
result.listitem = listitem
return result
kodi_type = KODITYPE_FROM_PLEXTYPE[api.getType()]
kodi_id = int(kodi_id)
2017-01-03 00:07:24 +11:00
# ORGANIZE CURRENT PLAYLIST ################
contextmenu_play = window('plex_contextplay') == 'true'
window('plex_contextplay', clear=True)
2017-01-03 00:07:24 +11:00
homeScreen = getCondVisibility('Window.IsActive(home)')
sizePlaylist = len(playqueue.items)
if contextmenu_play:
# Need to start with the items we're inserting here
startPos = sizePlaylist
else:
# Can return -1
2017-01-03 00:07:24 +11:00
startPos = max(playqueue.kodi_pl.getposition(), 0)
2016-02-09 05:40:58 +11:00
self.currentPosition = startPos
2016-05-31 16:06:42 +10:00
propertiesPlayback = window('plex_playbackProps') == "true"
introsPlaylist = False
dummyPlaylist = False
log.info("Playing from contextmenu: %s" % contextmenu_play)
2016-09-02 03:20:09 +10:00
log.info("Playlist start position: %s" % startPos)
log.info("Playlist plugin position: %s" % self.currentPosition)
log.info("Playlist size: %s" % sizePlaylist)
2017-01-03 00:07:24 +11:00
# RESUME POINT ################
seektime, runtime = api.getRuntime()
2016-12-29 05:38:43 +11:00
if window('plex_customplaylist.seektime'):
# Already got seektime, e.g. from playqueue & Plex companion
seektime = int(window('plex_customplaylist.seektime'))
# We need to ensure we add the intro and additional parts only once.
# Otherwise we get a loop.
if not propertiesPlayback:
2016-05-31 16:06:42 +10:00
window('plex_playbackProps', value="true")
2016-09-02 03:20:09 +10:00
log.info("Setting up properties in playlist.")
2017-01-03 00:07:24 +11:00
# Where will the player need to start?
# Do we need to get trailers?
2016-12-30 01:41:14 +11:00
trailers = False
if (api.getType() == PLEX_TYPE_MOVIE and
not seektime and
sizePlaylist < 2 and
settings('enableCinema') == "true"):
2016-12-30 01:41:14 +11:00
if settings('askCinema') == "true":
2017-01-03 00:07:24 +11:00
trailers = xbmcgui.Dialog().yesno(
addonName,
"Play trailers?")
2016-12-30 01:41:14 +11:00
else:
trailers = True
2017-01-03 00:07:24 +11:00
# Post to the PMS. REUSE THE PLAYQUEUE!
xml = GetPlexPlaylist(
plex_id,
plex_lib_UUID,
mediatype=api.getType(),
2016-12-30 01:41:14 +11:00
trailers=trailers)
2017-01-03 00:07:24 +11:00
get_playlist_details_from_xml(playqueue, xml=xml)
2016-12-30 01:41:14 +11:00
if (not homeScreen and not seektime and sizePlaylist < 2 and
2016-11-09 06:51:34 +11:00
window('plex_customplaylist') != "true" and
not contextmenu_play):
2017-01-03 00:07:24 +11:00
# Need to add a dummy file because the first item will fail
2016-09-02 03:20:09 +10:00
log.debug("Adding dummy file to playlist.")
dummyPlaylist = True
2017-01-03 00:07:24 +11:00
add_listitem_to_Kodi_playlist(
playqueue,
startPos,
xbmcgui.ListItem(),
2016-12-29 05:38:43 +11:00
playurl,
2017-01-03 00:07:24 +11:00
xml[0])
2016-11-09 06:51:34 +11:00
# Remove the original item from playlist
2017-01-03 00:07:24 +11:00
remove_from_Kodi_playlist(
playqueue,
startPos+1)
2016-12-29 05:38:43 +11:00
# Readd the original item to playlist - via jsonrpc so we have
# full metadata
2017-01-03 00:07:24 +11:00
add_item_to_kodi_playlist(
playqueue,
2016-02-09 05:40:58 +11:00
self.currentPosition+1,
2017-01-03 00:07:24 +11:00
kodi_id=kodi_id,
kodi_type=kodi_type,
file=playurl)
2016-02-09 05:40:58 +11:00
self.currentPosition += 1
2017-01-03 00:07:24 +11:00
# -- ADD TRAILERS ################
if trailers:
2016-02-09 05:40:58 +11:00
introsPlaylist = self.AddTrailers(xml)
2017-01-03 00:07:24 +11:00
# -- ADD MAIN ITEM ONLY FOR HOMESCREEN ##############
2016-11-09 06:51:34 +11:00
if homeScreen and not seektime and not sizePlaylist:
# Extend our current playlist with the actual item to play
# only if there's no playlist first
2016-09-02 03:20:09 +10:00
log.info("Adding main item to playlist.")
2017-01-03 00:07:24 +11:00
add_item_to_kodi_playlist(
playqueue,
self.currentPosition,
kodi_id,
kodi_type)
2016-11-09 06:51:34 +11:00
elif contextmenu_play:
if window('useDirectPaths') == 'true':
# Cannot add via JSON with full metadata because then we
# Would be using the direct path
log.debug("Adding contextmenu item for direct paths")
if window('emby_%s.playmethod' % playurl) == "Transcode":
window('emby_%s.playmethod' % playurl,
clear=True)
playurl = tryEncode(playutils.audioSubsPref(
listitem, tryDecode(playurl)))
window('emby_%s.playmethod' % playurl,
value="Transcode")
2017-01-03 00:07:24 +11:00
api.CreateListItemFromPlexItem(listitem)
2016-11-09 06:51:34 +11:00
self.setProperties(playurl, listitem)
self.setArtwork(listitem)
kodiPl.add(playurl, listitem, index=self.currentPosition+1)
else:
# Full metadata
2017-01-03 00:07:24 +11:00
self.pl.insertintoPlaylist(
2016-11-09 06:51:34 +11:00
self.currentPosition+1,
2017-01-03 00:07:24 +11:00
kodi_id,
kodi_type)
2016-11-09 06:51:34 +11:00
self.currentPosition += 1
if seektime:
window('plex_customplaylist.seektime', value=str(seektime))
# Ensure that additional parts are played after the main item
2016-02-09 05:40:58 +11:00
self.currentPosition += 1
2017-01-03 00:07:24 +11:00
# -- CHECK FOR ADDITIONAL PARTS ################
if len(item[0]) > 1:
# Only add to the playlist after intros have played
2017-01-03 00:07:24 +11:00
for counter, part in enumerate(item[0]):
2016-04-12 01:50:56 +10:00
# Never add first part
if counter == 0:
continue
2016-02-09 05:40:58 +11:00
# Set listitem and properties for each additional parts
2017-01-03 00:07:24 +11:00
api.setPartNumber(counter)
additionalListItem = xbmcgui.ListItem()
2016-02-09 05:40:58 +11:00
additionalPlayurl = playutils.getPlayUrl(
partNumber=counter)
2016-11-09 07:00:13 +11:00
log.debug("Adding additional part: %s, url: %s"
% (counter, additionalPlayurl))
2017-01-03 00:07:24 +11:00
api.CreateListItemFromPlexItem(additionalListItem)
2016-02-09 05:40:58 +11:00
self.setProperties(additionalPlayurl, additionalListItem)
self.setArtwork(additionalListItem)
2017-01-03 00:07:24 +11:00
add_listitem_to_playlist(
playqueue,
self.currentPosition,
additionalListItem,
kodi_id=kodi_id,
kodi_type=kodi_type,
plex_id=plex_id,
file=additionalPlayurl)
2016-02-09 05:40:58 +11:00
self.currentPosition += 1
2017-01-03 00:07:24 +11:00
api.setPartNumber(0)
if dummyPlaylist:
# Added a dummy file to the playlist,
# because the first item is going to fail automatically.
2016-09-02 03:20:09 +10:00
log.info("Processed as a playlist. First item is skipped.")
2017-01-03 00:07:24 +11:00
# Delete the item that's gonna fail!
with lock:
del playqueue.items[startPos]
# Don't attach listitem
return result
# We just skipped adding properties. Reset flag for next time.
elif propertiesPlayback:
2016-09-02 03:20:09 +10:00
log.debug("Resetting properties playback flag.")
2016-05-31 16:06:42 +10:00
window('plex_playbackProps', clear=True)
2017-01-03 00:07:24 +11:00
# SETUP MAIN ITEM ##########
# For transcoding only, ask for audio/subs pref
2016-11-09 06:51:34 +11:00
if (window('emby_%s.playmethod' % playurl) == "Transcode" and
not contextmenu_play):
window('emby_%s.playmethod' % playurl, clear=True)
2016-09-02 03:20:09 +10:00
playurl = tryEncode(playutils.audioSubsPref(
listitem, tryDecode(playurl)))
2016-02-17 19:13:37 +11:00
window('emby_%s.playmethod' % playurl, value="Transcode")
2016-02-05 06:23:04 +11:00
listitem.setPath(playurl)
self.setProperties(playurl, listitem)
2016-02-03 23:01:13 +11:00
2017-01-03 00:07:24 +11:00
# PLAYBACK ################
if (homeScreen and seektime and window('plex_customplaylist') != "true"
and not contextmenu_play):
2017-01-03 00:07:24 +11:00
log.info("Play as a widget item")
api.CreateListItemFromPlexItem(listitem)
result.listitem = listitem
return result
2016-05-31 16:06:42 +10:00
elif ((introsPlaylist and window('plex_customplaylist') == "true") or
(homeScreen and not sizePlaylist) or
contextmenu_play):
# Playlist was created just now, play it.
# Contextmenu plays always need this
2017-01-08 22:56:40 +11:00
log.info("Play playlist from starting position %s" % startPos)
2017-01-03 00:07:24 +11:00
# Need a separate thread because Player won't return in time
thread = Thread(target=Player().play,
args=(playqueue.kodi_pl, None, False, startPos))
thread.setDaemon(True)
thread.start()
# Don't attach listitem
return result
else:
2017-01-03 00:07:24 +11:00
log.info("Play as a regular item")
result.listitem = listitem
return result
2016-01-30 06:07:21 +11:00
2016-02-09 05:40:58 +11:00
def AddTrailers(self, xml):
"""
Adds trailers to a movie, if applicable. Returns True if trailers were
added
"""
# Failure when getting trailers, e.g. when no plex pass
if xml.attrib.get('size') == '1':
return False
# Playurl needs to point back so we can get metadata!
2017-01-03 00:07:24 +11:00
path = "plugin://plugin.video.plexkodiconnect/movies/"
2016-02-09 05:40:58 +11:00
params = {
'mode': "play",
'dbid': 'plextrailer'
2016-02-09 05:40:58 +11:00
}
for counter, intro in enumerate(xml):
# Don't process the last item - it's the original movie
if counter == len(xml)-1:
break
2017-01-03 00:07:24 +11:00
introAPI = API(intro)
listitem = introAPI.CreateListItemFromPlexItem()
2016-02-09 05:40:58 +11:00
params['id'] = introAPI.getRatingKey()
params['filename'] = introAPI.getKey()
introPlayurl = path + '?' + urlencode(params)
2017-01-03 00:07:24 +11:00
self.setArtwork(listitem, introAPI)
# Overwrite the Plex url
listitem.setPath(introPlayurl)
2016-09-02 03:20:09 +10:00
log.info("Adding Intro: %s" % introPlayurl)
2017-01-03 00:07:24 +11:00
add_listitem_to_Kodi_playlist(
self.playqueue,
2016-12-29 05:38:43 +11:00
self.currentPosition,
2017-01-03 00:07:24 +11:00
listitem,
introPlayurl,
intro)
2016-02-09 05:40:58 +11:00
self.currentPosition += 1
return True
2015-12-25 07:07:00 +11:00
def setProperties(self, playurl, listitem):
# Set all properties necessary for plugin path playback
2017-01-03 00:07:24 +11:00
itemid = self.api.getRatingKey()
itemtype = self.api.getType()
userdata = self.api.getUserData()
2015-12-25 07:07:00 +11:00
embyitem = "emby_%s" % playurl
window('%s.runtime' % embyitem, value=str(userdata['Runtime']))
2016-02-17 19:13:37 +11:00
window('%s.type' % embyitem, value=itemtype)
window('%s.itemid' % embyitem, value=itemid)
window('%s.playcount' % embyitem, value=str(userdata['PlayCount']))
2015-12-25 07:07:00 +11:00
2016-02-09 05:40:58 +11:00
if itemtype == "episode":
window('%s.refreshid' % embyitem,
2017-01-03 00:07:24 +11:00
value=self.api.getParentRatingKey())
2015-12-25 07:07:00 +11:00
else:
2016-02-17 19:13:37 +11:00
window('%s.refreshid' % embyitem, value=itemid)
2015-12-25 07:07:00 +11:00
# Append external subtitles to stream
playmethod = window('%s.playmethod' % embyitem)
if playmethod in ("DirectStream", "DirectPlay"):
2017-01-03 00:07:24 +11:00
subtitles = self.api.externalSubs(playurl)
listitem.setSubtitles(subtitles)
2015-12-25 07:07:00 +11:00
self.setArtwork(listitem)
2017-01-03 00:07:24 +11:00
def setArtwork(self, listItem, api=None):
if api is None:
api = self.api
allartwork = api.getAllArtwork(parentInfo=True)
2015-12-25 07:07:00 +11:00
arttypes = {
'poster': "Primary",
'tvshow.poster': "Thumb",
2015-12-25 07:07:00 +11:00
'clearart': "Art",
'tvshow.clearart': "Art",
2016-02-09 05:40:58 +11:00
'clearart': "Primary",
'tvshow.clearart': "Primary",
2015-12-25 07:07:00 +11:00
'clearlogo': "Logo",
'tvshow.clearlogo': "Logo",
'discart': "Disc",
'fanart_image': "Backdrop",
'landscape': "Backdrop",
"banner": "Banner"
2015-12-25 07:07:00 +11:00
}
for arttype in arttypes:
art = arttypes[arttype]
if art == "Backdrop":
try:
# Backdrop is a list, grab the first backdrop
2015-12-25 07:07:00 +11:00
self.setArtProp(listItem, arttype, allartwork[art][0])
except:
pass
2015-12-25 07:07:00 +11:00
else:
self.setArtProp(listItem, arttype, allartwork[art])
def setArtProp(self, listItem, arttype, path):
if arttype in (
'thumb', 'fanart_image', 'small_poster', 'tiny_poster',
'medium_landscape', 'medium_poster', 'small_fanartimage',
'medium_fanartimage', 'fanart_noindicators'):
listItem.setProperty(arttype, path)
else:
listItem.setArt({arttype: path})