Finished updating socs for client, config, exceptions

This commit is contained in:
Michael Shepanski 2017-01-23 00:15:51 -05:00
parent db18f2640f
commit 706b974b05
9 changed files with 271 additions and 262 deletions

4
docs/configuration.rst Normal file
View file

@ -0,0 +1,4 @@
Configuration
=============
dasfasd

View file

@ -1,5 +1,5 @@
Introduction Getting Started
============ ===============
.. |br| raw:: html .. |br| raw:: html

View file

@ -1,12 +1,14 @@
.. toctree:: .. toctree::
:maxdepth: 2 :maxdepth: 1
:caption: Table of Contents :caption: Table of Contents
:titlesonly:
self self
introduction introduction
configuration
.. toctree:: .. toctree::
:maxdepth: 2 :maxdepth: 1
:caption: Modules :caption: Modules
modules/audio modules/audio

View file

@ -1,5 +1,4 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from plexapi import media, utils from plexapi import media, utils
from plexapi.utils import Playable, PlexPartialObject from plexapi.utils import Playable, PlexPartialObject
@ -10,6 +9,11 @@ class Audio(PlexPartialObject):
""" Base class for audio :class:`~plexapi.audio.Artist`, :class:`~plexapi.audio.Album` """ Base class for audio :class:`~plexapi.audio.Artist`, :class:`~plexapi.audio.Album`
and :class:`~plexapi.audio.Track` objects. and :class:`~plexapi.audio.Track` objects.
Parameters:
server (:class:`~plexapi.server.PlexServer`): PlexServer this client is connected to (optional)
data (:class:`ElementTree`): Response from PlexServer used to build this object (optional).
initpath (str): Relative path requested when retrieving specified `data` (optional).
Attributes: Attributes:
addedAt (datetime): Datetime this item was added to the library. addedAt (datetime): Datetime this item was added to the library.
index (sting): Index Number (often the track number). index (sting): Index Number (often the track number).
@ -67,6 +71,11 @@ class Audio(PlexPartialObject):
class Artist(Audio): class Artist(Audio):
""" Represents a single audio artist. """ Represents a single audio artist.
Parameters:
server (:class:`~plexapi.server.PlexServer`): PlexServer this client is connected to (optional)
data (:class:`ElementTree`): Response from PlexServer used to build this object (optional).
initpath (str): Relative path requested when retrieving specified `data` (optional).
Attributes: Attributes:
art (str): Artist artwork (/library/metadata/<ratingkey>/art/<artid>) art (str): Artist artwork (/library/metadata/<ratingkey>/art/<artid>)
countries (list): List of :class:`~plexapi.media.Country` objects this artist respresents. countries (list): List of :class:`~plexapi.media.Country` objects this artist respresents.
@ -76,7 +85,6 @@ class Artist(Audio):
location (str): Filepath this artist is found on disk. location (str): Filepath this artist is found on disk.
similar (list): List of :class:`~plexapi.media.Similar` artists. similar (list): List of :class:`~plexapi.media.Similar` artists.
""" """
TYPE = 'artist' TYPE = 'artist'
def _loadData(self, data): def _loadData(self, data):
@ -128,6 +136,11 @@ class Artist(Audio):
class Album(Audio): class Album(Audio):
""" Represents a single audio album. """ Represents a single audio album.
Parameters:
server (:class:`~plexapi.server.PlexServer`): PlexServer this client is connected to (optional)
data (:class:`ElementTree`): Response from PlexServer used to build this object (optional).
initpath (str): Relative path requested when retrieving specified `data` (optional).
Attributes: Attributes:
art (str): Album artwork (/library/metadata/<ratingkey>/art/<artid>) art (str): Album artwork (/library/metadata/<ratingkey>/art/<artid>)
genres (list): List of :class:`~plexapi.media.Genre` objects this album respresents. genres (list): List of :class:`~plexapi.media.Genre` objects this album respresents.
@ -140,7 +153,6 @@ class Album(Audio):
studio (str): Studio that released this album. studio (str): Studio that released this album.
year (int): Year this album was released. year (int): Year this album was released.
""" """
TYPE = 'album' TYPE = 'album'
def _loadData(self, data): def _loadData(self, data):
@ -185,6 +197,11 @@ class Album(Audio):
class Track(Audio, Playable): class Track(Audio, Playable):
""" Represents a single audio track. """ Represents a single audio track.
Parameters:
server (:class:`~plexapi.server.PlexServer`): PlexServer this client is connected to (optional)
data (:class:`ElementTree`): XML response from PlexServer used to build this object (optional).
initpath (str): Relative path requested when retrieving specified `data` (optional).
Attributes: Attributes:
art (str): Track artwork (/library/metadata/<ratingkey>/art/<artid>) art (str): Track artwork (/library/metadata/<ratingkey>/art/<artid>)
chapterSource (TYPE): Unknown chapterSource (TYPE): Unknown
@ -210,9 +227,9 @@ class Track(Audio, Playable):
sessionKey (int): Session Key (active sessions only). sessionKey (int): Session Key (active sessions only).
username (str): Username of person playing this track (active sessions only). username (str): Username of person playing this track (active sessions only).
player (str): :class:`~plexapi.client.PlexClient` for playing track (active sessions only). player (str): :class:`~plexapi.client.PlexClient` for playing track (active sessions only).
transcodeSession (None): :class:`~plexapi.media.TranscodeSession` for playing track (active sessions only). transcodeSession (None): :class:`~plexapi.media.TranscodeSession` for playing
track (active sessions only).
""" """
TYPE = 'track' TYPE = 'track'
def _loadData(self, data): def _loadData(self, data):

View file

@ -1,10 +1,4 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
"""
PlexAPI Client
To understand how this works, read this page:
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 BASE_HEADERS, TIMEOUT, log, utils from plexapi import BASE_HEADERS, TIMEOUT, log, utils
@ -13,40 +7,42 @@ from xml.etree import ElementTree
class PlexClient(object): class PlexClient(object):
"""Main class for interacting with a client. """ Main class for interacting with a Plex client. This class can connect
directly to the client and control it or proxy commands through your
Plex Server. To better understand the Plex client API's read this page:
https://github.com/plexinc/plex-media-player/wiki/Remote-control-API
Parameters:
baseurl (str): HTTP URL to connect dirrectly to this client.
token (str): X-Plex-Token used for authenication (optional).
session (:class:`~requests.Session`): requests.Session object if you want more control (optional).
server (:class:`~plexapi.server.PlexServer`): PlexServer this client is connected to (optional)
data (:class:`ElementTree`): Response from PlexServer used to build this object (optional).
Attributes: Attributes:
baseurl (str): http adress for the client baseurl (str): HTTP address of the client
device (None): Description device (str): Best guess on the type of device this is (PS, iPhone, Linux, etc).
deviceClass (sting): pc, phone deviceClass (str): Device class (pc, phone, etc).
machineIdentifier (str): uuid fx 5471D9EA-1467-4051-9BE7-FCBDF490ACE3 machineIdentifier (str): Unique ID for this device.
model (TYPE): Description model (str): Unknown
platform (TYPE): Description platform (str): Unknown
platformVersion (TYPE): Description platformVersion (str): Description
product (str): plex for ios product (str): Client Product (Plex for iOS, etc).
protocol (str): plex protocol (str): Always seems ot be 'plex'.
protocolCapabilities (list): List of what client can do protocolCapabilities (list<str>): List of client capabilities (navigation, playback,
protocolVersion (str): 1 timeline, mirror, playqueues).
server (plexapi.server.Plexserver): PMS your connected to protocolVersion (str): Protocol version (1, future proofing?)
session (None or requests.Session): Add your own session object to cache stuff server (:class:`~plexapi.server.PlexServer`): Server this client is connected to.
state (None): Description session (:class:`~requests.Session`): Session object used for connection.
title (str): fx Johns Iphone state (str): Unknown
token (str): X-Plex-Token, using for authenication with PMS title (str): Name of this client (Johns iPhone, etc).
vendor (str): Description token (str): X-Plex-Token used for authenication
version (str): fx. 4.6 vendor (str): Unknown
version (str): Device version (4.6.1, etc).
_proxyThroughServer (bool): Set to True after calling
:func:`~plexapi.client.PlexClient.proxyThroughServer()` (default False).
""" """
def __init__(self, baseurl, token=None, session=None, server=None, data=None): def __init__(self, baseurl, token=None, session=None, server=None, data=None):
"""Kick shit off.
Args:
baseurl (sting): fx http://10.0.0.99:1111222
token (None, optional): X-Plex-Token, using for authenication with PMS
session (None, optional): requests.Session() or your own session
server (None, optional): PlexServer
data (None, optional): XML response from PMS as Element
or uses connect to get it
"""
self.baseurl = baseurl.strip('/') self.baseurl = baseurl.strip('/')
self.token = token self.token = token
self.session = session or requests.Session() self.session = session or requests.Session()
@ -56,22 +52,17 @@ class PlexClient(object):
self._commandId = 0 self._commandId = 0
def _loadData(self, data): def _loadData(self, data):
"""Sets attrs to the class. """ Load attribute values from Plex XML response. """
Args:
data (Element): XML response from PMS as a Element
"""
self.deviceClass = data.attrib.get('deviceClass') self.deviceClass = data.attrib.get('deviceClass')
self.machineIdentifier = data.attrib.get('machineIdentifier') self.machineIdentifier = data.attrib.get('machineIdentifier')
self.product = data.attrib.get('product') self.product = data.attrib.get('product')
self.protocol = data.attrib.get('protocol') self.protocol = data.attrib.get('protocol')
self.protocolCapabilities = data.attrib.get( self.protocolCapabilities = data.attrib.get('protocolCapabilities', '').split(',')
'protocolCapabilities', '').split(',')
self.protocolVersion = data.attrib.get('protocolVersion') self.protocolVersion = data.attrib.get('protocolVersion')
self.platform = data.attrib.get('platform') self.platform = data.attrib.get('platform')
self.platformVersion = data.attrib.get('platformVersion') self.platformVersion = data.attrib.get('platformVersion')
self.title = data.attrib.get('title') or data.attrib.get('name') self.title = data.attrib.get('title') or data.attrib.get('name')
# active session details # Active session details
self.device = data.attrib.get('device') self.device = data.attrib.get('device')
self.model = data.attrib.get('model') self.model = data.attrib.get('model')
self.state = data.attrib.get('state') self.state = data.attrib.get('state')
@ -79,7 +70,7 @@ class PlexClient(object):
self.version = data.attrib.get('version') self.version = data.attrib.get('version')
def connect(self): def connect(self):
"""Connect""" """ Connects to the client and reloads all class attributes. """
try: try:
data = self.query('/resources')[0] data = self.query('/resources')[0]
self._loadData(data) self._loadData(data)
@ -88,43 +79,38 @@ class PlexClient(object):
raise NotFound('No client found at: %s' % self.baseurl) raise NotFound('No client found at: %s' % self.baseurl)
def headers(self): def headers(self):
"""Default headers """ Returns a dict of all default headers for Client requests. """
Returns:
dict: default headers
"""
headers = BASE_HEADERS headers = BASE_HEADERS
if self.token: if self.token:
headers['X-Plex-Token'] = self.token headers['X-Plex-Token'] = self.token
return headers return headers
def proxyThroughServer(self, value=True): def proxyThroughServer(self, value=True):
"""Connect to the client via the server. """ Tells this PlexClient instance to proxy all future commands through the PlexServer.
Useful if you do not wish to connect directly to the Client device itself.
Args: Parameters:
value (bool, optional): Description value (bool): Enable or disable proxying (optional, default True).
Raises: Raises:
Unsupported: Cannot use client proxy with unknown server. :class:`~plexapi.exceptions.Unsupported`: Cannot use client proxy with unknown server.
""" """
if value is True and not self.server: if value is True and not self.server:
raise Unsupported('Cannot use client proxy with unknown server.') raise Unsupported('Cannot use client proxy with unknown server.')
self._proxyThroughServer = value self._proxyThroughServer = value
def query(self, path, method=None, headers=None, **kwargs): def query(self, path, method=None, headers=None, **kwargs):
"""Used to fetch relative paths to pms. """ Returns an :class:`ElementTree` object containing the response
from the specified request path.
Args: Parameters:
path (str): Relative path path (str): Relative path to query.
method (None, optional): requests.post etc method (func): `self.session.get` or `self.session.post`
headers (None, optional): Set headers manually headers (dict): Additional headers to include or override in the request.
**kwargs (TYPE): Passord to the http request used for filter, sorting. **kwargs (TYPE): Additional arguments to inclde in the request.<method> call.
Returns:
Element
Raises: Raises:
BadRequest: Http error and code :class:`~plexapi.exceptions.BadRequest`: When the response is not in [200, 201]
""" """
url = self.url(path) url = self.url(path)
method = method or self.session.get method = method or self.session.get
@ -138,24 +124,23 @@ class PlexClient(object):
return ElementTree.fromstring(data) if data else None return ElementTree.fromstring(data) if data else None
def sendCommand(self, command, proxy=None, **params): def sendCommand(self, command, proxy=None, **params):
"""Send a command to the client """ Convenience wrapper around :func:`~plexapi.client.PlexClient.query()` to more easily
send simple commands to the client. Returns an :class:`ElementTree` object containing
the response.
Args: Parameters:
command (str): See the commands listed below command (str): Command to be sent in for format '<controller>/<command>'.
proxy (None, optional): Description proxy (bool): Set True to proxy this command through the PlexServer.
**params (dict): Description **params (dict): Additional GET parameters to include with the command.
Returns:
Element
Raises: Raises:
Unsupported: Unsupported clients :class:`~plexapi.exceptions.Unsupported`: When we detect the client doesn't support this capability.
""" """
command = command.strip('/') command = command.strip('/')
controller = command.split('/')[0] controller = command.split('/')[0]
if controller not in self.protocolCapabilities: if controller not in self.protocolCapabilities:
raise Unsupported( raise Unsupported('Client %s does not support the %s controller.' %
'Client %s does not support the %s controller.' % (self.title, controller)) (self.title, controller))
path = '/player/%s%s' % (command, utils.joinArgs(params)) path = '/player/%s%s' % (command, utils.joinArgs(params))
headers = {'X-Plex-Target-Client-Identifier': self.machineIdentifier} headers = {'X-Plex-Target-Client-Identifier': self.machineIdentifier}
self._commandId += 1 self._commandId += 1
@ -167,19 +152,17 @@ class PlexClient(object):
return self.query(path, headers=headers) return self.query(path, headers=headers)
def url(self, path): def url(self, path):
"""Return a full url """ Given a path, this retuns the full PlexClient the PlexServer URL to request.
Args: Parameters:
path (str): Relative path path (str): Relative path to be converted.
Returns:
string: full path to PMS
""" """
if self.token: if self.token:
delim = '&' if '?' in path else '?' delim = '&' if '?' in path else '?'
return '%s%s%sX-Plex-Token=%s' % (self.baseurl, path, delim, self.token) return '%s%s%sX-Plex-Token=%s' % (self.baseurl, path, delim, self.token)
return '%s%s' % (self.baseurl, path) return '%s%s' % (self.baseurl, path)
#---------------------
# Navigation Commands # Navigation Commands
# These commands navigate around the user-interface. # These commands navigate around the user-interface.
def contextMenu(self): def contextMenu(self):
@ -187,62 +170,69 @@ class PlexClient(object):
self.sendCommand('navigation/contextMenu') self.sendCommand('navigation/contextMenu')
def goBack(self): def goBack(self):
"""One step back""" """ Navigate back one position. """
self.sendCommand('navigation/back') self.sendCommand('navigation/back')
def goToHome(self): def goToHome(self):
"""Jump to home screen.""" """ Go directly to the home screen. """
self.sendCommand('navigation/home') self.sendCommand('navigation/home')
def goToMusic(self): def goToMusic(self):
"""Jump to music.""" """ Go directly to the playing music panel. """
self.sendCommand('navigation/music') self.sendCommand('navigation/music')
def moveDown(self): def moveDown(self):
"""One step down.""" """ Move selection down a position. """
self.sendCommand('navigation/moveDown') self.sendCommand('navigation/moveDown')
def moveLeft(self): def moveLeft(self):
""" Move selection left a position. """
self.sendCommand('navigation/moveLeft') self.sendCommand('navigation/moveLeft')
def moveRight(self): def moveRight(self):
""" Move selection right a position. """
self.sendCommand('navigation/moveRight') self.sendCommand('navigation/moveRight')
def moveUp(self): def moveUp(self):
""" Move selection up a position. """
self.sendCommand('navigation/moveUp') self.sendCommand('navigation/moveUp')
def nextLetter(self): def nextLetter(self):
"""Jump to the next letter in the alphabeth.""" """ Jump to next letter in the alphabet. """
self.sendCommand('navigation/nextLetter') self.sendCommand('navigation/nextLetter')
def pageDown(self): def pageDown(self):
""" Move selection down a full page. """
self.sendCommand('navigation/pageDown') self.sendCommand('navigation/pageDown')
def pageUp(self): def pageUp(self):
""" Move selection up a full page. """
self.sendCommand('navigation/pageUp') self.sendCommand('navigation/pageUp')
def previousLetter(self): def previousLetter(self):
""" Jump to previous letter in the alphabet. """
self.sendCommand('navigation/previousLetter') self.sendCommand('navigation/previousLetter')
def select(self): def select(self):
""" Select element at the current position. """
self.sendCommand('navigation/select') self.sendCommand('navigation/select')
def toggleOSD(self): def toggleOSD(self):
""" Toggle the on screen display during playback. """
self.sendCommand('navigation/toggleOSD') self.sendCommand('navigation/toggleOSD')
def goToMedia(self, media, **params): def goToMedia(self, media, **params):
"""Go to a media on the client. """ Navigate directly to the specified media page.
Args: Parameters:
media (str): movie, music, photo media (:class:`~plexapi.media.Media`): Media object to navigate to.
**params (TYPE): Description # todo **params (dict): Additional GET parameters to include with the command.
Raises: Raises:
Unsupported: Description :class:`~plexapi.exceptions.Unsupported`: When no PlexServer specified in this object.
""" """
if not self.server: if not self.server:
raise Unsupported( raise Unsupported('A server must be specified before using this command.')
'A server must be specified before using this command.')
server_url = media.server.baseurl.split(':') server_url = media.server.baseurl.split(':')
self.sendCommand('mirror/details', **dict({ self.sendCommand('mirror/details', **dict({
'machineIdentifier': self.server.machineIdentifier, 'machineIdentifier': self.server.machineIdentifier,
@ -251,170 +241,160 @@ class PlexClient(object):
'key': media.key, 'key': media.key,
}, **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).
def pause(self, mtype): def pause(self, mtype):
"""Pause playback """ Pause the currently playing media type.
Args: Parameters:
mtype (str): music, photo, video mtype (str): Media type to take action against (music, photo, video).
""" """
self.sendCommand('playback/pause', type=mtype) self.sendCommand('playback/pause', type=mtype)
def play(self, mtype): def play(self, mtype):
"""Start playback """ Start playback for the specified media type.
Args: Parameters:
mtype (str): music, photo, video mtype (str): Media type to take action against (music, photo, video).
""" """
self.sendCommand('playback/play', type=mtype) self.sendCommand('playback/play', type=mtype)
def refreshPlayQueue(self, playQueueID, mtype=None): def refreshPlayQueue(self, playQueueID, mtype=None):
"""Summary """ Refresh the specified Playqueue.
Args:
playQueueID (TYPE): Description
mtype (None, optional): photo, video, music
Parameters:
playQueueID (str): Playqueue ID.
mtype (str): Media type to take action against (music, photo, video).
""" """
self.sendCommand( self.sendCommand(
'playback/refreshPlayQueue', playQueueID=playQueueID, type=mtype) 'playback/refreshPlayQueue', playQueueID=playQueueID, type=mtype)
def seekTo(self, offset, mtype=None): def seekTo(self, offset, mtype=None):
"""Seek to a time in a plaback. """ Seek to the specified offset (ms) during playback.
Args:
offset (int): in milliseconds
mtype (None, optional): photo, video, music
Parameters:
offset (int): Position to seek to (milliseconds).
mtype (str): Media type to take action against (music, photo, video).
""" """
self.sendCommand('playback/seekTo', offset=offset, type=mtype) self.sendCommand('playback/seekTo', offset=offset, type=mtype)
def skipNext(self, mtype=None): def skipNext(self, mtype=None):
"""Skip to next """ Skip to the next playback item.
Args: Parameters:
mtype (None, string, optional): photo, video, music mtype (str): Media type to take action against (music, photo, video).
""" """
self.sendCommand('playback/skipNext', type=mtype) self.sendCommand('playback/skipNext', type=mtype)
def skipPrevious(self, mtype=None): def skipPrevious(self, mtype=None):
"""Skip to previous """ Skip to previous playback item.
Args: Parameters:
mtype (None, optional): Description mtype (str): Media type to take action against (music, photo, video).
""" """
self.sendCommand('playback/skipPrevious', type=mtype) self.sendCommand('playback/skipPrevious', type=mtype)
def skipTo(self, key, mtype=None): def skipTo(self, key, mtype=None):
"""Jump to """ Skip to the playback item with the specified key.
Args: Parameters:
key (TYPE): # what is this key (str): Key of the media item to skip to.
mtype (None, optional): photo, video, music mtype (str): Media type to take action against (music, photo, video).
Returns:
TYPE: Description
""" """
# skips to item with matching key
self.sendCommand('playback/skipTo', key=key, type=mtype) self.sendCommand('playback/skipTo', key=key, type=mtype)
def stepBack(self, mtype=None): def stepBack(self, mtype=None):
""" """ Step backward a chunk of time in the current playback item.
Args: Parameters:
mtype (None, optional): photo, video, music mtype (str): Media type to take action against (music, photo, video).
""" """
self.sendCommand('playback/stepBack', type=mtype) self.sendCommand('playback/stepBack', type=mtype)
def stepForward(self, mtype): def stepForward(self, mtype):
"""Summary """ Step forward a chunk of time in the current playback item.
Args: Parameters:
mtype (TYPE): Description mtype (str): Media type to take action against (music, photo, video).
Returns:
TYPE: Description
""" """
self.sendCommand('playback/stepForward', type=mtype) self.sendCommand('playback/stepForward', type=mtype)
def stop(self, mtype): def stop(self, mtype):
"""Stop playback """ Stop the currently playing item.
Args:
mtype (str): video, music, photo
Parameters:
mtype (str): Media type to take action against (music, photo, video).
""" """
self.sendCommand('playback/stop', type=mtype) self.sendCommand('playback/stop', type=mtype)
def setRepeat(self, repeat, mtype): def setRepeat(self, repeat, mtype):
"""Summary """ Enable repeat for the specified playback items.
Args: Parameters:
repeat (int): 0=off, 1=repeatone, 2=repeatall repeat (int): Repeat mode (0=off, 1=repeatone, 2=repeatall).
mtype (TYPE): video, music, photo mtype (str): Media type to take action against (music, photo, video).
""" """
self.setParameters(repeat=repeat, mtype=mtype) self.setParameters(repeat=repeat, mtype=mtype)
def setShuffle(self, shuffle, mtype): def setShuffle(self, shuffle, mtype):
"""Set shuffle """ Enable shuffle for the specified playback items.
Args: Parameters:
shuffle (int): 0=off, 1=on shuffle (int): Shuffle mode (0=off, 1=on)
mtype (TYPE): Description mtype (str): Media type to take action against (music, photo, video).
""" """
self.setParameters(shuffle=shuffle, mtype=mtype) self.setParameters(shuffle=shuffle, mtype=mtype)
def setVolume(self, volume, mtype): def setVolume(self, volume, mtype):
"""Change volume """ Enable volume for the current playback item.
Args: Parameters:
volume (int): 0-100 volume (int): Volume level (0-100).
mtype (TYPE): Description mtype (str): Media type to take action against (music, photo, video).
""" """
self.setParameters(volume=volume, mtype=mtype) self.setParameters(volume=volume, mtype=mtype)
def setAudioStream(self, audioStreamID, mtype): def setAudioStream(self, audioStreamID, mtype):
"""Select a audio stream """ Select the audio stream for the current playback item (only video).
Args: Parameters:
audioStreamID (TYPE): Description audioStreamID (str): ID of the audio stream from the media object.
mtype (str): video, music, photo mtype (str): Media type to take action against (music, photo, video).
""" """
self.setStreams(audioStreamID=audioStreamID, mtype=mtype) self.setStreams(audioStreamID=audioStreamID, mtype=mtype)
def setSubtitleStream(self, subtitleStreamID, mtype): def setSubtitleStream(self, subtitleStreamID, mtype):
"""Select a subtitle """ Select the subtitle stream for the current playback item (only video).
Args: Parameters:
subtitleStreamID (TYPE): Description subtitleStreamID (str): ID of the subtitle stream from the media object.
mtype (str): video, music, photo mtype (str): Media type to take action against (music, photo, video).
""" """
self.setStreams(subtitleStreamID=subtitleStreamID, mtype=mtype) self.setStreams(subtitleStreamID=subtitleStreamID, mtype=mtype)
def setVideoStream(self, videoStreamID, mtype): def setVideoStream(self, videoStreamID, mtype):
"""Summary """ Select the video stream for the current playback item (only video).
Args:
videoStreamID (TYPE): Description
mtype (str): video, music, photo
Parameters:
videoStreamID (str): ID of the video stream from the media object.
mtype (str): Media type to take action against (music, photo, video).
""" """
self.setStreams(videoStreamID=videoStreamID, mtype=mtype) self.setStreams(videoStreamID=videoStreamID, mtype=mtype)
def playMedia(self, media, **params): def playMedia(self, media, **params):
"""Start playback on a media item. """ Start playback of the specified media item.
Args: Parameters:
media (str): movie, music, photo media (:class:`plexapi.media.Media`): Media item to be played back (movie, music, photo).
**params (TYPE): Description **params (TYPE): Additional parameters to include in the request. Useful
to specify things such as offset, audio, or subtitle streams.
Raises: Raises:
Unsupported: Description :class:`~plexapi.exceptions.Unsupported`: When no PlexServer specified in this object.
""" """
if not self.server: if not self.server:
raise Unsupported( raise Unsupported(
@ -430,13 +410,13 @@ class PlexClient(object):
}, **params)) }, **params))
def setParameters(self, volume=None, shuffle=None, repeat=None, mtype=None): def setParameters(self, volume=None, shuffle=None, repeat=None, mtype=None):
"""Set params for the client """ Set multiple playback parameters at once.
Args: Parameters:
volume (None, optional): 0-100 volume (int): Volume level (0-100; optional).
shuffle (None, optional): 0=off, 1=on shuffle (int): Shuffle mode (0=off, 1=on; optional).
repeat (None, optional): 0=off, 1=repeatone, 2=repeatall repeat (int): Repeat mode (0=off, 1=repeatone, 2=repeatall; optional).
mtype (None, optional): music,photo,video mtype (str): Media type to take action against (optional music, photo, video).
""" """
params = {} params = {}
if repeat is not None: if repeat is not None:
@ -449,15 +429,14 @@ class PlexClient(object):
params['type'] = mtype params['type'] = mtype
self.sendCommand('playback/setParameters', **params) self.sendCommand('playback/setParameters', **params)
def setStreams(self, audioStreamID=None, subtitleStreamID=None, def setStreams(self, audioStreamID=None, subtitleStreamID=None, videoStreamID=None, mtype=None):
videoStreamID=None, mtype=None): """ Select multiple playback streams at once.
"""Select streams.
Args: Parameters:
audioStreamID (None, optional): Description audioStreamID (str): ID of the audio stream from the media object.
subtitleStreamID (None, optional): Description subtitleStreamID (str): ID of the subtitle stream from the media object.
videoStreamID (None, optional): Description videoStreamID (str): ID of the video stream from the media object.
mtype (None, optional): music,photo,video mtype (str): Media type to take action against (optional music, photo, video).
""" """
params = {} params = {}
if audioStreamID is not None: if audioStreamID is not None:
@ -470,19 +449,18 @@ class PlexClient(object):
params['type'] = mtype params['type'] = mtype
self.sendCommand('playback/setStreams', **params) self.sendCommand('playback/setStreams', **params)
#-------------------
# Timeline Commands # Timeline Commands
def timeline(self): def timeline(self):
"""Timeline""" """ Poll the current timeline and return the XML response. """
return self.sendCommand('timeline/poll', **{'wait': 1, 'commandID': 4}) return self.sendCommand('timeline/poll', **{'wait': 1, 'commandID': 4})
def isPlayingMedia(self, includePaused=False): def isPlayingMedia(self, includePaused=False):
"""Check timeline if anything is playing """ Returns True if any media is currently playing.
Args: Parameters:
includePaused (bool, optional): Should paused be included includePaused (bool): Set True to treat currently paused items
as playing (optional; default True).
Returns:
bool
""" """
for mediatype in self.timeline(): for mediatype in self.timeline():
if mediatype.get('state') == 'playing': if mediatype.get('state') == 'playing':

View file

@ -1,9 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# flake8:noqa # Python 2/3 compatability
""" # Always try Py3 first
Python 2/3 compatability
Always try Py3 first
"""
import sys import sys
try: try:

View file

@ -1,17 +1,17 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
"""
PlexConfig
Settings are stored in an INI file and can be overridden after import
plexapi by simply setting the value.
"""
from collections import defaultdict from collections import defaultdict
try: from plexapi.compat import ConfigParser
from ConfigParser import ConfigParser # Python2
except ImportError:
from configparser import ConfigParser # Python3
class PlexConfig(ConfigParser): class PlexConfig(ConfigParser):
""" PlexAPI configuration object. Settings are stored in an INI file within the
user's home directory and can be overridden after importing plexapi by simply
setting the value. See the documentation section 'Configuration' for more
details on available options.
Parameters:
path (str): Path of the configuration file to load.
"""
def __init__(self, path): def __init__(self, path):
ConfigParser.__init__(self) ConfigParser.__init__(self)
@ -27,6 +27,13 @@ class PlexConfig(ConfigParser):
raise Exception('Config attr not found: %s' % attr) raise Exception('Config attr not found: %s' % attr)
def get(self, key, default=None, cast=None): def get(self, key, default=None, cast=None):
""" Returns the specified configuration value or <default> if not found.
Parameters:
key (str): Configuration variable to load in the format '<section>.<variable>'.
default: Default value to use if key not found.
cast (func): Cast the value to the specified type before returning.
"""
try: try:
section, name = key.split('.') section, name = key.split('.')
value = self.data.get(section.lower(), {}).get(name.lower(), default) value = self.data.get(section.lower(), {}).get(name.lower(), default)
@ -35,6 +42,7 @@ class PlexConfig(ConfigParser):
return default return default
def _asDict(self): def _asDict(self):
""" Returns all configuration values as a dictionary. """
config = defaultdict(dict) config = defaultdict(dict)
for section in self._sections: for section in self._sections:
for name, value in self._sections[section].items(): for name, value in self._sections[section].items():
@ -44,6 +52,7 @@ class PlexConfig(ConfigParser):
def reset_base_headers(): def reset_base_headers():
""" Convenience function returns a dict of all base X-Plex-* headers for session requests. """
import plexapi import plexapi
return { return {
'X-Plex-Platform': plexapi.X_PLEX_PLATFORM, 'X-Plex-Platform': plexapi.X_PLEX_PLATFORM,

View file

@ -1,23 +1,25 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# flake8:noqa
"""
PlexAPI Exceptions
"""
class PlexApiException(Exception): class PlexApiException(Exception):
""" Base class for all PlexAPI exceptions. """
pass pass
class BadRequest(PlexApiException): class BadRequest(PlexApiException):
""" An invalid request, generally a user error. """
pass pass
class NotFound(PlexApiException): class NotFound(PlexApiException):
""" Request media item or device is not found. """
pass pass
class UnknownType(PlexApiException): class UnknownType(PlexApiException):
""" Unknown library type. """
pass pass
class Unsupported(PlexApiException): class Unsupported(PlexApiException):
""" Unsupported client request. """
pass pass
class Unauthorized(PlexApiException): class Unauthorized(PlexApiException):
""" Invalid username or password. """
pass pass

View file

@ -124,8 +124,8 @@ class MyPlexAccount(object):
password (str): Your MyPlex.tv password. password (str): Your MyPlex.tv password.
Raises: Raises:
Unauthorized: (401) If the username or password are invalid. :class:`~plexapi.exceptions.Unauthorized`: (401) If the username or password are invalid.
BadRequest: If any other errors occured not allowing us to log into MyPlex.tv. :class:`~plexapi.exceptions.BadRequest`: If any other errors occured not allowing us to log into MyPlex.tv.
""" """
if 'X-Plex-Token' in plexapi.BASE_HEADERS: if 'X-Plex-Token' in plexapi.BASE_HEADERS:
del plexapi.BASE_HEADERS['X-Plex-Token'] del plexapi.BASE_HEADERS['X-Plex-Token']
@ -254,7 +254,7 @@ class MyPlexResource(object):
HTTP or HTTPS connection. HTTP or HTTPS connection.
Raises: Raises:
NotFound: When unable to connect to any addresses for this resource. :class:`~plexapi.exceptions.NotFound`: When unable to connect to any addresses for this resource.
""" """
# Sort connections from (https, local) to (http, remote) # Sort connections from (https, local) to (http, remote)
# Only check non-local connections unless we own the resource # Only check non-local connections unless we own the resource
@ -371,7 +371,7 @@ class MyPlexDevice(object):
successful, the PlexClient object is built and returned. successful, the PlexClient object is built and returned.
Raises: Raises:
NotFound: When unable to connect to any addresses for this device. :class:`~plexapi.exceptions.NotFound`: When unable to connect to any addresses for this device.
""" """
# Try connecting to all known resource connections in parellel, but # Try connecting to all known resource connections in parellel, but
# only return the first server (in order) that provides a response. # only return the first server (in order) that provides a response.