PlexKodiConnect/resources/lib/playstrm.py
2019-04-27 13:23:01 +02:00

278 lines
11 KiB
Python

# -*- coding: utf-8 -*-
from __future__ import absolute_import, division, unicode_literals
from logging import getLogger
import xbmc
from .plex_api import API
from .playutils import PlayUtils
from .windows.resume import resume_dialog
from . import app, plex_functions as PF, utils, json_rpc, variables as v, \
widgets, playlist_func as PL, playqueue as PQ
LOG = getLogger('PLEX.playstrm')
class PlayStrmException(Exception):
"""
Any Exception associated with playstrm
"""
pass
class PlayStrm(object):
'''
Workflow: Strm that calls our webservice in database. When played, the
webserivce returns a dummy file to play. Meanwhile, PlayStrm adds the real
listitems for items to play to the playlist.
'''
def __init__(self, params, server_id=None):
LOG.debug('Starting PlayStrm with server_id %s, params: %s',
server_id, params)
self.xml = None
self.playqueue_item = None
self.api = None
self.start_index = None
self.index = None
self.server_id = server_id
self.plex_id = utils.cast(int, params['plex_id'])
self.plex_type = params.get('plex_type')
if params.get('synched') and params['synched'].lower() == 'false':
self.synched = False
else:
self.synched = True
self.kodi_id = utils.cast(int, params.get('kodi_id'))
self.kodi_type = params.get('kodi_type')
self._get_xml()
self.name = self.api.title()
if ((self.kodi_id is None or self.kodi_type is None) and
self.xml[0].get('pkc_db_item')):
self.kodi_id = self.xml[0].get('pkc_db_item')['kodi_id']
self.kodi_type = self.xml[0].get('pkc_db_item')['kodi_type']
self.transcode = params.get('transcode')
if self.transcode is None:
self.transcode = utils.settings('playFromTranscode.bool') if utils.settings('playFromStream.bool') else None
if app.PLAYSTATE.audioplaylist:
LOG.debug('Audio playlist detected')
self.playqueue = PQ.get_playqueue_from_type(v.KODI_TYPE_AUDIO)
else:
LOG.debug('Video playlist detected')
self.playqueue = PQ.get_playqueue_from_type(v.KODI_TYPE_VIDEO)
self.kodi_playlist = self.playqueue.kodi_pl
def __repr__(self):
return ("{{"
"'name': '{self.name}', "
"'plex_id': {self.plex_id}, "
"'plex_type': '{self.plex_type}', "
"'kodi_id': {self.kodi_id}, "
"'kodi_type': '{self.kodi_type}', "
"'server_id': '{self.server_id}', "
"'transcode': {self.transcode}, "
"'start_index': {self.start_index}, "
"'index': {self.index}"
"}}").format(self=self).encode('utf-8')
__str__ = __repr__
def playlist_add_json(self):
playlistid = self.kodi_playlist.getPlayListId()
LOG.debug('Adding kodi_id %s, kodi_type %s to playlist %s at index %s',
self.kodi_id, self.kodi_type, playlistid, self.index)
if self.index is None:
json_rpc.playlist_add(playlistid,
{'%sid' % self.kodi_type: self.kodi_id})
else:
json_rpc.playlist_insert({'playlistid': playlistid,
'position': self.index,
'item': {'%sid' % self.kodi_type: self.kodi_id}})
def playlist_add(self, url, listitem):
self.kodi_playlist.add(url=url, listitem=listitem, index=self.index)
self.playqueue_item.file = url.decode('utf-8')
self.playqueue.items.insert(self.index, self.playqueue_item)
self.index += 1
def remove_from_playlist(self, index):
LOG.debug('Removing playlist item number %s from %s', index, self)
json_rpc.playlist_remove(self.kodi_playlist.getPlayListId(),
index)
def _get_xml(self):
self.xml = PF.GetPlexMetadata(self.plex_id)
if self.xml in (None, 401):
raise PlayStrmException('No xml received from the PMS')
if self.synched:
# Adds a new key 'pkc_db_item' to self.xml[0].attrib
widgets.attach_kodi_ids(self.xml)
else:
self.xml[0].set('pkc_db_item', None)
self.api = API(self.xml[0])
def set_playqueue_item(self, xml, kodi_id, kodi_type):
self.playqueue_item = PL.playlist_item_from_xml(xml,
kodi_id=kodi_id,
kodi_type=kodi_type)
self.playqueue_item.force_transcode = self.transcode
def start_playback(self, index=0):
LOG.debug('Starting playback at %s', index)
xbmc.Player().play(self.kodi_playlist, startpos=index, windowed=False)
def play(self, start_position=None, delayed=True):
'''
Create and add listitems to the Kodi playlist.
'''
LOG.debug('play called with start_position %s, delayed %s',
start_position, delayed)
if start_position is not None:
self.start_index = start_position
else:
self.start_index = max(self.kodi_playlist.getposition(), 0)
self.index = self.start_index
self._set_playlist()
LOG.info('Initiating play for %s', self)
if not delayed:
self.start_playback(self.start_index)
return self.start_index
def play_folder(self, position=None):
'''
When an entire queue is requested, If requested from Kodi, kodi_type is
provided, add as Kodi would, otherwise queue playlist items using strm
links to setup playback later.
'''
self.start_index = position or max(self.kodi_playlist.size(), 0)
self.index = self.start_index + 1
LOG.info('Play folder plex_id %s, index: %s', self.plex_id, self.index)
if self.kodi_id and self.kodi_type:
self.playlist_add_json()
self.index += 1
else:
listitem = widgets.get_listitem(self.xml[0], resume=True)
url = 'http://127.0.0.1:%s/plex/play/file.strm' % v.WEBSERVICE_PORT
args = {
'mode': 'play',
'plex_id': self.plex_id,
'plex_type': self.api.plex_type()
}
if self.kodi_id:
args['kodi_id'] = self.kodi_id
if self.kodi_type:
args['kodi_type'] = self.kodi_type
if self.server_id:
args['server_id'] = self.server_id
if self.transcode:
args['transcode'] = True
url = utils.extend_url(url, args).encode('utf-8')
listitem.setPath(url)
self.playlist_add(url, listitem)
return self.index - 1
def _set_playlist(self):
'''
Verify seektime, set intros, set main item and set additional parts.
Detect the seektime for video type content. Verify the default video
action set in Kodi for accurate resume behavior.
'''
seektime = self._resume()
trailers = False
if (not seektime and self.plex_type == v.PLEX_TYPE_MOVIE and
utils.settings('enableCinema') == 'true'):
if utils.settings('askCinema') == "true":
# "Play trailers?"
trailers = utils.yesno_dialog(utils.lang(29999),
utils.lang(33016)) or False
else:
trailers = True
LOG.debug('Playing trailers: %s', trailers)
xml = PF.init_plex_playqueue(self.plex_id,
self.xml.get('librarySectionUUID'),
mediatype=self.plex_type,
trailers=trailers)
if xml is None:
LOG.error('Could not get playqueue for UUID %s for %s',
self.xml.get('librarySectionUUID'), self)
# "Play error"
utils.dialog('notification',
utils.lang(29999),
utils.lang(30128),
icon='{error}')
app.PLAYSTATE.context_menu_play = False
app.PLAYSTATE.resume_playback = False
return
PL.get_playlist_details_from_xml(self.playqueue, xml)
# See that we add trailers, if they exist in the xml return
self._add_intros(xml)
# Add the main item
if seektime:
listitem = widgets.get_listitem(self.xml[0], resume=True)
else:
listitem = widgets.get_listitem(self.xml[0], resume=False)
listitem.setSubtitles(self.api.cache_external_subs())
self.set_playqueue_item(self.xml[0], self.kodi_id, self.kodi_type)
play = PlayUtils(self.api, self.playqueue_item)
url = play.getPlayUrl().encode('utf-8')
listitem.setPath(url)
self.playlist_add(url, listitem)
# Add additional file parts, if any exist
self._add_additional_parts()
def _resume(self):
'''
Resume item if available. Returns bool or raise an PlayStrmException if
resume was cancelled by user.
'''
seektime = app.PLAYSTATE.resume_playback
app.PLAYSTATE.resume_playback = None
if app.PLAYSTATE.autoplay:
seektime = False
LOG.info('Skip resume for autoplay')
elif seektime is None:
resume = self.api.resume_point()
if resume:
seektime = resume_dialog(resume)
LOG.info('User chose resume: %s', seektime)
if seektime is None:
raise PlayStrmException('User backed out of resume dialog.')
app.PLAYSTATE.autoplay = True
return seektime
def _add_intros(self, xml):
'''
if we have any play them when the movie/show is not being resumed.
'''
if not len(xml) > 1:
LOG.debug('No trailers returned from the PMS')
return
for intro in xml:
api = API(intro)
if not api.plex_type() == v.PLEX_TYPE_CLIP:
# E.g. the main item we're looking at - skip!
continue
LOG.debug('Adding trailer: %s', api.title())
listitem = widgets.get_listitem(intro, resume=False)
self.set_playqueue_item(intro, None, None)
play = PlayUtils(api, self.playqueue_item)
url = play.getPlayUrl().encode('utf-8')
listitem.setPath(url)
self.playlist_add(url, listitem)
def _add_additional_parts(self):
''' Create listitems and add them to the stack of playlist.
'''
for part, _ in enumerate(self.xml[0][0]):
if part == 0:
# The first part that we've already added
continue
self.api.set_part_number(part)
LOG.debug('Adding addional part %s', part)
self.set_playqueue_item(self.xml[0], self.kodi_id, self.kodi_type)
self.playqueue_item.part = part
listitem = widgets.get_listitem(self.xml[0], resume=False)
listitem.setSubtitles(self.api.cache_external_subs())
playqueue_item = PL.playlist_item_from_xml(self.xml[0])
play = PlayUtils(self.api, playqueue_item)
url = play.getPlayUrl().encode('utf-8')
listitem.setPath(url)
self.playlist_add(url, listitem)