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 -*- # -*- coding: utf-8 -*-
"""
PlexAPI Audio
"""
from plexapi import media, utils from plexapi import media, utils
from plexapi.utils import Playable, PlexPartialObject from plexapi.utils import Playable, PlexPartialObject
NA = utils.NA NA = utils.NA
class Audio(PlexPartialObject): 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 TYPE = None
def __init__(self, server, data, initpath): 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) super(Audio, self).__init__(data, initpath, server)
def _loadData(self, data): 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.listType = 'audio'
self.addedAt = utils.toDatetime(data.attrib.get('addedAt', NA)) self.addedAt = utils.toDatetime(data.attrib.get('addedAt', NA))
self.index = data.attrib.get('index', NA) self.index = data.attrib.get('index', NA)
self.key = data.attrib.get('key', NA) self.key = data.attrib.get('key', NA)
self.lastViewedAt = utils.toDatetime(data.attrib.get('lastViewedAt', NA)) self.lastViewedAt = utils.toDatetime(data.attrib.get('lastViewedAt', NA))
self.librarySectionID = data.attrib.get('librarySectionID', 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.summary = data.attrib.get('summary', NA)
self.thumb = data.attrib.get('thumb', NA) self.thumb = data.attrib.get('thumb', NA)
self.title = data.attrib.get('title', NA) self.title = data.attrib.get('title', NA)
@ -31,55 +63,121 @@ class Audio(PlexPartialObject):
@property @property
def thumbUrl(self): def thumbUrl(self):
"""Return url to thumb image."""
return self.server.url(self.thumb) return self.server.url(self.thumb)
def refresh(self): def refresh(self):
"""Refresh the metadata."""
self.server.query('%s/refresh' % self.key, method=self.server.session.put) self.server.query('%s/refresh' % self.key, method=self.server.session.put)
def section(self): def section(self):
"""Library section."""
return self.server.library.sectionByID(self.librarySectionID) return self.server.library.sectionByID(self.librarySectionID)
@utils.register_libtype @utils.register_libtype
class Artist(Audio): 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' TYPE = 'artist'
def _loadData(self, data): 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) Audio._loadData(self, data)
self.art = data.attrib.get('art', NA) self.art = data.attrib.get('art', NA)
self.guid = data.attrib.get('guid', NA) self.guid = data.attrib.get('guid', NA)
self.key = self.key.replace('/children', '') # FIX_BUG_50 self.key = self.key.replace('/children', '') # FIX_BUG_50
self.location = utils.findLocations(data, single=True) self.location = utils.findLocations(data, single=True)
if self.isFullObject(): 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.countries = [media.Country(self.server, e)
self.genres = [media.Genre(self.server, e) for e in data if e.tag == media.Genre.TYPE] for e in data if e.tag == media.Country.TYPE]
self.similar = [media.Similar(self.server, e) for e in data if e.tag == media.Similar.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): def albums(self):
"""Return a list of Albums by thus artist."""
path = '%s/children' % self.key path = '%s/children' % self.key
return utils.listItems(self.server, path, Album.TYPE) return utils.listItems(self.server, path, Album.TYPE)
def album(self, title): def album(self, title):
"""Return a album from this artist that match title."""
path = '%s/children' % self.key path = '%s/children' % self.key
return utils.findItem(self.server, path, title) return utils.findItem(self.server, path, title)
def tracks(self, watched=None): 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 path = '%s/allLeaves' % self.key
return utils.listItems(self.server, path, watched=watched) return utils.listItems(self.server, path, watched=watched)
def track(self, title): def track(self, title):
"""Return a Track that matches title.
Args:
title (str): Fx song name
Returns:
Track:
"""
path = '%s/allLeaves' % self.key path = '%s/allLeaves' % self.key
return utils.findItem(self.server, path, title) return utils.findItem(self.server, path, title)
def get(self, title): def get(self, title):
"""Alias. See track."""
return self.track(title) return self.track(title)
@utils.register_libtype @utils.register_libtype
class Album(Audio): 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' TYPE = 'album'
def _loadData(self, data): 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) Audio._loadData(self, data)
self.art = data.attrib.get('art', NA) self.art = data.attrib.get('art', NA)
self.key = self.key.replace('/children', '') # FIX_BUG_50 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] self.genres = [media.Genre(self.server, e) for e in data if e.tag == media.Genre.TYPE]
def tracks(self, watched=None): 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 path = '%s/children' % self.key
return utils.listItems(self.server, path, watched=watched) return utils.listItems(self.server, path, watched=watched)
def track(self, title): def track(self, title):
"""Return a Track that matches title.
Args:
title (str): Fx song name
Returns:
Track:
"""
path = '%s/children' % self.key path = '%s/children' % self.key
return utils.findItem(self.server, path, title) return utils.findItem(self.server, path, title)
def get(self, title): def get(self, title):
"""Alias. See track."""
return self.track(title) return self.track(title)
def artist(self): def artist(self):
"""Return Artist of this album."""
return utils.listItems(self.server, self.parentKey)[0] return utils.listItems(self.server, self.parentKey)[0]
def watched(self): def watched(self):
"""Return Track that is lisson on."""
return self.tracks(watched=True) return self.tracks(watched=True)
def unwatched(self): def unwatched(self):
"""Return Track that is not lisson on."""
return self.tracks(watched=False) return self.tracks(watched=False)
@utils.register_libtype @utils.register_libtype
class Track(Audio, Playable): 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' TYPE = 'track'
def _loadData(self, data): def _loadData(self, data):
"""Used to set the attributes
Args:
data (Element): Usually built from server.query
"""
Audio._loadData(self, data) Audio._loadData(self, data)
Playable._loadData(self, data) Playable._loadData(self, data)
self.art = data.attrib.get('art', NA) 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.ratingCount = utils.cast(int, data.attrib.get('ratingCount', NA))
self.viewOffset = utils.cast(int, data.attrib.get('viewOffset', 0)) self.viewOffset = utils.cast(int, data.attrib.get('viewOffset', 0))
self.year = utils.cast(int, data.attrib.get('year', NA)) self.year = utils.cast(int, data.attrib.get('year', NA))
if self.isFullObject(): if self.isFullObject(): # check me
self.moods = [media.Mood(self.server, e) for e in data if e.tag == media.Mood.TYPE] self.moods = [media.Mood(self.server, e)
self.media = [media.Media(self.server, e, self.initpath, self) for e in data if e.tag == media.Media.TYPE] 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 # data for active sessions and history
self.sessionKey = utils.cast(int, data.attrib.get('sessionKey', NA)) self.sessionKey = utils.cast(int, data.attrib.get('sessionKey', NA))
self.username = utils.findUsername(data) self.username = utils.findUsername(data)
@ -151,10 +307,13 @@ class Track(Audio, Playable):
@property @property
def thumbUrl(self): def thumbUrl(self):
"""Return url to thumb image."""
return self.server.url(self.parentThumb) return self.server.url(self.parentThumb)
def album(self): def album(self):
"""Return this track's Album."""
return utils.listItems(self.server, self.parentKey)[0] return utils.listItems(self.server, self.parentKey)[0]
def artist(self): def artist(self):
"""Return this track's Artist."""
return utils.listItems(self.server, self.grandparentKey)[0] return utils.listItems(self.server, self.grandparentKey)[0]

View file

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

View file

@ -1,22 +1,68 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
""" import sys
PlexAPI MyPlex
""" if sys.version_info <= (3, 3):
import plexapi, requests 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 import TIMEOUT, log, utils
from plexapi.exceptions import BadRequest, NotFound, Unauthorized from plexapi.exceptions import BadRequest, NotFound, Unauthorized
from plexapi.client import PlexClient from plexapi.client import PlexClient
from plexapi.server import PlexServer 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): 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' BASEURL = 'https://plex.tv/users/account'
SIGNIN = 'https://my.plexapp.com/users/sign_in.xml' SIGNIN = 'https://my.plexapp.com/users/sign_in.xml'
def __init__(self, data, initpath=None): 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.authenticationToken = data.attrib.get('authenticationToken')
self.certificateVersion = data.attrib.get('certificateVersion') self.certificateVersion = data.attrib.get('certificateVersion')
self.cloudSyncDevice = data.attrib.get('cloudSyncDevice') self.cloudSyncDevice = data.attrib.get('cloudSyncDevice')
@ -47,37 +93,92 @@ class MyPlexAccount(object):
self.entitlements = None self.entitlements = None
def __repr__(self): def __repr__(self):
"""Pretty print."""
return '<%s:%s:%s>' % (self.__class__.__name__, self.id, self.username.encode('utf8')) return '<%s:%s:%s>' % (self.__class__.__name__, self.id, self.username.encode('utf8'))
def devices(self): def devices(self):
"""Return a all devices connected to the plex account.
Returns:
list: of MyPlexDevice
"""
return _listItems(MyPlexDevice.BASEURL, self.authenticationToken, MyPlexDevice) return _listItems(MyPlexDevice.BASEURL, self.authenticationToken, MyPlexDevice)
def device(self, name): 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) return _findItem(self.devices(), name)
def resources(self): def resources(self):
"""Resources.
Returns:
List: of MyPlexResource
"""
return _listItems(MyPlexResource.BASEURL, self.authenticationToken, MyPlexResource) return _listItems(MyPlexResource.BASEURL, self.authenticationToken, MyPlexResource)
def resource(self, name): def resource(self, name):
"""Find resource ny name.
Args:
name (str): to find
Returns:
class: MyPlexResource
"""
return _findItem(self.resources(), name) return _findItem(self.resources(), name)
def users(self): def users(self):
"""List of users.
Returns:
List: of MyPlexuser
"""
return _listItems(MyPlexUser.BASEURL, self.authenticationToken, MyPlexUser) return _listItems(MyPlexUser.BASEURL, self.authenticationToken, MyPlexUser)
def user(self, email): 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']) return _findItem(self.users(), email, ['username', 'email'])
@classmethod @classmethod
def signin(cls, username, password): 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: if 'X-Plex-Token' in plexapi.BASE_HEADERS:
del plexapi.BASE_HEADERS['X-Plex-Token'] del plexapi.BASE_HEADERS['X-Plex-Token']
auth = (username, password) auth = (username, password)
log.info('POST %s', cls.SIGNIN) 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: if response.status_code != requests.codes.created:
codename = codes.get(response.status_code)[0] codename = codes.get(response.status_code)[0]
if response.status_code == 401: 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)) raise BadRequest('(%s) %s' % (response.status_code, codename))
data = ElementTree.fromstring(response.text.encode('utf8')) data = ElementTree.fromstring(response.text.encode('utf8'))
return cls(data, cls.SIGNIN) return cls(data, cls.SIGNIN)
@ -86,10 +187,39 @@ class MyPlexAccount(object):
# Not to be confused with the MyPlexAccount, this represents # Not to be confused with the MyPlexAccount, this represents
# non-signed in users such as friends and linked accounts. # non-signed in users such as friends and linked accounts.
class MyPlexUser(object): 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/' BASEURL = 'https://plex.tv/api/users/'
def __init__(self, data, initpath=None): 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.allowChannels = utils.cast(bool, data.attrib.get('allowChannels'))
self.allowSync = utils.cast(bool, data.attrib.get('allowSync')) self.allowSync = utils.cast(bool, data.attrib.get('allowSync'))
self.email = data.attrib.get('email') self.email = data.attrib.get('email')
@ -101,20 +231,48 @@ class MyPlexUser(object):
self.home = utils.cast(bool, data.attrib.get('home')) self.home = utils.cast(bool, data.attrib.get('home'))
self.id = utils.cast(int, data.attrib.get('id')) self.id = utils.cast(int, data.attrib.get('id'))
self.protected = utils.cast(bool, data.attrib.get('protected')) 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.restricted = data.attrib.get('restricted')
self.thumb = data.attrib.get('thumb') self.thumb = data.attrib.get('thumb')
self.title = data.attrib.get('title') self.title = data.attrib.get('title')
self.username = data.attrib.get('username') self.username = data.attrib.get('username')
def __repr__(self): def __repr__(self):
"""Pretty repr."""
return '<%s:%s:%s>' % (self.__class__.__name__, self.id, self.username) return '<%s:%s:%s>' % (self.__class__.__name__, self.id, self.username)
class MyPlexResource(object): 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' BASEURL = 'https://plex.tv/api/resources?includeHttps=1'
def __init__(self, data): def __init__(self, data):
"""Summary
Args:
data (Element): XML response as Element
"""
self.name = data.attrib.get('name') self.name = data.attrib.get('name')
self.accessToken = data.attrib.get('accessToken') self.accessToken = data.attrib.get('accessToken')
self.product = data.attrib.get('product') self.product = data.attrib.get('product')
@ -130,16 +288,31 @@ class MyPlexResource(object):
self.home = utils.cast(bool, data.attrib.get('home')) self.home = utils.cast(bool, data.attrib.get('home'))
self.synced = utils.cast(bool, data.attrib.get('synced')) self.synced = utils.cast(bool, data.attrib.get('synced'))
self.presence = utils.cast(bool, data.attrib.get('presence')) 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): def __repr__(self):
"""Pretty repr."""
return '<%s:%s>' % (self.__class__.__name__, self.name.encode('utf8')) return '<%s:%s>' % (self.__class__.__name__, self.name.encode('utf8'))
def connect(self, ssl=None): 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) # 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
forcelocal = lambda c: self.owned or c.local 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)] https = [c.uri for c in self.connections if forcelocal(c)]
http = [c.httpuri for c in self.connections if forcelocal(c)] http = [c.httpuri for c in self.connections if forcelocal(c)]
connections = https + http connections = https + http
@ -148,26 +321,56 @@ class MyPlexResource(object):
listargs = [[c] for c in connections] listargs = [[c] for c in connections]
results = utils.threaded(self._connect, listargs) results = utils.threaded(self._connect, listargs)
# At this point we have a list of result tuples containing (url, token, PlexServer) # 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: for url, token, result in results:
okerr = 'OK' if result else 'ERR' okerr = 'OK' if result else 'ERR'
log.info('Testing resource connection: %s?X-Plex-Token=%s %s', url, token, okerr) log.info(
results = list(filter(None, [r[2] for r in results if r])) '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: if not results:
raise NotFound('Unable to connect to resource: %s' % self.name) 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] return results[0]
def _connect(self, url, results, i): def _connect(self, url, results, i):
"""Connect.
Args:
url (str): url to the resource
results (TYPE): Description
i (TYPE): Description
Returns:
TYPE: Description
"""
try: try:
results[i] = (url, self.accessToken, PlexServer(url, self.accessToken)) results[i] = (url, self.accessToken,
PlexServer(url, self.accessToken))
except NotFound: except NotFound:
results[i] = (url, self.accessToken, None) results[i] = (url, self.accessToken, None)
class ResourceConnection(object): 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): def __init__(self, data):
"""Set attrs.
Args:
data (Element): XML response as Element from PMS.
"""
self.protocol = data.attrib.get('protocol') self.protocol = data.attrib.get('protocol')
self.address = data.attrib.get('address') self.address = data.attrib.get('address')
self.port = utils.cast(int, data.attrib.get('port')) 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) self.httpuri = 'http://%s:%s' % (self.address, self.port)
def __repr__(self): def __repr__(self):
"""Pretty repr."""
return '<%s:%s>' % (self.__class__.__name__, self.uri.encode('utf8')) return '<%s:%s>' % (self.__class__.__name__, self.uri.encode('utf8'))
class MyPlexDevice(object): 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' BASEURL = 'https://plex.tv/devices.xml'
def __init__(self, data): def __init__(self, data):
"""Set attrs
Args:
data (Element): XML response as Element from PMS
"""
self.name = data.attrib.get('name') self.name = data.attrib.get('name')
self.publicAddress = data.attrib.get('publicAddress') self.publicAddress = data.attrib.get('publicAddress')
self.product = data.attrib.get('product') self.product = data.attrib.get('product')
@ -199,36 +431,75 @@ class MyPlexDevice(object):
self.token = data.attrib.get('token') self.token = data.attrib.get('token')
self.screenResolution = data.attrib.get('screenResolution') self.screenResolution = data.attrib.get('screenResolution')
self.screenDensity = data.attrib.get('screenDensity') 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): def __repr__(self):
"""Pretty repr."""
return '<%s:%s:%s>' % (self.__class__.__name__, self.name.encode('utf8'), self.product.encode('utf8')) return '<%s:%s:%s>' % (self.__class__.__name__, self.name.encode('utf8'), self.product.encode('utf8'))
def connect(self, ssl=None): 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 # 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.
listargs = [[c] for c in self.connections] listargs = [[c] for c in self.connections]
results = utils.threaded(self._connect, listargs) results = utils.threaded(self._connect, listargs)
# At this point we have a list of result tuples containing (url, token, PlexServer) # 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: for url, token, result in results:
okerr = 'OK' if result else 'ERR' okerr = 'OK' if result else 'ERR'
log.info('Testing device connection: %s?X-Plex-Token=%s %s', url, token, okerr) log.info('Testing device connection: %s?X-Plex-Token=%s %s',
results = list(filter(None, [r[2] for r in results if r])) url, token, okerr)
results = [r[2] for r in results if r and r[2] is not None]
if not results: if not results:
raise NotFound('Unable to connect to resource: %s' % self.name) 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] return results[0]
def _connect(self, url, results, i): def _connect(self, url, results, i):
"""Summary
Args:
url (TYPE): Description
results (TYPE): Description
i (TYPE): Description
Returns:
TYPE: Description
"""
try: try:
results[i] = (url, self.token, PlexClient(url, self.token)) results[i] = (url, self.token, PlexClient(url, self.token))
except NotFound as err: except NotFound as err:
print(err)
results[i] = (url, self.token, None) results[i] = (url, self.token, None)
def _findItem(items, value, attrs=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'] attrs = attrs or ['name']
for item in items: for item in items:
for attr in attrs: for attr in attrs:
@ -238,6 +509,16 @@ def _findItem(items, value, attrs=None):
def _listItems(url, token, cls): 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 = plexapi.BASE_HEADERS
headers['X-Plex-Token'] = token headers['X-Plex-Token'] = token
log.info('GET %s?X-Plex-Token=%s', url, token) log.info('GET %s?X-Plex-Token=%s', url, token)

View file

@ -1,6 +1,9 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
""" """
PlexPhoto PlexPhoto
Attributes:
NA (TYPE): Description
""" """
from plexapi import media, utils from plexapi import media, utils
from plexapi.utils import PlexPartialObject from plexapi.utils import PlexPartialObject
@ -9,12 +12,46 @@ NA = utils.NA
@utils.register_libtype @utils.register_libtype
class Photoalbum(PlexPartialObject): 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' TYPE = 'photoalbum'
def __init__(self, server, data, initpath): def __init__(self, server, data, initpath):
"""Summary
Args:
server (TYPE): Description
data (TYPE): Description
initpath (TYPE): Description
"""
super(Photoalbum, self).__init__(data, initpath, server) super(Photoalbum, self).__init__(data, initpath, server)
def _loadData(self, data): def _loadData(self, data):
"""Summary
Args:
data (TYPE): Description
Returns:
TYPE: Description
"""
self.listType = 'photo' self.listType = 'photo'
self.addedAt = utils.toDatetime(data.attrib.get('addedAt', NA)) self.addedAt = utils.toDatetime(data.attrib.get('addedAt', NA))
self.art = data.attrib.get('art', NA) self.art = data.attrib.get('art', NA)
@ -31,30 +68,84 @@ class Photoalbum(PlexPartialObject):
self.updatedAt = utils.toDatetime(data.attrib.get('updatedAt', NA)) self.updatedAt = utils.toDatetime(data.attrib.get('updatedAt', NA))
def photos(self): def photos(self):
"""Summary
Returns:
TYPE: Description
"""
path = '/library/metadata/%s/children' % self.ratingKey path = '/library/metadata/%s/children' % self.ratingKey
return utils.listItems(self.server, path, Photo.TYPE) return utils.listItems(self.server, path, Photo.TYPE)
def photo(self, title): def photo(self, title):
"""Summary
Args:
title (TYPE): Description
Returns:
TYPE: Description
"""
path = '/library/metadata/%s/children' % self.ratingKey path = '/library/metadata/%s/children' % self.ratingKey
return utils.findItem(self.server, path, title) return utils.findItem(self.server, path, title)
def section(self): def section(self):
"""Summary
Returns:
TYPE: Description
"""
return self.server.library.sectionByID(self.librarySectionID) return self.server.library.sectionByID(self.librarySectionID)
@utils.register_libtype @utils.register_libtype
class Photo(PlexPartialObject): 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' TYPE = 'photo'
def __init__(self, server, data, initpath): def __init__(self, server, data, initpath):
"""Summary
Args:
server (TYPE): Description
data (TYPE): Description
initpath (TYPE): Description
"""
super(Photo, self).__init__(data, initpath, server) super(Photo, self).__init__(data, initpath, server)
def _loadData(self, data): def _loadData(self, data):
"""Summary
Args:
data (TYPE): Description
Returns:
TYPE: Description
"""
self.listType = 'photo' self.listType = 'photo'
self.addedAt = utils.toDatetime(data.attrib.get('addedAt', NA)) self.addedAt = utils.toDatetime(data.attrib.get('addedAt', NA))
self.index = utils.cast(int, data.attrib.get('index', NA)) self.index = utils.cast(int, data.attrib.get('index', NA))
self.key = data.attrib.get('key', 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.parentKey = data.attrib.get('parentKey', NA)
self.parentRatingKey = data.attrib.get('parentRatingKey', NA) self.parentRatingKey = data.attrib.get('parentRatingKey', NA)
self.ratingKey = data.attrib.get('ratingKey', 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.updatedAt = utils.toDatetime(data.attrib.get('updatedAt', NA))
self.year = utils.cast(int, data.attrib.get('year', NA)) self.year = utils.cast(int, data.attrib.get('year', NA))
if self.isFullObject(): 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): def photoalbum(self):
"""Summary
Returns:
TYPE: Description
"""
return utils.listItems(self.server, self.parentKey)[0] return utils.listItems(self.server, self.parentKey)[0]
def section(self): def section(self):
"""Summary
Returns:
TYPE: Description
"""
return self.server.library.sectionByID(self.photoalbum().librarySectionID) return self.server.library.sectionByID(self.photoalbum().librarySectionID)

View file

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

View file

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

View file

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

View file

@ -1,15 +1,35 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
"""
PlexAPI Sync
"""
import requests import requests
from plexapi import utils from plexapi import utils
from plexapi.exceptions import NotFound from plexapi.exceptions import NotFound
class SyncItem(object): 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): def __init__(self, device, data, servers=None):
"""Summary
Args:
device (TYPE): Description
data (TYPE): Description
servers (None, optional): Description
"""
self.device = device self.device = device
self.servers = servers self.servers = servers
self.id = utils.cast(int, data.attrib.get('id')) self.id = utils.cast(int, data.attrib.get('id'))
@ -24,20 +44,49 @@ class SyncItem(object):
self.location = data.find('Location').attrib.copy() self.location = data.find('Location').attrib.copy()
def __repr__(self): def __repr__(self):
"""Summary
Returns:
TYPE: Description
"""
return '<%s:%s>' % (self.__class__.__name__, self.id) return '<%s:%s>' % (self.__class__.__name__, self.id)
def server(self): 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): 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] return server[0]
def getMedia(self): def getMedia(self):
"""Summary
Returns:
TYPE: Description
"""
server = self.server().connect() server = self.server().connect()
items = utils.listItems(server, '/sync/items/%s' % self.id) items = utils.listItems(server, '/sync/items/%s' % self.id)
return items return items
def markAsDone(self, sync_id): def markAsDone(self, sync_id):
"""Summary
Args:
sync_id (TYPE): Description
Returns:
TYPE: Description
"""
server = self.server().connect() 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) server.query(url, method=requests.put)

View file

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

View file

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