New test for client.timeline(); Fix bug in missing proper headers for timeline

This commit is contained in:
Michael Shepanski 2016-03-31 23:39:09 -04:00
parent 7bce1c4b32
commit 7bb700d395
4 changed files with 59 additions and 46 deletions

View file

@ -13,7 +13,7 @@ CONFIG = PlexConfig(CONFIG_PATH)
# Core Settings # Core Settings
PROJECT = 'PlexAPI' # name provided to plex server PROJECT = 'PlexAPI' # name provided to plex server
VERSION = '2.0.0a' # version of this api VERSION = '2.0.0a' # version of this api
TIMEOUT = CONFIG.get('plexapi.timeout', 5, int) # request timeout TIMEOUT = CONFIG.get('plexapi.timeout', 30, int) # request timeout
X_PLEX_CONTAINER_SIZE = 50 # max results to return in a single search page X_PLEX_CONTAINER_SIZE = 50 # max results to return in a single search page
# Plex Header Configuation # Plex Header Configuation

View file

@ -6,7 +6,7 @@ https://github.com/plexinc/plex-media-player/wiki/Remote-control-API
""" """
import requests import requests
from requests.status_codes import _codes as codes from requests.status_codes import _codes as codes
from plexapi import TIMEOUT, log, utils from plexapi import BASE_HEADERS, TIMEOUT, log, utils
from plexapi.exceptions import BadRequest, Unsupported from plexapi.exceptions import BadRequest, Unsupported
from xml.etree import ElementTree from xml.etree import ElementTree
@ -50,15 +50,10 @@ class Client(object):
if controller not in self.protocolCapabilities: if controller not in self.protocolCapabilities:
raise Unsupported('Client %s does not support the %s controller.' % (self.quickName, controller)) raise Unsupported('Client %s does not support the %s controller.' % (self.quickName, controller))
self._commandId += 1 self._commandId += 1
params.update({ params['commandID'] = self._commandId
'X-Plex-Device-Name': self.name,
'X-Plex-Client-Identifier': self.server.machineIdentifier,
'X-Plex-Target-Client-Identifier': self.machineIdentifier,
'commandID': self._commandId,
})
url = 'http://%s:%s/player/%s%s' % (self.address, self.port, command.lstrip('/'), utils.joinArgs(params)) url = 'http://%s:%s/player/%s%s' % (self.address, self.port, command.lstrip('/'), utils.joinArgs(params))
log.info('GET %s', url) log.info('GET %s', url)
response = requests.get(url, timeout=TIMEOUT) response = requests.get(url, headers=BASE_HEADERS, timeout=TIMEOUT)
if response.status_code != requests.codes.ok: if response.status_code != requests.codes.ok:
codename = codes.get(response.status_code)[0] codename = codes.get(response.status_code)[0]
raise BadRequest('(%s) %s' % (response.status_code, codename)) raise BadRequest('(%s) %s' % (response.status_code, codename))
@ -97,7 +92,7 @@ class Client(object):
}, **params)) }, **params))
# Playback Commands # Playback Commands
# most of the playback commands take a mandatory mtype {'music','photo','video'} argument, # Most of the playback commands take a mandatory mtype {'music','photo','video'} argument,
# to specify which media type to apply the command to, (except for playMedia). This # to specify which media type to apply the command to, (except for playMedia). This
# is in case there are multiple things happening (e.g. music in the background, photo # is in case there are multiple things happening (e.g. music in the background, photo
# slideshow in the foreground). # slideshow in the foreground).
@ -138,7 +133,6 @@ class Client(object):
self.sendCommand('playback/setParameters', **params) self.sendCommand('playback/setParameters', **params)
def setStreams(self, audioStreamID=None, subtitleStreamID=None, videoStreamID=None, mtype=None): def setStreams(self, audioStreamID=None, subtitleStreamID=None, videoStreamID=None, mtype=None):
# Can possibly send {next,on,off}
params = {} params = {}
if audioStreamID is not None: params['audioStreamID'] = audioStreamID if audioStreamID is not None: params['audioStreamID'] = audioStreamID
if subtitleStreamID is not None: params['subtitleStreamID'] = subtitleStreamID if subtitleStreamID is not None: params['subtitleStreamID'] = subtitleStreamID
@ -147,12 +141,12 @@ class Client(object):
self.sendCommand('playback/setStreams', **params) self.sendCommand('playback/setStreams', **params)
# Timeline Commands # Timeline Commands
# TODO: We should properly parse the Timeline XML objects here
def timeline(self): def timeline(self):
self.sendCommand('timeline/poll', **{'wait':1, 'commandID':4}) return self.sendCommand('timeline/poll', **{'wait':1, 'commandID':4})
def isPlayingMedia(self): def isPlayingMedia(self):
timeline = self.timeline() for mediatype in self.timeline():
for media_type in timeline: if mediatype.get('state') == 'playing':
if media_type.get('state') == 'playing':
return True return True
return False return False

View file

@ -113,6 +113,15 @@ class LibrarySection(object):
def onDeck(self): def onDeck(self):
return utils.listItems(self.server, '/library/sections/%s/onDeck' % self.key) return utils.listItems(self.server, '/library/sections/%s/onDeck' % self.key)
def analyze(self):
self.server.query('/library/sections/%s/analyze' % self.key)
def emptyTrash(self):
self.server.query('/library/sections/%s/emptyTrash' % self.key)
def refresh(self):
self.server.query('/library/sections/%s/refresh' % self.key)
def listChoices(self, category, libtype=None, **kwargs): def listChoices(self, category, libtype=None, **kwargs):
""" List choices for the specified filter category. kwargs can be any of the same """ List choices for the specified filter category. kwargs can be any of the same
kwargs in self.search() to help narrow down the choices to only those that kwargs in self.search() to help narrow down the choices to only those that
@ -171,15 +180,6 @@ class LibrarySection(object):
results += subresults[:maxresults-len(results)] results += subresults[:maxresults-len(results)]
args['X-Plex-Container-Start'] += args['X-Plex-Container-Size'] args['X-Plex-Container-Start'] += args['X-Plex-Container-Size']
return results return results
def analyze(self):
self.server.query('/library/sections/%s/analyze' % self.key)
def emptyTrash(self):
self.server.query('/library/sections/%s/emptyTrash' % self.key)
def refresh(self):
self.server.query('/library/sections/%s/refresh' % self.key)
def _cleanSearchFilter(self, category, value, libtype=None): def _cleanSearchFilter(self, category, value, libtype=None):
# check a few things before we begin # check a few things before we begin

View file

@ -144,7 +144,6 @@ def test_crazy_search(plex, user=None):
assert movie in movies.search(actor=judy.id), 'Unable to filter movie by year.' assert movie in movies.search(actor=judy.id), 'Unable to filter movie by year.'
#----------------------- #-----------------------
# Library Navigation # Library Navigation
#----------------------- #-----------------------
@ -392,10 +391,10 @@ def test_client_navigation(plex, user=None):
# @register('client') # @register('client')
# def test_client_navigation_via_proxy(plex, user=None): def test_client_navigation_via_proxy(plex, user=None):
# client = plex.client(PLEX_CLIENT) client = plex.client(PLEX_CLIENT)
# client.proxyThroughServer() client.proxyThroughServer()
# _navigate(plex, client) _navigate(plex, client)
def _navigate(plex, client): def _navigate(plex, client):
@ -437,10 +436,10 @@ def test_video_playback(plex, user=None):
# @register('client') # @register('client')
# def test_video_playback_via_proxy(plex, user=None): def test_video_playback_via_proxy(plex, user=None):
# client = plex.client(PLEX_CLIENT) client = plex.client(PLEX_CLIENT)
# client.proxyThroughServer() client.proxyThroughServer()
# _video_playback(plex, client) _video_playback(plex, client)
def _video_playback(plex, client): def _video_playback(plex, client):
@ -467,19 +466,39 @@ def _video_playback(plex, client):
client.stop(mtype); time.sleep(1) client.stop(mtype); time.sleep(1)
# def test_sync_items(plex, user=None): @register('client')
# user = MyPlexUser('user', 'pass') def test_client_timeline(plex, user=None):
# device = user.getDevice('device-uuid') mtype = 'video'
# # fetch the sync items via the device sync list client = plex.client(PLEX_CLIENT)
# for item in device.sync_items(): movie = plex.library.section(MOVIE_SECTION).get(MOVIE_TITLE)
# # fetch the media object associated with the sync item playing = client.isPlayingMedia()
# for video in item.get_media(): log(2, 'Playing Media: %s' % playing)
# # fetch the media parts (actual video/audio streams) associated with the media assert playing is False, 'isPlayingMedia() should have returned False.'
# for part in video.iterParts(): client.playMedia(movie); time.sleep(30)
# print('Found media to download!') playing = client.isPlayingMedia()
# # make the relevant sync id (media part) as downloaded log(2, 'Playing Media: %s' % playing)
# # this tells the server that this device has successfully downloaded this media part of this sync item assert playing is True, 'isPlayingMedia() should have returned True.'
# item.mark_as_done(part.sync_id) client.stop(mtype); time.sleep(30)
playing = client.isPlayingMedia()
log(2, 'Playing Media: %s' % playing)
assert playing is False, 'isPlayingMedia() should have returned False.'
# TODO: MAKE THIS WORK..
# @register('client')
def test_sync_items(plex, user=None):
user = MyPlexUser('user', 'pass')
device = user.getDevice('device-uuid')
# fetch the sync items via the device sync list
for item in device.sync_items():
# fetch the media object associated with the sync item
for video in item.get_media():
# fetch the media parts (actual video/audio streams) associated with the media
for part in video.iterParts():
print('Found media to download!')
# make the relevant sync id (media part) as downloaded
# this tells the server that this device has successfully downloaded this media part of this sync item
item.mark_as_done(part.sync_id)
#----------------------- #-----------------------