PlexKodiConnect/resources/lib/playbackutils.py

365 lines
15 KiB
Python
Raw Permalink 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
from utils import window, settings, tryEncode, tryDecode, language as lang
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
2017-02-19 03:04:30 +11:00
from PlexFunctions import init_plex_playqueue
2017-01-11 07:27:14 +11:00
from PKC_listitem import PKC_ListItem as ListItem, convert_PKC_to_listitem
2017-01-03 00:07:24 +11:00
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
from pickler import Playback_Successful
2017-01-10 05:56:57 +11:00
from plexdb_functions import Get_Plex_DB
import variables as v
2017-05-17 23:42:12 +10:00
import state
2016-02-09 05:40:58 +11:00
2016-09-02 03:20:09 +10:00
###############################################################################
log = logging.getLogger("PLEX."+__name__)
###############################################################################
2015-12-25 07:07:00 +11:00
class PlaybackUtils():
2016-02-20 06:03:06 +11:00
2017-01-10 05:56:57 +11:00
def __init__(self, xml, playqueue):
self.xml = xml
self.playqueue = playqueue
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")
2017-01-10 05:56:57 +11:00
item = self.xml[0]
api = API(item)
2017-01-03 00:07:24 +11:00
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)
2017-01-09 01:03:41 +11:00
api.set_listitem_artwork(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
2017-01-09 01:03:41 +11:00
window('plex_%s.playmethod' % playurl, clear=True)
2016-04-17 21:36:41 +10:00
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'))
2017-01-09 01:03:41 +11:00
window('plex_%s.playmethod' % playurl, value='DirectStream')
2016-04-17 21:36:41 +10:00
2017-01-09 01:03:41 +11:00
playmethod = window('plex_%s.playmethod' % playurl)
2016-02-09 05:40:58 +11:00
if playmethod == "Transcode":
2017-05-02 03:51:10 +10:00
playutils.audioSubsPref(listitem, tryDecode(playurl))
2016-02-03 23:01:13 +11:00
listitem.setPath(playurl)
2017-01-09 01:03:41 +11:00
api.set_playback_win_props(playurl, listitem)
2017-01-03 00:07:24 +11:00
result.listitem = listitem
return result
kodi_type = v.KODITYPE_FROM_PLEXTYPE[api.getType()]
2017-01-03 00:07:24 +11:00
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() == v.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(
lang(29999),
2017-01-03 00:07:24 +11:00
"Play trailers?")
trailers = True if trailers else False
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!
2017-02-19 03:04:30 +11:00
xml = init_plex_playqueue(plex_id,
plex_lib_UUID,
mediatype=api.getType(),
trailers=trailers)
try:
get_playlist_details_from_xml(playqueue, xml=xml)
except KeyError:
return
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:
2017-01-10 05:56:57 +11:00
for i, item in enumerate(xml):
if i == len(xml) - 1:
# Don't add the main movie itself
break
self.add_trailer(item)
introsPlaylist = True
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:
2017-05-17 23:42:12 +10:00
if state.DIRECT_PATHS:
2016-11-09 06:51:34 +11:00
# Cannot add via JSON with full metadata because then we
# Would be using the direct path
log.debug("Adding contextmenu item for direct paths")
2017-01-09 01:03:41 +11:00
if window('plex_%s.playmethod' % playurl) == "Transcode":
2017-05-02 03:51:10 +10:00
playutils.audioSubsPref(listitem, tryDecode(playurl))
2017-01-03 00:07:24 +11:00
api.CreateListItemFromPlexItem(listitem)
2017-01-09 01:03:41 +11:00
api.set_playback_win_props(playurl, listitem)
api.set_listitem_artwork(listitem)
2017-01-11 07:27:14 +11:00
add_listitem_to_Kodi_playlist(
playqueue,
2017-01-11 07:45:15 +11:00
self.currentPosition+1,
2017-01-11 07:27:14 +11:00
convert_PKC_to_listitem(listitem),
2017-05-08 00:58:12 +10:00
file=playurl,
2017-01-11 07:45:15 +11:00
kodi_item={'id': kodi_id, 'type': kodi_type})
2016-11-09 06:51:34 +11:00
else:
2017-01-11 07:27:14 +11:00
# Full metadata$
add_item_to_kodi_playlist(
playqueue,
2017-01-11 07:45:15 +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:
2017-01-10 05:56:57 +11:00
self.add_part(item, api, kodi_id, kodi_type)
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!
2017-01-10 05:56:57 +11:00
del playqueue.items[startPos]
2017-01-03 00:07:24 +11:00
# 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
2017-01-09 01:03:41 +11:00
if (window('plex_%s.playmethod' % playurl) == "Transcode" and
2016-11-09 06:51:34 +11:00
not contextmenu_play):
2017-05-02 03:51:10 +10:00
playutils.audioSubsPref(listitem, tryDecode(playurl))
2016-02-05 06:23:04 +11:00
listitem.setPath(playurl)
2017-01-09 01:03:41 +11:00
api.set_playback_win_props(playurl, listitem)
api.set_listitem_artwork(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
2017-01-10 05:56:57 +11:00
def play_all(self):
2016-02-09 05:40:58 +11:00
"""
2017-01-10 05:56:57 +11:00
Play all items contained in the xml passed in. Called by Plex Companion
2016-02-09 05:40:58 +11:00
"""
2017-01-10 05:56:57 +11:00
log.info("Playbackutils play_all called")
window('plex_playbackProps', value="true")
self.currentPosition = 0
for item in self.xml:
api = API(item)
successful = True
if api.getType() == v.PLEX_TYPE_CLIP:
2017-01-10 05:56:57 +11:00
self.add_trailer(item)
else:
with Get_Plex_DB() as plex_db:
db_item = plex_db.getItem_byId(api.getRatingKey())
if db_item is not None:
successful = add_item_to_kodi_playlist(
self.playqueue,
self.currentPosition,
kodi_id=db_item[0],
kodi_type=db_item[4])
if successful is True:
self.currentPosition += 1
if len(item[0]) > 1:
self.add_part(item,
api,
db_item[0],
db_item[4])
else:
# Item not in Kodi DB
self.add_trailer(item)
if successful is True:
self.playqueue.items[self.currentPosition - 1].ID = item.get(
'%sItemID' % self.playqueue.kind)
2017-01-10 05:56:57 +11:00
def add_trailer(self, item):
2016-02-09 05:40:58 +11:00
# 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
}
2017-01-10 05:56:57 +11:00
introAPI = API(item)
listitem = introAPI.CreateListItemFromPlexItem()
params['id'] = introAPI.getRatingKey()
params['filename'] = introAPI.getKey()
introPlayurl = path + '?' + urlencode(params)
introAPI.set_listitem_artwork(listitem)
# Overwrite the Plex url
listitem.setPath(introPlayurl)
log.info("Adding Plex trailer: %s" % introPlayurl)
add_listitem_to_Kodi_playlist(
self.playqueue,
self.currentPosition,
listitem,
introPlayurl,
xml_video_element=item)
self.currentPosition += 1
def add_part(self, item, api, kodi_id, kodi_type):
"""
Adds an additional part to the playlist
"""
# Only add to the playlist after intros have played
for counter, part in enumerate(item[0]):
# Never add first part
if counter == 0:
continue
# Set listitem and properties for each additional parts
api.setPartNumber(counter)
additionalListItem = xbmcgui.ListItem()
playutils = putils.PlayUtils(item)
additionalPlayurl = playutils.getPlayUrl(
partNumber=counter)
log.debug("Adding additional part: %s, url: %s"
% (counter, additionalPlayurl))
api.CreateListItemFromPlexItem(additionalListItem)
api.set_playback_win_props(additionalPlayurl,
additionalListItem)
api.set_listitem_artwork(additionalListItem)
add_listitem_to_playlist(
2017-01-03 00:07:24 +11:00
self.playqueue,
2016-12-29 05:38:43 +11:00
self.currentPosition,
2017-01-10 05:56:57 +11:00
additionalListItem,
kodi_id=kodi_id,
kodi_type=kodi_type,
plex_id=api.getRatingKey(),
file=additionalPlayurl)
2016-02-09 05:40:58 +11:00
self.currentPosition += 1
2017-01-10 05:56:57 +11:00
api.setPartNumber(0)