Moredocs (#84)

* Docs and speedups.
This commit is contained in:
Hellowlol 2017-01-02 22:06:40 +01:00 committed by GitHub
parent 8686e6e5bb
commit 1075f65bb4
10 changed files with 890 additions and 220 deletions

View file

@ -1,26 +1,58 @@
# -*- coding: utf-8 -*-
"""
PlexAPI Audio
"""
from plexapi import media, utils
from plexapi.utils import Playable, PlexPartialObject
NA = utils.NA
class Audio(PlexPartialObject):
"""Base class for audio.
Attributes:
addedAt (int): int from epoch, datetime.datetime
index (sting): 1
key (str): Fx /library/metadata/102631
lastViewedAt (datetime.datetime): parse int into datetime.datetime.
librarySectionID (int):
listType (str): audio
ratingKey (int): Unique key to identify this item
summary (str): Summery of the artist, track, album
thumb (str): Url to thumb image
title (str): Fx Aerosmith
titleSort (str): Defaults title if None
TYPE (str): overwritten by subclass
type (string, NA): Description
updatedAt (datatime.datetime): parse int to datetime.datetime
viewCount (int): How many time has this item been played
"""
TYPE = None
def __init__(self, server, data, initpath):
"""Used to set the attributes.
Args:
server (Plexserver): PMS your connected to
data (Element): XML reponse from PMS as Element
normally built from server.query
initpath (str): Fx /library/sections/7/all
"""
super(Audio, self).__init__(data, initpath, server)
def _loadData(self, data):
"""Used to set the attributes.
Args:
data (Element): XML reponse from PMS as Element
normally built from server.query
"""
self.listType = 'audio'
self.addedAt = utils.toDatetime(data.attrib.get('addedAt', NA))
self.index = data.attrib.get('index', NA)
self.key = data.attrib.get('key', NA)
self.lastViewedAt = utils.toDatetime(data.attrib.get('lastViewedAt', NA))
self.librarySectionID = data.attrib.get('librarySectionID', NA)
self.ratingKey = data.attrib.get('ratingKey', NA)
self.ratingKey = utils.cast(int, data.attrib.get('ratingKey', NA))
self.summary = data.attrib.get('summary', NA)
self.thumb = data.attrib.get('thumb', NA)
self.title = data.attrib.get('title', NA)
@ -31,55 +63,121 @@ class Audio(PlexPartialObject):
@property
def thumbUrl(self):
"""Return url to thumb image."""
return self.server.url(self.thumb)
def refresh(self):
"""Refresh the metadata."""
self.server.query('%s/refresh' % self.key, method=self.server.session.put)
def section(self):
"""Library section."""
return self.server.library.sectionByID(self.librarySectionID)
@utils.register_libtype
class Artist(Audio):
"""Artist.
Attributes:
art (str): /library/metadata/102631/art/1469310342
countries (list): List of media.County fx [<Country:24200:United.States>]
genres (list): List of media.Genre fx [<Genre:25555:Classic.Rock>]
guid (str): Fx guid com.plexapp.agents.plexmusic://gracenote/artist/05517B8701668D28?lang=en
key (str): Fx /library/metadata/102631
location (str): Filepath
similar (list): List of media.Similar fx [<Similar:25220:Guns.N'.Roses>]
TYPE (str): artist
"""
TYPE = 'artist'
def _loadData(self, data):
"""Used to set the attributes.
Args:
data (Element): XML reponse from PMS as Element
normally built from server.query
"""
Audio._loadData(self, data)
self.art = data.attrib.get('art', NA)
self.guid = data.attrib.get('guid', NA)
self.key = self.key.replace('/children', '') # FIX_BUG_50
self.location = utils.findLocations(data, single=True)
if self.isFullObject():
self.countries = [media.Country(self.server, e) for e in data if e.tag == media.Country.TYPE]
self.genres = [media.Genre(self.server, e) for e in data if e.tag == media.Genre.TYPE]
self.similar = [media.Similar(self.server, e) for e in data if e.tag == media.Similar.TYPE]
if self.isFullObject(): # check if this is needed
self.countries = [media.Country(self.server, e)
for e in data if e.tag == media.Country.TYPE]
self.genres = [media.Genre(self.server, e)
for e in data if e.tag == media.Genre.TYPE]
self.similar = [media.Similar(self.server, e)
for e in data if e.tag == media.Similar.TYPE]
def albums(self):
"""Return a list of Albums by thus artist."""
path = '%s/children' % self.key
return utils.listItems(self.server, path, Album.TYPE)
def album(self, title):
"""Return a album from this artist that match title."""
path = '%s/children' % self.key
return utils.findItem(self.server, path, title)
def tracks(self, watched=None):
"""Return all tracks to this artist.
Args:
watched(None, False, True): Default to None.
Returns:
List: of Track
"""
path = '%s/allLeaves' % self.key
return utils.listItems(self.server, path, watched=watched)
def track(self, title):
"""Return a Track that matches title.
Args:
title (str): Fx song name
Returns:
Track:
"""
path = '%s/allLeaves' % self.key
return utils.findItem(self.server, path, title)
def get(self, title):
"""Alias. See track."""
return self.track(title)
@utils.register_libtype
class Album(Audio):
"""Album.
Attributes:
art (str): Fx /library/metadata/102631/art/1469310342
genres (list): List of media.Genre
key (str): Fx /library/metadata/102632
originallyAvailableAt (TYPE): Description
parentKey (str): /library/metadata/102631
parentRatingKey (int): Fx 1337
parentThumb (TYPE): Relative url to parent thumb image
parentTitle (str): Aerosmith
studio (str):
TYPE (str): album
year (int): 1999
"""
TYPE = 'album'
def _loadData(self, data):
"""Used to set the attributes.
Args:
data (Element): XML reponse from PMS as Element
normally built from server.query
"""
Audio._loadData(self, data)
self.art = data.attrib.get('art', NA)
self.key = self.key.replace('/children', '') # FIX_BUG_50
@ -94,31 +192,87 @@ class Album(Audio):
self.genres = [media.Genre(self.server, e) for e in data if e.tag == media.Genre.TYPE]
def tracks(self, watched=None):
"""Return all tracks to this album.
Args:
watched(None, False, True): Default to None.
Returns:
List: of Track
"""
path = '%s/children' % self.key
return utils.listItems(self.server, path, watched=watched)
def track(self, title):
"""Return a Track that matches title.
Args:
title (str): Fx song name
Returns:
Track:
"""
path = '%s/children' % self.key
return utils.findItem(self.server, path, title)
def get(self, title):
"""Alias. See track."""
return self.track(title)
def artist(self):
"""Return Artist of this album."""
return utils.listItems(self.server, self.parentKey)[0]
def watched(self):
"""Return Track that is lisson on."""
return self.tracks(watched=True)
def unwatched(self):
"""Return Track that is not lisson on."""
return self.tracks(watched=False)
@utils.register_libtype
class Track(Audio, Playable):
"""Track.
Attributes:
art (str): Relative path fx /library/metadata/102631/art/1469310342
chapterSource (TYPE): Description
duration (TYPE): Description
grandparentArt (str): Relative path
grandparentKey (str): Relative path Fx /library/metadata/102631
grandparentRatingKey (TYPE): Description
grandparentThumb (str): Relative path to Artist thumb img
grandparentTitle (str): Aerosmith
guid (TYPE): Description
media (list): List of media.Media
moods (list): List of media.Moods
originalTitle (str): Some track title
parentIndex (int): 1
parentKey (str): Relative path Fx /library/metadata/102632
parentRatingKey (int): 1337
parentThumb (str): Relative path to Album thumb
parentTitle (str): Album title
player (None): #TODO
primaryExtraKey (TYPE): #TODO
ratingCount (int): 10
sessionKey (int): Description
transcodeSession (None):
TYPE (str): track
username (str): username@mail.com
viewOffset (int): 100
year (int): 1999
"""
TYPE = 'track'
def _loadData(self, data):
"""Used to set the attributes
Args:
data (Element): Usually built from server.query
"""
Audio._loadData(self, data)
Playable._loadData(self, data)
self.art = data.attrib.get('art', NA)
@ -140,9 +294,11 @@ class Track(Audio, Playable):
self.ratingCount = utils.cast(int, data.attrib.get('ratingCount', NA))
self.viewOffset = utils.cast(int, data.attrib.get('viewOffset', 0))
self.year = utils.cast(int, data.attrib.get('year', NA))
if self.isFullObject():
self.moods = [media.Mood(self.server, e) for e in data if e.tag == media.Mood.TYPE]
self.media = [media.Media(self.server, e, self.initpath, self) for e in data if e.tag == media.Media.TYPE]
if self.isFullObject(): # check me
self.moods = [media.Mood(self.server, e)
for e in data if e.tag == media.Mood.TYPE]
self.media = [media.Media(self.server, e, self.initpath, self)
for e in data if e.tag == media.Media.TYPE]
# data for active sessions and history
self.sessionKey = utils.cast(int, data.attrib.get('sessionKey', NA))
self.username = utils.findUsername(data)
@ -151,10 +307,13 @@ class Track(Audio, Playable):
@property
def thumbUrl(self):
"""Return url to thumb image."""
return self.server.url(self.parentThumb)
def album(self):
"""Return this track's Album."""
return utils.listItems(self.server, self.parentKey)[0]
def artist(self):
"""Return this track's Artist."""
return utils.listItems(self.server, self.grandparentKey)[0]

View file

@ -13,27 +13,27 @@ from xml.etree import ElementTree
class PlexClient(object):
"""Main class for interacting with a client
"""Main class for interacting with a client.
Attributes:
baseurl (string): http adress for the client
baseurl (str): http adress for the client
device (None): Description
deviceClass (sting): pc, phone
machineIdentifier (string): uuid fx 5471D9EA-1467-4051-9BE7-FCBDF490ACE3
machineIdentifier (str): uuid fx 5471D9EA-1467-4051-9BE7-FCBDF490ACE3
model (TYPE): Description
platform (TYPE): Description
platformVersion (TYPE): Description
product (string): plex for ios
protocol (string): plex
product (str): plex for ios
protocol (str): plex
protocolCapabilities (list): List of what client can do
protocolVersion (string): 1
protocolVersion (str): 1
server (plexapi.server.Plexserver): PMS your connected to
session (None or requests.Session): Add your own session object to cache stuff
state (None): Description
title (string): fx Johns Iphone
token (string): X-Plex-Token, using for authenication with PMS
vendor (string): Description
version (string): fx. 4.6
title (str): fx Johns Iphone
token (str): X-Plex-Token, using for authenication with PMS
vendor (str): Description
version (str): fx. 4.6
"""
def __init__(self, baseurl, token=None, session=None, server=None, data=None):
@ -115,7 +115,7 @@ class PlexClient(object):
"""Used to fetch relative paths to pms.
Args:
path (string): Relative path
path (str): Relative path
method (None, optional): requests.post etc
headers (None, optional): Set headers manually
**kwargs (TYPE): Passord to the http request used for filter, sorting.
@ -141,7 +141,7 @@ class PlexClient(object):
"""Send a command to the client
Args:
command (string): See the commands listed below
command (str): See the commands listed below
proxy (None, optional): Description
**params (dict): Description
@ -170,7 +170,7 @@ class PlexClient(object):
"""Return a full url
Args:
path (string): Relative path
path (str): Relative path
Returns:
string: full path to PMS
@ -234,7 +234,7 @@ class PlexClient(object):
"""Go to a media on the client.
Args:
media (string): movie, music, photo
media (str): movie, music, photo
**params (TYPE): Description # todo
Raises:
@ -261,7 +261,7 @@ class PlexClient(object):
"""Pause playback
Args:
mtype (string): music, photo, video
mtype (str): music, photo, video
"""
self.sendCommand('playback/pause', type=mtype)
@ -269,7 +269,7 @@ class PlexClient(object):
"""Start playback
Args:
mtype (string): music, photo, video
mtype (str): music, photo, video
"""
self.sendCommand('playback/play', type=mtype)
@ -346,7 +346,7 @@ class PlexClient(object):
"""Stop playback
Args:
mtype (string): video, music, photo
mtype (str): video, music, photo
"""
self.sendCommand('playback/stop', type=mtype)
@ -383,7 +383,7 @@ class PlexClient(object):
Args:
audioStreamID (TYPE): Description
mtype (string): video, music, photo
mtype (str): video, music, photo
"""
self.setStreams(audioStreamID=audioStreamID, mtype=mtype)
@ -392,7 +392,7 @@ class PlexClient(object):
Args:
subtitleStreamID (TYPE): Description
mtype (string): video, music, photo
mtype (str): video, music, photo
"""
self.setStreams(subtitleStreamID=subtitleStreamID, mtype=mtype)
@ -401,7 +401,7 @@ class PlexClient(object):
Args:
videoStreamID (TYPE): Description
mtype (string): video, music, photo
mtype (str): video, music, photo
"""
self.setStreams(videoStreamID=videoStreamID, mtype=mtype)
@ -410,7 +410,7 @@ class PlexClient(object):
"""Start playback on a media item.
Args:
media (string): movie, music, photo
media (str): movie, music, photo
**params (TYPE): Description
Raises:

View file

@ -1,22 +1,68 @@
# -*- coding: utf-8 -*-
"""
PlexAPI MyPlex
"""
import plexapi, requests
import sys
if sys.version_info <= (3, 3):
try:
from xml.etree import cElementTree as ElementTree
except ImportError:
from xml.etree import ElementTree
else:
# py 3.3 and above selects the fastest automatically
from xml.etree import ElementTree
from requests.status_codes import _codes as codes
import plexapi
import requests
from plexapi import TIMEOUT, log, utils
from plexapi.exceptions import BadRequest, NotFound, Unauthorized
from plexapi.client import PlexClient
from plexapi.server import PlexServer
from requests.status_codes import _codes as codes
from xml.etree import ElementTree
# Your personal MyPlex account and profile information
class MyPlexAccount(object):
"""Your personal MyPlex account and profile information
Attributes:
authenticationToken (TYPE): Description
BASEURL (str): Description
certificateVersion (TYPE): Description
cloudSyncDevice (TYPE): Description
email (TYPE): Description
entitlements (TYPE): Description
guest (TYPE): Description
home (TYPE): Description
homeSize (TYPE): Description
id (TYPE): Description
locale (TYPE): Description
mailing_list_status (TYPE): Description
maxHomeSize (TYPE): Description
queueEmail (TYPE): Description
queueUid (TYPE): Description
restricted (TYPE): Description
roles (TYPE): Description
scrobbleTypes (TYPE): Description
secure (TYPE): Description
SIGNIN (str): Description
subscriptionActive (TYPE): Description
subscriptionFeatures (TYPE): Description
subscriptionPlan (TYPE): Description
subscriptionStatus (TYPE): Description
thumb (TYPE): Description
title (TYPE): Description
username (TYPE): Description
uuid (TYPE): Description
"""
BASEURL = 'https://plex.tv/users/account'
SIGNIN = 'https://my.plexapp.com/users/sign_in.xml'
def __init__(self, data, initpath=None):
"""Sets the attrs.
Args:
data (Element): XML response from PMS as a Element
initpath (string, optional): relative path.
"""
self.authenticationToken = data.attrib.get('authenticationToken')
self.certificateVersion = data.attrib.get('certificateVersion')
self.cloudSyncDevice = data.attrib.get('cloudSyncDevice')
@ -47,37 +93,92 @@ class MyPlexAccount(object):
self.entitlements = None
def __repr__(self):
"""Pretty print."""
return '<%s:%s:%s>' % (self.__class__.__name__, self.id, self.username.encode('utf8'))
def devices(self):
"""Return a all devices connected to the plex account.
Returns:
list: of MyPlexDevice
"""
return _listItems(MyPlexDevice.BASEURL, self.authenticationToken, MyPlexDevice)
def device(self, name):
"""Return a device wth a matching name.
Args:
name (str): Name to match against.
Returns:
class: MyPlexDevice
"""
return _findItem(self.devices(), name)
def resources(self):
"""Resources.
Returns:
List: of MyPlexResource
"""
return _listItems(MyPlexResource.BASEURL, self.authenticationToken, MyPlexResource)
def resource(self, name):
"""Find resource ny name.
Args:
name (str): to find
Returns:
class: MyPlexResource
"""
return _findItem(self.resources(), name)
def users(self):
"""List of users.
Returns:
List: of MyPlexuser
"""
return _listItems(MyPlexUser.BASEURL, self.authenticationToken, MyPlexUser)
def user(self, email):
"""Find a user by email.
Args:
email (str): Username to match against.
Returns:
class: User
"""
return _findItem(self.users(), email, ['username', 'email'])
@classmethod
def signin(cls, username, password):
"""Summary
Args:
username (str): username
password (str): password
Returns:
class: MyPlexAccount
Raises:
BadRequest: (HTTPCODE) http codename
Unauthorized: (HTTPCODE) http codename
"""
if 'X-Plex-Token' in plexapi.BASE_HEADERS:
del plexapi.BASE_HEADERS['X-Plex-Token']
auth = (username, password)
log.info('POST %s', cls.SIGNIN)
response = requests.post(cls.SIGNIN, headers=plexapi.BASE_HEADERS, auth=auth, timeout=TIMEOUT)
response = requests.post(
cls.SIGNIN, headers=plexapi.BASE_HEADERS, auth=auth, timeout=TIMEOUT)
if response.status_code != requests.codes.created:
codename = codes.get(response.status_code)[0]
if response.status_code == 401:
raise Unauthorized('(%s) %s' % (response.status_code, codename))
raise Unauthorized('(%s) %s' %
(response.status_code, codename))
raise BadRequest('(%s) %s' % (response.status_code, codename))
data = ElementTree.fromstring(response.text.encode('utf8'))
return cls(data, cls.SIGNIN)
@ -86,10 +187,39 @@ class MyPlexAccount(object):
# Not to be confused with the MyPlexAccount, this represents
# non-signed in users such as friends and linked accounts.
class MyPlexUser(object):
"""Class to other users.
Attributes:
allowCameraUpload (bool): True if this user can upload images
allowChannels (bool): True if this user has access to channels
allowSync (bool): True if this user can sync
BASEURL (str): Description
email (str): user@gmail.com
filterAll (str): Description
filterMovies (str): Description
filterMusic (str): Description
filterPhotos (str): Description
filterTelevision (str): Description
home (bool):
id (int): 1337
protected (False): Is this if ssl? check it
recommendationsPlaylistId (str): Description
restricted (str): fx 0
thumb (str): Link to the users avatar
title (str): Hellowlol
username (str): Hellowlol
"""
BASEURL = 'https://plex.tv/api/users/'
def __init__(self, data, initpath=None):
self.allowCameraUpload = utils.cast(bool, data.attrib.get('allowCameraUpload'))
"""Summary
Args:
data (Element): XML repsonse as Element
initpath (None, optional): Relative url str
"""
self.allowCameraUpload = utils.cast(
bool, data.attrib.get('allowCameraUpload'))
self.allowChannels = utils.cast(bool, data.attrib.get('allowChannels'))
self.allowSync = utils.cast(bool, data.attrib.get('allowSync'))
self.email = data.attrib.get('email')
@ -101,20 +231,48 @@ class MyPlexUser(object):
self.home = utils.cast(bool, data.attrib.get('home'))
self.id = utils.cast(int, data.attrib.get('id'))
self.protected = utils.cast(bool, data.attrib.get('protected'))
self.recommendationsPlaylistId = data.attrib.get('recommendationsPlaylistId')
self.recommendationsPlaylistId = data.attrib.get(
'recommendationsPlaylistId')
self.restricted = data.attrib.get('restricted')
self.thumb = data.attrib.get('thumb')
self.title = data.attrib.get('title')
self.username = data.attrib.get('username')
def __repr__(self):
"""Pretty repr."""
return '<%s:%s:%s>' % (self.__class__.__name__, self.id, self.username)
class MyPlexResource(object):
"""Summary
Attributes:
accessToken (str): This resource accesstoken.
BASEURL (TYPE): Description
clientIdentifier (str): 1f2fe128794fd...
connections (list): of ResourceConnection
createdAt (datetime): Description
device (str): pc
home (None): Dunno wtf this can me
lastSeenAt (datetime): Description
name (str): Pretty name fx S-PC
owned (bool): True if this is your own.
platform (str): Windows
platformVersion (str): fx. 6.1 (Build 7601)
presence (bool): True if online
product (str): Plex Media Server
productVersion (str): 1.3.3.3148-b38628e
provides (str): fx server
synced (bool): Description
"""
BASEURL = 'https://plex.tv/api/resources?includeHttps=1'
def __init__(self, data):
"""Summary
Args:
data (Element): XML response as Element
"""
self.name = data.attrib.get('name')
self.accessToken = data.attrib.get('accessToken')
self.product = data.attrib.get('product')
@ -130,16 +288,31 @@ class MyPlexResource(object):
self.home = utils.cast(bool, data.attrib.get('home'))
self.synced = utils.cast(bool, data.attrib.get('synced'))
self.presence = utils.cast(bool, data.attrib.get('presence'))
self.connections = [ResourceConnection(elem) for elem in data if elem.tag == 'Connection']
self.connections = [ResourceConnection(
elem) for elem in data if elem.tag == 'Connection']
def __repr__(self):
"""Pretty repr."""
return '<%s:%s>' % (self.__class__.__name__, self.name.encode('utf8'))
def connect(self, ssl=None):
"""Connect.
Args:
ssl (None, optional): Use ssl.
Returns:
class: Plexserver
Raises:
NotFound: Unable to connect to resource: name
"""
# Sort connections from (https, local) to (http, remote)
# Only check non-local connections unless we own the resource
forcelocal = lambda c: self.owned or c.local
connections = sorted(self.connections, key=lambda c:c.local, reverse=True)
connections = sorted(
self.connections, key=lambda c: c.local, reverse=True)
https = [c.uri for c in self.connections if forcelocal(c)]
http = [c.httpuri for c in self.connections if forcelocal(c)]
connections = https + http
@ -148,26 +321,56 @@ class MyPlexResource(object):
listargs = [[c] for c in connections]
results = utils.threaded(self._connect, listargs)
# At this point we have a list of result tuples containing (url, token, PlexServer)
# or (url, token, None) in the case a connection could not be established.
# or (url, token, None) in the case a connection could not be
# established.
for url, token, result in results:
okerr = 'OK' if result else 'ERR'
log.info('Testing resource connection: %s?X-Plex-Token=%s %s', url, token, okerr)
results = list(filter(None, [r[2] for r in results if r]))
log.info(
'Testing resource connection: %s?X-Plex-Token=%s %s', url, token, okerr)
results = [r[2] for r in results if r and r is not None]
if not results:
raise NotFound('Unable to connect to resource: %s' % self.name)
log.info('Connecting to server: %s?X-Plex-Token=%s', results[0].baseurl, results[0].token)
log.info('Connecting to server: %s?X-Plex-Token=%s',
results[0].baseurl, results[0].token)
return results[0]
def _connect(self, url, results, i):
"""Connect.
Args:
url (str): url to the resource
results (TYPE): Description
i (TYPE): Description
Returns:
TYPE: Description
"""
try:
results[i] = (url, self.accessToken, PlexServer(url, self.accessToken))
results[i] = (url, self.accessToken,
PlexServer(url, self.accessToken))
except NotFound:
results[i] = (url, self.accessToken, None)
class ResourceConnection(object):
"""ResourceConnection.
Attributes:
address (str): Local ip adress
httpuri (str): Full local address
local (bool): True if local
port (int): 32400
protocol (str): http or https
uri (str): External adress
"""
def __init__(self, data):
"""Set attrs.
Args:
data (Element): XML response as Element from PMS.
"""
self.protocol = data.attrib.get('protocol')
self.address = data.attrib.get('address')
self.port = utils.cast(int, data.attrib.get('port'))
@ -176,13 +379,42 @@ class ResourceConnection(object):
self.httpuri = 'http://%s:%s' % (self.address, self.port)
def __repr__(self):
"""Pretty repr."""
return '<%s:%s>' % (self.__class__.__name__, self.uri.encode('utf8'))
class MyPlexDevice(object):
"""Device connected.
Attributes:
BASEURL (str): Plex.tv XML device url
clientIdentifier (str): 0x685d43d...
connections (list):
device (str): fx Windows
id (str): 123
model (str):
name (str): fx Computername
platform (str): Windows
platformVersion (str): Fx 8
product (str): Fx PlexAPI
productVersion (string): 2.0.2
provides (str): fx controller
publicAddress (str): Public ip address
screenDensity (str): Description
screenResolution (str): Description
token (str): Auth token
vendor (str): Description
version (str): fx 2.0.2
"""
BASEURL = 'https://plex.tv/devices.xml'
def __init__(self, data):
"""Set attrs
Args:
data (Element): XML response as Element from PMS
"""
self.name = data.attrib.get('name')
self.publicAddress = data.attrib.get('publicAddress')
self.product = data.attrib.get('product')
@ -199,36 +431,75 @@ class MyPlexDevice(object):
self.token = data.attrib.get('token')
self.screenResolution = data.attrib.get('screenResolution')
self.screenDensity = data.attrib.get('screenDensity')
self.connections = [connection.attrib.get('uri') for connection in data.iter('Connection')]
self.connections = [connection.attrib.get(
'uri') for connection in data.iter('Connection')]
def __repr__(self):
"""Pretty repr."""
return '<%s:%s:%s>' % (self.__class__.__name__, self.name.encode('utf8'), self.product.encode('utf8'))
def connect(self, ssl=None):
"""Connect to the first server.
Args:
ssl (None, optional): Use SSL?
Returns:
TYPE: Plexserver
Raises:
NotFound: Unable to connect to resource: name
"""
# Try connecting to all known resource connections in parellel, but
# only return the first server (in order) that provides a response.
listargs = [[c] for c in self.connections]
results = utils.threaded(self._connect, listargs)
# At this point we have a list of result tuples containing (url, token, PlexServer)
# or (url, token, None) in the case a connection could not be established.
# or (url, token, None) in the case a connection could not be
# established.
for url, token, result in results:
okerr = 'OK' if result else 'ERR'
log.info('Testing device connection: %s?X-Plex-Token=%s %s', url, token, okerr)
results = list(filter(None, [r[2] for r in results if r]))
log.info('Testing device connection: %s?X-Plex-Token=%s %s',
url, token, okerr)
results = [r[2] for r in results if r and r[2] is not None]
if not results:
raise NotFound('Unable to connect to resource: %s' % self.name)
log.info('Connecting to server: %s?X-Plex-Token=%s', results[0].baseurl, results[0].token)
log.info('Connecting to server: %s?X-Plex-Token=%s',
results[0].baseurl, results[0].token)
return results[0]
def _connect(self, url, results, i):
"""Summary
Args:
url (TYPE): Description
results (TYPE): Description
i (TYPE): Description
Returns:
TYPE: Description
"""
try:
results[i] = (url, self.token, PlexClient(url, self.token))
except NotFound as err:
print(err)
results[i] = (url, self.token, None)
def _findItem(items, value, attrs=None):
"""Simple helper to find something using attrs
Args:
items (cls): list of Object to get the attrs from
value (str): value to match against
attrs (None, optional): attr to match against value.
Returns:
TYPE: Description
Raises:
NotFound: Description
"""
attrs = attrs or ['name']
for item in items:
for attr in attrs:
@ -238,6 +509,16 @@ def _findItem(items, value, attrs=None):
def _listItems(url, token, cls):
"""Helper that builds list of classes from a XML response.
Args:
url (str): Description
token (str): Description
cls (class): Class to initate
Returns:
List: of classes
"""
headers = plexapi.BASE_HEADERS
headers['X-Plex-Token'] = token
log.info('GET %s?X-Plex-Token=%s', url, token)

View file

@ -1,6 +1,9 @@
# -*- coding: utf-8 -*-
"""
PlexPhoto
Attributes:
NA (TYPE): Description
"""
from plexapi import media, utils
from plexapi.utils import PlexPartialObject
@ -9,12 +12,46 @@ NA = utils.NA
@utils.register_libtype
class Photoalbum(PlexPartialObject):
"""Summary
Attributes:
addedAt (TYPE): Description
art (TYPE): Description
composite (TYPE): Description
guid (TYPE): Description
index (TYPE): Description
key (TYPE): Description
librarySectionID (TYPE): Description
listType (str): Description
ratingKey (TYPE): Description
summary (TYPE): Description
thumb (TYPE): Description
title (TYPE): Description
TYPE (str): Description
type (TYPE): Description
updatedAt (TYPE): Description
"""
TYPE = 'photoalbum'
def __init__(self, server, data, initpath):
"""Summary
Args:
server (TYPE): Description
data (TYPE): Description
initpath (TYPE): Description
"""
super(Photoalbum, self).__init__(data, initpath, server)
def _loadData(self, data):
"""Summary
Args:
data (TYPE): Description
Returns:
TYPE: Description
"""
self.listType = 'photo'
self.addedAt = utils.toDatetime(data.attrib.get('addedAt', NA))
self.art = data.attrib.get('art', NA)
@ -31,30 +68,84 @@ class Photoalbum(PlexPartialObject):
self.updatedAt = utils.toDatetime(data.attrib.get('updatedAt', NA))
def photos(self):
"""Summary
Returns:
TYPE: Description
"""
path = '/library/metadata/%s/children' % self.ratingKey
return utils.listItems(self.server, path, Photo.TYPE)
def photo(self, title):
"""Summary
Args:
title (TYPE): Description
Returns:
TYPE: Description
"""
path = '/library/metadata/%s/children' % self.ratingKey
return utils.findItem(self.server, path, title)
def section(self):
"""Summary
Returns:
TYPE: Description
"""
return self.server.library.sectionByID(self.librarySectionID)
@utils.register_libtype
class Photo(PlexPartialObject):
"""Summary
Attributes:
addedAt (TYPE): Description
index (TYPE): Description
key (TYPE): Description
listType (str): Description
media (TYPE): Description
originallyAvailableAt (TYPE): Description
parentKey (TYPE): Description
parentRatingKey (TYPE): Description
ratingKey (TYPE): Description
summary (TYPE): Description
thumb (TYPE): Description
title (TYPE): Description
TYPE (str): Description
type (TYPE): Description
updatedAt (TYPE): Description
year (TYPE): Description
"""
TYPE = 'photo'
def __init__(self, server, data, initpath):
"""Summary
Args:
server (TYPE): Description
data (TYPE): Description
initpath (TYPE): Description
"""
super(Photo, self).__init__(data, initpath, server)
def _loadData(self, data):
"""Summary
Args:
data (TYPE): Description
Returns:
TYPE: Description
"""
self.listType = 'photo'
self.addedAt = utils.toDatetime(data.attrib.get('addedAt', NA))
self.index = utils.cast(int, data.attrib.get('index', NA))
self.key = data.attrib.get('key', NA)
self.originallyAvailableAt = utils.toDatetime(data.attrib.get('originallyAvailableAt', NA), '%Y-%m-%d')
self.originallyAvailableAt = utils.toDatetime(
data.attrib.get('originallyAvailableAt', NA), '%Y-%m-%d')
self.parentKey = data.attrib.get('parentKey', NA)
self.parentRatingKey = data.attrib.get('parentRatingKey', NA)
self.ratingKey = data.attrib.get('ratingKey', NA)
@ -65,10 +156,21 @@ class Photo(PlexPartialObject):
self.updatedAt = utils.toDatetime(data.attrib.get('updatedAt', NA))
self.year = utils.cast(int, data.attrib.get('year', NA))
if self.isFullObject():
self.media = [media.Media(self.server, e, self.initpath, self) for e in data if e.tag == media.Media.TYPE]
self.media = [media.Media(self.server, e, self.initpath, self)
for e in data if e.tag == media.Media.TYPE]
def photoalbum(self):
"""Summary
Returns:
TYPE: Description
"""
return utils.listItems(self.server, self.parentKey)[0]
def section(self):
"""Summary
Returns:
TYPE: Description
"""
return self.server.library.sectionByID(self.photoalbum().librarySectionID)

View file

@ -14,9 +14,22 @@ class Playlist(PlexPartialObject, Playable):
TYPE = 'playlist'
def __init__(self, server, data, initpath):
"""Playlist stuff.
Args:
server (Plexserver): The PMS server your connected to
data (Element): Element built from server.query
initpath (str): Relativ path fx /library/sections/1/all
"""
super(Playlist, self).__init__(data, initpath, server)
def _loadData(self, data):
"""Used to set the attributes
Args:
data (Element): Usually built from server.query
"""
Playable._loadData(self, data)
self.addedAt = toDatetime(data.attrib.get('addedAt', NA))
self.composite = data.attrib.get('composite', NA) # url to thumbnail
@ -27,7 +40,7 @@ class Playlist(PlexPartialObject, Playable):
self.key = self.key.replace('/items', '') if self.key else self.key # FIX_BUG_50
self.leafCount = cast(int, data.attrib.get('leafCount', NA))
self.playlistType = data.attrib.get('playlistType', NA)
self.ratingKey = data.attrib.get('ratingKey', NA)
self.ratingKey = cast(int, data.attrib.get('ratingKey', NA))
self.smart = cast(bool, data.attrib.get('smart', NA))
self.summary = data.attrib.get('summary', NA)
self.title = data.attrib.get('title', NA)
@ -35,10 +48,12 @@ class Playlist(PlexPartialObject, Playable):
self.updatedAt = toDatetime(data.attrib.get('updatedAt', NA))
def items(self):
"""Return all items in the playlist."""
path = '%s/items' % self.key
return utils.listItems(self.server, path)
def addItems(self, items):
"""Add items to a playlist."""
if not isinstance(items, (list, tuple)):
items = [items]
ratingKeys = []
@ -54,23 +69,29 @@ class Playlist(PlexPartialObject, Playable):
return self.server.query(path, method=self.server.session.put)
def removeItem(self, item):
"""Remove a file from a playlist."""
path = '%s/items/%s' % (self.key, item.playlistItemID)
return self.server.query(path, method=self.server.session.delete)
def moveItem(self, item, after=None):
"""Move a to a new position in playlist."""
path = '%s/items/%s/move' % (self.key, item.playlistItemID)
if after: path += '?after=%s' % after.playlistItemID
if after:
path += '?after=%s' % after.playlistItemID
return self.server.query(path, method=self.server.session.put)
def edit(self, title=None, summary=None):
"""Edit playlist."""
path = '/library/metadata/%s%s' % (self.ratingKey, utils.joinArgs({'title':title, 'summary':summary}))
return self.server.query(path, method=self.server.session.put)
def delete(self):
"""Delete playlist."""
return self.server.query(self.key, method=self.server.session.delete)
@classmethod
def create(cls, server, title, items):
"""Create a playlist."""
if not isinstance(items, (list, tuple)):
items = [items]
ratingKeys = []

View file

@ -1,28 +1,64 @@
# -*- coding: utf-8 -*-
"""
PlexAPI Play PlayQueues
"""
import plexapi, requests
import plexapi
import requests
from plexapi import utils
class PlayQueue(object):
"""Summary
Attributes:
identifier (TYPE): Description
initpath (TYPE): Description
items (TYPE): Description
mediaTagPrefix (TYPE): Description
mediaTagVersion (TYPE): Description
playQueueID (TYPE): Description
playQueueSelectedItemID (TYPE): Description
playQueueSelectedItemOffset (TYPE): Description
playQueueTotalCount (TYPE): Description
playQueueVersion (TYPE): Description
server (TYPE): Description
"""
def __init__(self, server, data, initpath):
"""Summary
Args:
server (TYPE): Description
data (TYPE): Description
initpath (TYPE): Description
"""
self.server = server
self.initpath = initpath
self.identifier = data.attrib.get('identifier')
self.mediaTagPrefix = data.attrib.get('mediaTagPrefix')
self.mediaTagVersion = data.attrib.get('mediaTagVersion')
self.playQueueID = data.attrib.get('playQueueID')
self.playQueueSelectedItemID = data.attrib.get('playQueueSelectedItemID')
self.playQueueSelectedItemOffset = data.attrib.get('playQueueSelectedItemOffset')
self.playQueueSelectedItemID = data.attrib.get(
'playQueueSelectedItemID')
self.playQueueSelectedItemOffset = data.attrib.get(
'playQueueSelectedItemOffset')
self.playQueueTotalCount = data.attrib.get('playQueueTotalCount')
self.playQueueVersion = data.attrib.get('playQueueVersion')
self.items = [utils.buildItem(server, elem, initpath) for elem in data]
@classmethod
def create(cls, server, item, shuffle=0, repeat=0, includeChapters=1, includeRelated=1):
"""Summary
Args:
server (TYPE): Description
item (TYPE): Description
shuffle (int, optional): Description
repeat (int, optional): Description
includeChapters (int, optional): Description
includeRelated (int, optional): Description
Returns:
TYPE: Description
"""
args = {}
args['includeChapters'] = includeChapters
args['includeRelated'] = includeRelated

View file

@ -1,10 +1,14 @@
# -*- coding: utf-8 -*-
import sys
"""
PlexServer
"""
from xml.etree import ElementTree
if sys.version_info <= (3, 3):
try:
from xml.etree import cElementTree as ElementTree
except ImportError:
from xml.etree import ElementTree
else:
# py 3.3 and above selects the fastest automatically
from xml.etree import ElementTree
import requests
from requests.status_codes import _codes as codes
@ -32,29 +36,29 @@ class PlexServer(object):
See test/example.py for more examples
Attributes:
baseurl (string): Base url for PMS
friendlyName (string): Pretty name for PMS
machineIdentifier (string): uuid for PMS
myPlex (TYPE): Description
myPlexMappingState (TYPE): Description
myPlexSigninState (TYPE): Description
myPlexSubscription (TYPE): Description
myPlexUsername (string): Description
platform (string): Description
platformVersion (string): Description
baseurl (str): Base url for PMS. Fx http://10.0.0.97:32400
friendlyName (str): Pretty name for PMS fx s-PC
machineIdentifier (str): uuid for PMS
myPlex (bool): Description
myPlexMappingState (str): fx mapped
myPlexSigninState (str): fx ok
myPlexSubscription (str): 1
myPlexUsername (str): username@email.com
platform (str): The platform PMS is running on.
platformVersion (str): fx 6.1 (Build 7601)
session (requests.Session, optinal): Add your own session object for caching
token (string): X-Plex-Token, using for authenication with PMS
token (str): X-Plex-Token, using for authenication with PMS
transcoderActiveVideoSessions (int): How any active video sessions
updatedAt (int): Last updated at
version (TYPE): Description
updatedAt (int): Last updated at as epoch
version (str): fx 1.3.2.3112-1751929
"""
def __init__(self, baseurl='http://localhost:32400', token=None, session=None):
"""
Args:
baseurl (string): Base url for PMS
token (string): X-Plex-Token, using for authenication with PMS
baseurl (str): Base url for PMS
token (str): X-Plex-Token, using for authenication with PMS
session (requests.Session, optional): Use your own session object if you want
to cache the http responses from PMS
"""
@ -96,11 +100,12 @@ class PlexServer(object):
return self._library
def account(self):
"""Returns Account."""
data = self.query('/myplex/account')
return Account(self, data)
def clients(self):
"""Query PMS for all clients connected to PMS
"""Query PMS for all clients connected to PMS.
Returns:
list: of Plexclient connnected to PMS
@ -114,13 +119,13 @@ class PlexServer(object):
return items
def client(self, name):
"""Querys PMS for all clients connected to PMS
"""Querys PMS for all clients connected to PMS.
Returns:
Plexclient
Args:
name (string): client title, John's Iphone
name (str): client title, John's Iphone
Raises:
NotFound: Unknown client name Betty
@ -134,7 +139,7 @@ class PlexServer(object):
raise NotFound('Unknown client name: %s' % name)
def createPlaylist(self, title, items):
"""Create a playlist
"""Create a playlist.
Returns:
Playlist
@ -165,10 +170,10 @@ class PlexServer(object):
"""Returns a playlist with a given name or raise NotFound.
Args:
title (string): title of the playlist
title (str): title of the playlist
Raises:
NotFound: Description
NotFound: Invalid playlist title: title
"""
for item in self.playlists():
if item.title == title:
@ -178,16 +183,16 @@ class PlexServer(object):
def query(self, path, method=None, headers=None, **kwargs):
"""Main method used to handle http connection to PMS.
encodes the response to utf-8 and parses the xml returned
from PMS
from PMS into a Element
Args:
path (sting): relative path to PMS, fx /search?query=HELLO
method (None, optional): requests.method, fx requests.put
path (str): relative path to PMS, fx /search?query=HELLO
method (None, optional): requests.method, requests.put
headers (None, optional): Headers that will be passed to PMS
**kwargs (dict): Used for filter and sorting.
Raises:
BadRequest: Description
BadRequest: fx (404) Not Found
Returns:
xml.etree.ElementTree.Element or None
@ -209,8 +214,8 @@ class PlexServer(object):
"""Searching within a library section is much more powerful.
Args:
query (string): Search string
mediatype (string, optional): Limit your search to a media type.
query (str): Search str
mediatype (str, optional): Limit your search to a media type.
Returns:
List
@ -233,28 +238,32 @@ class PlexServer(object):
class Account(object):
"""This is the locally cached MyPlex account information. The properties provided don't match
the myplex.MyPlexAccount object very well. I believe this is here because access to myplex
is not required to get basic plex information.
"""This is the locally cached MyPlex account information.
The properties provided don't matchthe myplex.MyPlexAccount object very well.
I believe this is here because access to myplexis not required
to get basic plex information.
Attributes:
authToken (sting): X-Plex-Token, using for authenication with PMS
mappingError (TYPE): Description
mappingErrorMessage (TYPE): Description
mappingError (str):
mappingErrorMessage (None, str): Description
mappingState (TYPE): Description
privateAddress (TYPE): Description
privatePort (TYPE): Description
publicAddress (TYPE): Description
publicPort (TYPE): Description
signInState (TYPE): Description
subscriptionActive (TYPE): Description
subscriptionFeatures (TYPE): Description
subscriptionState (TYPE): Description
username (TYPE): Description
privateAddress (str): Local ip
privatePort (str): Local port
publicAddress (str): Public ip
publicPort (str): Public port
signInState (str): ok
subscriptionActive (str): is returned as it
subscriptionFeatures (str): What feature your account has access to.
Fx: camera_upload,cloudsync,content_filter
subscriptionState (str): Active
username (str): You username
"""
def __init__(self, server, data):
"""Args:
"""Set attrs.
Args:
server (Plexclient):
data (xml.etree.ElementTree.Element): used to set the class attributes.
"""

View file

@ -1,15 +1,35 @@
# -*- coding: utf-8 -*-
"""
PlexAPI Sync
"""
import requests
from plexapi import utils
from plexapi.exceptions import NotFound
class SyncItem(object):
"""Summary
Attributes:
device (TYPE): Description
id (TYPE): Description
location (TYPE): Description
machineIdentifier (TYPE): Description
MediaSettings (TYPE): Description
metadataType (TYPE): Description
policy (TYPE): Description
rootTitle (TYPE): Description
servers (TYPE): Description
status (TYPE): Description
title (TYPE): Description
version (TYPE): Description
"""
def __init__(self, device, data, servers=None):
"""Summary
Args:
device (TYPE): Description
data (TYPE): Description
servers (None, optional): Description
"""
self.device = device
self.servers = servers
self.id = utils.cast(int, data.attrib.get('id'))
@ -24,20 +44,49 @@ class SyncItem(object):
self.location = data.find('Location').attrib.copy()
def __repr__(self):
"""Summary
Returns:
TYPE: Description
"""
return '<%s:%s>' % (self.__class__.__name__, self.id)
def server(self):
server = list(filter(lambda x: x.machineIdentifier == self.machineIdentifier, self.servers))
"""Summary
Returns:
TYPE: Description
Raises:
NotFound: Description
"""
server = list(filter(lambda x: x.machineIdentifier ==
self.machineIdentifier, self.servers))
if 0 == len(server):
raise NotFound('Unable to find server with uuid %s' % self.machineIdentifier)
raise NotFound('Unable to find server with uuid %s' %
self.machineIdentifier)
return server[0]
def getMedia(self):
"""Summary
Returns:
TYPE: Description
"""
server = self.server().connect()
items = utils.listItems(server, '/sync/items/%s' % self.id)
return items
def markAsDone(self, sync_id):
"""Summary
Args:
sync_id (TYPE): Description
Returns:
TYPE: Description
"""
server = self.server().connect()
url = '/sync/%s/%s/files/%s/downloaded' % (self.device.clientIdentifier, server.machineIdentifier, sync_id)
url = '/sync/%s/%s/files/%s/downloaded' % (
self.device.clientIdentifier, server.machineIdentifier, sync_id)
server.query(url, method=requests.put)

View file

@ -1,12 +1,5 @@
# -*- coding: utf-8 -*-
"""
PlexAPI Utils
Attributes:
LIBRARY_TYPES (dict): Description
NA (TYPE): Description
SEARCHTYPES (TYPE): Description
"""
import re
from datetime import datetime
from plexapi.compat import quote, urlencode
@ -39,38 +32,29 @@ class _NA(object):
"""
def __bool__(self):
"""Summary
"""Make sure Na always is False.
Returns:
TYPE: Description
bool: False
"""
return False
def __eq__(self, other):
"""Summary
"""Check eq.
Args:
other (TYPE): Description
other (str): Description
Returns:
TYPE: Description
bool: True is equal
"""
return isinstance(other, _NA) or other in [None, '__NA__']
def __nonzero__(self):
"""Summary
Returns:
TYPE: Description
"""
return False
def __repr__(self):
"""Summary
Returns:
TYPE: Description
"""
"""Pretty print."""
return '__NA__'
NA = _NA()
@ -83,15 +67,15 @@ class PlexPartialObject(object):
automatically and update itself.
Attributes:
initpath (TYPE): Description
server (TYPE): Description
initpath (str): Relative url to PMS
server (): Description
"""
def __init__(self, data, initpath, server=None):
"""
Args:
data (xml.etree.ElementTree.Element): passed from server.query
initpath (string): Relative path
initpath (str): Relative path
server (None or Plexserver, optional): PMS class your connected to
"""
self.server = server
@ -120,7 +104,7 @@ class PlexPartialObject(object):
"""Auto reload self, if the attribute is NA
Args:
attr (string): fx key
attr (str): fx key
"""
if attr == 'key' or self.__dict__.get(attr) or self.isFullObject():
return self.__dict__.get(attr, NA)
@ -131,7 +115,7 @@ class PlexPartialObject(object):
"""Set attribute
Args:
attr (string): fx key
attr (str): fx key
value (TYPE): Description
"""
if value != NA or self.isFullObject():
@ -164,12 +148,12 @@ class Playable(object):
Artists, Albums which are all not playable.
Attributes: # todo
player (TYPE): Description
playlistItemID (TYPE): Description
sessionKey (TYPE): Description
transcodeSession (TYPE): Description
username (TYPE): Description
viewedAt (datetime): Description
player (Plexclient): Player
playlistItemID (int): Playlist item id
sessionKey (int): 1223
transcodeSession (str): 12312312
username (str): Fx Hellowlol
viewedAt (datetime): viewed at.
"""
def _loadData(self, data):
@ -241,7 +225,7 @@ def buildItem(server, elem, initpath, bytag=False):
Args:
server (Plexserver): Your connected to.
elem (xml.etree.ElementTree.Element): xml from PMS
initpath (string): Relative path
initpath (str): Relative path
bytag (bool, optional): Description # figure out what this do
Raises:
@ -300,8 +284,8 @@ def findItem(server, path, title):
Args:
server (Plexserver): Description
path (string): Relative path
title (string): Fx 16 blocks
path (str): Relative path
title (str): Fx 16 blocks
Raises:
NotFound: Unable to find item: title
@ -355,7 +339,7 @@ def findStreams(media, streamtype):
Args:
media (Show, Movie, Episode): A item where find streams
streamtype (string): Possible options [movie, show, episode] # is this correct?
streamtype (str): Possible options [movie, show, episode] # is this correct?
Returns:
list: of streams
@ -402,10 +386,10 @@ def findUsername(data):
return None
def isInt(string):
def isInt(str):
"""Check of a string is a int"""
try:
int(string)
int(str)
return True
except ValueError:
return False
@ -435,7 +419,7 @@ def listChoices(server, path):
Args:
server (Plexserver): Server your connected to
path (string): Relative path to PMS
path (str): Relative path to PMS
Returns:
dict: title:key
@ -448,7 +432,7 @@ def listItems(server, path, libtype=None, watched=None, bytag=False):
Args:
server (Plexserver): PMS your connected to.
path (string): Relative path to PMS
path (str): Relative path to PMS
libtype (None or string, optional): [movie, show, episode, music] # check me
watched (None, True, False, optional): Skip or include watched items
bytag (bool, optional): Dunno wtf this is used for # todo
@ -496,7 +480,7 @@ def searchType(libtype):
Used when querying PMS.
Args:
libtype (string): Possible options see SEARCHTYPES
libtype (str): Possible options see SEARCHTYPES
Returns:
int: fx 1
@ -533,10 +517,10 @@ def threaded(callback, listargs):
def toDatetime(value, format=None):
"""Helper for datetime
"""Helper for datetime.
Args:
value (string): value to use to make datetime
value (str): value to use to make datetime
format (None, optional): string as strptime.
Returns:

View file

@ -1,13 +1,8 @@
# -*- coding: utf-8 -*-
"""
PlexVideo
Attributes:
NA (TYPE): Description
"""
from plexapi import media, utils
from plexapi.utils import Playable, PlexPartialObject
NA = utils.NA
@ -15,11 +10,12 @@ class Video(PlexPartialObject):
TYPE = None
def __init__(self, server, data, initpath):
"""
"""Default class for all video types.
Args:
server (Plexserver): The PMS server your connected to
data (Element): Element built from server.query
initpath (string): Relativ path fx /library/sections/1/all
initpath (str): Relativ path fx /library/sections/1/all
"""
super(Video, self).__init__(data, initpath, server)
@ -47,6 +43,7 @@ class Video(PlexPartialObject):
@property
def thumbUrl(self):
"""Return url to thumb image."""
return self.server.url(self.thumb)
def analyze(self):
@ -58,28 +55,24 @@ class Video(PlexPartialObject):
self.server.query('/%s/analyze' % self.key)
def markWatched(self):
"""Mark a items as watched.
"""
"""Mark a items as watched."""
path = '/:/scrobble?key=%s&identifier=com.plexapp.plugins.library' % self.ratingKey
self.server.query(path)
self.reload()
def markUnwatched(self):
"""Mark a item as unwatched.
"""
"""Mark a item as unwatched."""
path = '/:/unscrobble?key=%s&identifier=com.plexapp.plugins.library' % self.ratingKey
self.server.query(path)
self.reload()
def refresh(self):
"""Refresh a item.
"""
"""Refresh a item."""
self.server.query('%s/refresh' %
self.key, method=self.server.session.put)
def section(self):
"""Library section.
"""
"""Library section."""
return self.server.library.sectionByID(self.librarySectionID)
@ -91,7 +84,8 @@ class Movie(Video, Playable):
"""Used to set the attributes
Args:
data (Element): Usually built from server.query
data (Element): XML reponse from PMS as Element
normally built from server.query
"""
Video._loadData(self, data)
Playable._loadData(self, data)
@ -190,16 +184,25 @@ class Show(Video):
return bool(self.viewedLeafCount == self.leafCount)
def seasons(self):
"""Returns a list of Season
"""
"""Returns a list of Season."""
path = '/library/metadata/%s/children' % self.ratingKey
return utils.listItems(self.server, path, Season.TYPE)
def season(self, title):
"""Returns a Season
Args:
title (str): fx Season1
"""
path = '/library/metadata/%s/children' % self.ratingKey
return utils.findItem(self.server, path, title)
def episodes(self, watched=None):
"""Returs a list of Episode
Args:
watched (bool): Defaults to None. Exclude watched episodes
"""
leavesKey = '/library/metadata/%s/allLeaves' % self.ratingKey
return utils.listItems(self.server, leavesKey, watched=watched)
@ -208,21 +211,23 @@ class Show(Video):
return utils.findItem(self.server, path, title)
def watched(self):
"""Return a list of watched episodes
"""
"""Return a list of watched episodes"""
return self.episodes(watched=True)
def unwatched(self):
"""Return a list of unwatched episodes
"""
"""Return a list of unwatched episodes"""
return self.episodes(watched=False)
def get(self, title):
"""Get a Episode with a title.
Args:
title (str): fx Secret santa
"""
return self.episode(title)
def refresh(self):
"""Refresh the metadata
"""
"""Refresh the metadata."""
self.server.query('/library/metadata/%s/refresh' % self.ratingKey)
@ -250,36 +255,51 @@ class Season(Video):
@property
def seasonNumber(self):
"""Reurns season number."""
return self.index
def episodes(self, watched=None):
"""Return list of Episode
"""Returs a list of Episode
Args:
watched (None, optional): Description
watched (bool): Defaults to None. Exclude watched episodes
"""
childrenKey = '/library/metadata/%s/children' % self.ratingKey
return utils.listItems(self.server, childrenKey, watched=watched)
def episode(self, title):
"""Return Episode
"""Find a episode with a matching title.
Args:
title (TYPE): Description
title (sting): Fx
Returns:
Episode
"""
path = '/library/metadata/%s/children' % self.ratingKey
return utils.findItem(self.server, path, title)
def get(self, title):
"""Get a episode witha mathcing title
Args:
title (str): fx Secret santa
Returns:
Episode
"""
return self.episode(title)
def show(self):
"""Return this seasons show."""
return utils.listItems(self.server, self.parentKey)[0]
def watched(self):
"""Returns a list of watched Episode"""
return self.episodes(watched=True)
def unwatched(self):
"""Returns a list of unwatched Episode"""
return self.episodes(watched=False)
@ -288,6 +308,11 @@ class Episode(Video, Playable):
TYPE = 'episode'
def _loadData(self, data):
"""Used to set the attributes
Args:
data (Element): Usually built from server.query
"""
Video._loadData(self, data)
Playable._loadData(self, data)
self.art = data.attrib.get('art', NA)
@ -311,7 +336,6 @@ class Episode(Video, Playable):
self.rating = utils.cast(float, data.attrib.get('rating', NA))
self.viewOffset = utils.cast(int, data.attrib.get('viewOffset', 0))
self.year = utils.cast(int, data.attrib.get('year', NA))
#if self.isFullObject():
self.directors = [media.Director(self.server, e)
for e in data if e.tag == media.Director.TYPE]
self.media = [media.Media(self.server, e, self.initpath, self)
@ -331,20 +355,25 @@ class Episode(Video, Playable):
@property
def isWatched(self):
"""Returns True if watched, False if not."""
return bool(self.viewCount > 0)
@property
def seasonNumber(self):
"""Return this episode seasonnumber."""
if self._seasonNumber is None:
self._seasonNumber = self.parentIndex if self.parentIndex else self.season().seasonNumber
return self._seasonNumber
@property
def thumbUrl(self):
"""Return url to thumb image."""
return self.server.url(self.grandparentThumb)
def season(self):
"""Return this episode Season"""
return utils.listItems(self.server, self.parentKey)[0]
def show(self):
"""Return this episodes Show"""
return utils.listItems(self.server, self.grandparentKey)[0]