mirror of
https://github.com/pkkid/python-plexapi
synced 2024-11-22 19:53:17 +00:00
New test for client.timeline(); Fix bug in missing proper headers for timeline
This commit is contained in:
parent
7bce1c4b32
commit
7bb700d395
4 changed files with 59 additions and 46 deletions
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
||||||
#-----------------------
|
#-----------------------
|
||||||
|
|
Loading…
Reference in a new issue