PlexKodiConnect/resources/lib/plex_companion/processing.py

251 lines
8.9 KiB
Python
Raw Permalink Normal View History

#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
The Plex Companion master python file
"""
from logging import getLogger
import xbmc
from ..plex_api import API
from .. import utils
from ..utils import cast
from .. import plex_functions as PF
from .. import playlist_func as PL
from .. import playback
from .. import json_rpc as js
from .. import variables as v
from .. import app
from .. import exceptions
log = getLogger('PLEX.companion.processing')
def update_playqueue_from_PMS(playqueue,
playqueue_id=None,
repeat=None,
offset=None,
transient_token=None,
start_plex_id=None):
"""
Completely updates the Kodi playqueue with the new Plex playqueue. Pass
in playqueue_id if we need to fetch a new playqueue
repeat = 0, 1, 2
offset = time offset in Plextime (milliseconds)
"""
log.info('New playqueue %s received from Plex companion with offset '
'%s, repeat %s, start_plex_id %s',
playqueue_id, offset, repeat, start_plex_id)
# Safe transient token from being deleted
if transient_token is None:
transient_token = playqueue.plex_transient_token
with app.APP.lock_playqueues:
try:
xml = PL.get_PMS_playlist(playqueue, playqueue_id)
except exceptions.PlaylistError:
log.error('Could now download playqueue %s', playqueue_id)
return
if playqueue.id == playqueue_id:
# This seems to be happening ONLY if a Plex Companion device
# reconnects and Kodi is already playing something - silly, really
# For all other cases, a new playqueue is generated by Plex
log.debug('Update for existing playqueue detected')
return
playqueue.clear()
# Get new metadata for the playqueue first
try:
PL.get_playlist_details_from_xml(playqueue, xml)
except exceptions.PlaylistError:
log.error('Could not get playqueue ID %s', playqueue_id)
return
playqueue.repeat = 0 if not repeat else int(repeat)
playqueue.plex_transient_token = transient_token
playback.play_xml(playqueue,
xml,
offset=offset,
start_plex_id=start_plex_id)
def process_node(key, transient_token, offset):
"""
E.g. watch later initiated by Companion. Basically navigating Plex
"""
app.CONN.plex_transient_token = transient_token
params = {
'mode': 'plex_node',
'key': f'{{server}}{key}',
'offset': offset
}
handle = f'RunPlugin(plugin://{utils.extend_url(v.ADDON_ID, params)})'
xbmc.executebuiltin(handle)
def process_playlist(containerKey, typus, key, offset, token):
# Get the playqueue ID
_, container_key, query = PF.ParseContainerKey(containerKey)
try:
2021-10-31 20:44:27 +11:00
playqueue = app.PLAYQUEUES.from_plex_type(typus)
except ValueError:
# E.g. Plex web does not supply the media type
# Still need to figure out the type (video vs. music vs. pix)
xml = PF.GetPlexMetadata(key)
try:
xml[0].attrib
except (AttributeError, IndexError, TypeError):
log.error('Could not download Plex metadata')
return
api = API(xml[0])
2021-10-31 20:44:27 +11:00
playqueue = app.PLAYQUEUES.from_plex_type(api.plex_type)
if key:
_, key, _ = PF.ParseContainerKey(key)
update_playqueue_from_PMS(playqueue,
playqueue_id=container_key,
repeat=query.get('repeat'),
offset=utils.cast(int, offset),
transient_token=token,
start_plex_id=key)
2021-10-31 20:44:27 +11:00
def process_streams(plex_type, video_stream_id, audio_stream_id,
subtitle_stream_id):
"""
Plex Companion client adjusted audio or subtitle stream
"""
2021-10-31 20:44:27 +11:00
playqueue = app.PLAYQUEUES.from_plex_type(plex_type)
pos = js.get_position(playqueue.playlistid)
playqueue.items[pos].on_plex_stream_change(video_stream_id,
audio_stream_id,
subtitle_stream_id)
def process_refresh(playqueue_id):
"""
example data: {'playQueueID': '8475', 'commandID': '11'}
"""
xml = PL.get_pms_playqueue(playqueue_id)
if xml is None:
return
if len(xml) == 0:
log.debug('Empty playqueue received - clearing playqueue')
plex_type = PL.get_plextype_from_xml(xml)
if plex_type is None:
return
2021-10-31 20:44:27 +11:00
playqueue = app.PLAYQUEUES.from_plex_type(plex_type)
playqueue.clear()
return
2021-10-31 20:44:27 +11:00
playqueue = app.PLAYQUEUES.from_plex_type(xml[0].attrib['type'])
update_playqueue_from_PMS(playqueue, playqueue_id)
def skip_to(playqueue_item_id, key):
"""
Skip to a specific playlist position.
Does not seem to be implemented yet by Plex!
"""
_, plex_id = PF.GetPlexKeyNumber(key)
log.debug('Skipping to playQueueItemID %s, plex_id %s',
playqueue_item_id, plex_id)
found = True
for player in list(js.get_players().values()):
2021-10-31 20:44:27 +11:00
playqueue = app.PLAYQUEUES[player['playerid']]
for i, item in enumerate(playqueue.items):
if item.id == playqueue_item_id:
found = True
break
else:
for i, item in enumerate(playqueue.items):
if item.plex_id == plex_id:
found = True
break
if found is True:
app.APP.player.play(playqueue.kodi_pl, None, False, i)
else:
log.error('Item not found to skip to')
def convert_xml_to_params(xml):
new_params = dict(xml.attrib)
for key in xml.attrib:
if key.startswith('query'):
new_params[key[5].lower() + key[6:]] = xml.get(key)
del new_params[key]
return new_params
def process_command(cmd=None, path=None, params=None):
"""cmd: a "Command" etree xml"""
path = cmd.get('path') if cmd is not None else path
if not path.startswith('/'):
path = '/' + path
if params is None:
params = convert_xml_to_params(cmd)
if path == '/player/playback/playMedia' and \
params.get('address') == 'node.plexapp.com':
process_node(cmd.get('queryKey'),
cmd.get('queryToken'),
cmd.get('queryOffset') or 0)
elif path == '/player/playback/playMedia':
with app.APP.lock_playqueues:
process_playlist(params.get('containerKey'),
params.get('type'),
params.get('key'),
params.get('offset'),
params.get('token'))
elif path == '/player/playback/refreshPlayQueue':
with app.APP.lock_playqueues:
process_refresh(params.get('playQueueID'))
elif path == '/player/playback/setParameters':
js.set_volume(int(params.get('volume')))
elif path == '/player/playback/play':
js.play()
elif path == '/player/playback/pause':
js.pause()
elif path == '/player/playback/stop':
js.stop()
elif path == '/player/playback/seekTo':
js.seek_to(float(params.get('offset', 0.0)) / 1000.0)
elif path == '/player/playback/stepForward':
js.smallforward()
elif path == '/player/playback/stepBack':
js.smallbackward()
elif path == '/player/playback/skipNext':
js.skipnext()
elif path == '/player/playback/skipPrevious':
js.skipprevious()
elif path == '/player/playback/skipTo':
skip_to(params.get('playQueueItemID'), params.get('key'))
elif path == '/player/navigation/moveUp':
js.input_up()
elif path == '/player/navigation/moveDown':
js.input_down()
elif path == '/player/navigation/moveLeft':
js.input_left()
elif path == '/player/navigation/moveRight':
js.input_right()
elif path == '/player/navigation/select':
js.input_select()
elif path == '/player/navigation/home':
js.input_home()
elif path == '/player/navigation/back':
js.input_back()
elif path == '/player/playback/setStreams':
process_streams(params.get('type'),
cast(int, params.get('videoStreamID')),
cast(int, params.get('audioStreamID')),
cast(int, params.get('subtitleStreamID')))
elif path == '/player/timeline/subscribe':
pass
elif path == '/player/timeline/unsubscribe':
pass
else:
if cmd is None:
log.error('Unknown request_path: %s with params %s', path, params)
else:
log.error('Unknown Plex companion path/command: %s: %s',
cmd.tag, cmd.attrib)
return False
return True