mirror of
https://github.com/pkkid/python-plexapi
synced 2024-11-10 06:04:15 +00:00
f
This commit is contained in:
parent
8d05808236
commit
740e7a5b9b
3 changed files with 343 additions and 83 deletions
|
@ -1,4 +1,5 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
PlexServer
|
||||
"""
|
||||
|
@ -183,19 +184,20 @@ class PlexServer(object):
|
|||
path (sting): relative path to PMS, fx /search?query=HELLO
|
||||
method (None, optional): requests.method, fx requests.put
|
||||
headers (None, optional): Headers that will be passed to PMS
|
||||
**kwargs (dict): Loads of different stuff
|
||||
**kwargs (dict): Used for filter and sorting.
|
||||
|
||||
Raises:
|
||||
BadRequest: Description
|
||||
|
||||
Returns:
|
||||
ElementTree or None
|
||||
xml.etree.ElementTree.Element or None
|
||||
"""
|
||||
url = self.url(path)
|
||||
method = method or self.session.get
|
||||
log.info('%s %s', method.__name__.upper(), url)
|
||||
h = headers.copy()
|
||||
h.update(headers or {})
|
||||
h = self.headers().copy()
|
||||
if headers:
|
||||
h.update(headers)
|
||||
response = method(url, headers=h, timeout=TIMEOUT, **kwargs)
|
||||
if response.status_code not in [200, 201]:
|
||||
codename = codes.get(response.status_code)[0]
|
||||
|
@ -236,7 +238,7 @@ class Account(object):
|
|||
is not required to get basic plex information.
|
||||
|
||||
Attributes:
|
||||
authToken (TYPE): Description
|
||||
authToken (sting): X-Plex-Token, using for authenication with PMS
|
||||
mappingError (TYPE): Description
|
||||
mappingErrorMessage (TYPE): Description
|
||||
mappingState (TYPE): Description
|
||||
|
@ -252,6 +254,10 @@ class Account(object):
|
|||
"""
|
||||
|
||||
def __init__(self, server, data):
|
||||
"""Args:
|
||||
server (Plexclient):
|
||||
data (xml.etree.ElementTree.Element): used to set the class attributes.
|
||||
"""
|
||||
self.authToken = data.attrib.get('authToken')
|
||||
self.username = data.attrib.get('username')
|
||||
self.mappingState = data.attrib.get('mappingState')
|
||||
|
|
249
plexapi/utils.py
249
plexapi/utils.py
|
@ -39,15 +39,38 @@ class _NA(object):
|
|||
"""
|
||||
|
||||
def __bool__(self):
|
||||
"""Summary
|
||||
|
||||
Returns:
|
||||
TYPE: Description
|
||||
"""
|
||||
return False
|
||||
|
||||
def __eq__(self, other):
|
||||
"""Summary
|
||||
|
||||
Args:
|
||||
other (TYPE): Description
|
||||
|
||||
Returns:
|
||||
TYPE: Description
|
||||
"""
|
||||
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
|
||||
"""
|
||||
return '__NA__'
|
||||
|
||||
NA = _NA()
|
||||
|
@ -65,11 +88,25 @@ class PlexPartialObject(object):
|
|||
"""
|
||||
|
||||
def __init__(self, data, initpath, server=None):
|
||||
"""
|
||||
Args:
|
||||
data (xml.etree.ElementTree.Element): passed from server.query
|
||||
initpath (string): Relative path
|
||||
server (None or Plexserver, optional): PMS class your connected to
|
||||
"""
|
||||
self.server = server
|
||||
self.initpath = initpath
|
||||
self._loadData(data)
|
||||
|
||||
def __eq__(self, other):
|
||||
"""Summary
|
||||
|
||||
Args:
|
||||
other (TYPE): Description
|
||||
|
||||
Returns:
|
||||
TYPE: Description
|
||||
"""
|
||||
return other is not None and self.key == other.key
|
||||
|
||||
def __repr__(self):
|
||||
|
@ -80,18 +117,32 @@ class PlexPartialObject(object):
|
|||
return '<%s:%s:%s>' % (clsname, key, title)
|
||||
|
||||
def __getattr__(self, attr):
|
||||
"""Auto reload self, if the attribute is NA"""
|
||||
"""Auto reload self, if the attribute is NA
|
||||
|
||||
Args:
|
||||
attr (string): fx key
|
||||
"""
|
||||
if attr == 'key' or self.__dict__.get(attr) or self.isFullObject():
|
||||
return self.__dict__.get(attr, NA)
|
||||
self.reload()
|
||||
return self.__dict__.get(attr, NA)
|
||||
|
||||
def __setattr__(self, attr, value):
|
||||
"""Set attribute
|
||||
|
||||
Args:
|
||||
attr (string): fx key
|
||||
value (TYPE): Description
|
||||
"""
|
||||
if value != NA or self.isFullObject():
|
||||
self.__dict__[attr] = value # twice as fast
|
||||
#super(PlexPartialObject, self).__setattr__(attr, value)
|
||||
self.__dict__[attr] = value
|
||||
|
||||
def _loadData(self, data):
|
||||
"""Uses a element to set a attrs.
|
||||
|
||||
Args:
|
||||
data (Element): Used by attrs
|
||||
"""
|
||||
raise Exception('Abstract method not implemented.')
|
||||
|
||||
def isFullObject(self):
|
||||
|
@ -101,29 +152,32 @@ class PlexPartialObject(object):
|
|||
return not self.isFullObject()
|
||||
|
||||
def reload(self):
|
||||
"""Reload the data for this object from PlexServer XML.
|
||||
"""
|
||||
"""Reload the data for this object from PlexServer XML."""
|
||||
data = self.server.query(self.key)
|
||||
self.initpath = self.key
|
||||
self._loadData(data[0])
|
||||
|
||||
|
||||
|
||||
class Playable(object):
|
||||
"""This is a general place to store functions specific to media that is Playable. Things
|
||||
were getting mixed up a bit when dealing with Shows, Season, Artists, Albums which
|
||||
are all not playable.
|
||||
"""This is a general place to store functions specific to media that is Playable.
|
||||
Things were getting mixed up a bit when dealing with Shows, Season,
|
||||
Artists, Albums which are all not playable.
|
||||
|
||||
Attributes:
|
||||
Attributes: # todo
|
||||
player (TYPE): Description
|
||||
playlistItemID (TYPE): Description
|
||||
sessionKey (TYPE): Description
|
||||
transcodeSession (TYPE): Description
|
||||
username (TYPE): Description
|
||||
viewedAt (TYPE): Description
|
||||
viewedAt (datetime): Description
|
||||
"""
|
||||
|
||||
def _loadData(self, data):
|
||||
"""Set the class attributes
|
||||
|
||||
Args:
|
||||
data (xml.etree.ElementTree.Element): usually from server.query
|
||||
"""
|
||||
# data for active sessions (/status/sessions)
|
||||
self.sessionKey = cast(int, data.attrib.get('sessionKey', NA))
|
||||
self.username = findUsername(data)
|
||||
|
@ -135,6 +189,17 @@ class Playable(object):
|
|||
self.playlistItemID = cast(int, data.attrib.get('playlistItemID', NA))
|
||||
|
||||
def getStreamURL(self, **params):
|
||||
"""Make a stream url that can be used by vlc.
|
||||
|
||||
Args:
|
||||
**params (dict): Description
|
||||
|
||||
Returns:
|
||||
string: ''
|
||||
|
||||
Raises:
|
||||
Unsupported: Raises a error is the type is wrong.
|
||||
"""
|
||||
if self.TYPE not in ('movie', 'episode', 'track'):
|
||||
raise Unsupported(
|
||||
'Fetching stream URL for %s is unsupported.' % self.TYPE)
|
||||
|
@ -156,32 +221,31 @@ class Playable(object):
|
|||
return self.server.url('/%s/:/transcode/universal/start.m3u8?%s' % (streamtype, urlencode(params)))
|
||||
|
||||
def iterParts(self):
|
||||
"""Yield parts
|
||||
"""
|
||||
"""Yield parts."""
|
||||
for item in self.media:
|
||||
for part in item.parts:
|
||||
yield part
|
||||
|
||||
def play(self, client):
|
||||
"""Start playback on a client."""
|
||||
"""Start playback on a client.
|
||||
|
||||
Args:
|
||||
client (PlexClient): The client to start playing on.
|
||||
"""
|
||||
client.playMedia(self)
|
||||
|
||||
|
||||
def buildItem(server, elem, initpath, bytag=False):
|
||||
"""Build classes used by the plexapi.
|
||||
|
||||
Args:
|
||||
server (Plexserver): Server
|
||||
elem (ElementThree):
|
||||
initpath (string): init path of the url, used to determin
|
||||
if this is a full object.
|
||||
bytag (bool): Dunno # TOO
|
||||
Args:
|
||||
server (Plexserver): Your connected to.
|
||||
elem (xml.etree.ElementTree.Element): xml from PMS
|
||||
initpath (string): Relative path
|
||||
bytag (bool, optional): Description # figure out what this do
|
||||
|
||||
Returns:
|
||||
library type
|
||||
|
||||
Raises:
|
||||
UnknownType
|
||||
Raises:
|
||||
UnknownType: Unknown library type libtype
|
||||
|
||||
"""
|
||||
libtype = elem.tag if bytag else elem.attrib.get('type')
|
||||
|
@ -194,7 +258,12 @@ def buildItem(server, elem, initpath, bytag=False):
|
|||
|
||||
|
||||
def cast(func, value):
|
||||
"""Helper to change to the correct type"""
|
||||
"""Helper to change to the correct type
|
||||
|
||||
Args:
|
||||
func (function): function to used [int, bool float]
|
||||
value (string, int, float): value to cast
|
||||
"""
|
||||
if value not in [None, NA]:
|
||||
if func == bool:
|
||||
return bool(int(value))
|
||||
|
@ -208,7 +277,15 @@ def cast(func, value):
|
|||
|
||||
|
||||
def findKey(server, key):
|
||||
"""Finds and builds a object based on ratingKey."""
|
||||
"""Finds and builds a object based on ratingKey.
|
||||
|
||||
Args:
|
||||
server (Plexserver): PMS your connected to
|
||||
key (int): key to look for
|
||||
|
||||
Raises:
|
||||
NotFound: Unable to find key. Key
|
||||
"""
|
||||
path = '/library/metadata/{0}'.format(key)
|
||||
try:
|
||||
# Item seems to be the first sub element
|
||||
|
@ -219,7 +296,16 @@ def findKey(server, key):
|
|||
|
||||
|
||||
def findItem(server, path, title):
|
||||
"""Finds and builds a object based on title."""
|
||||
"""Finds and builds a object based on title.
|
||||
|
||||
Args:
|
||||
server (Plexserver): Description
|
||||
path (string): Relative path
|
||||
title (string): Fx 16 blocks
|
||||
|
||||
Raises:
|
||||
NotFound: Unable to find item: title
|
||||
"""
|
||||
for elem in server.query(path):
|
||||
if elem.attrib.get('title').lower() == title.lower():
|
||||
return buildItem(server, elem, path)
|
||||
|
@ -229,12 +315,12 @@ def findItem(server, path, title):
|
|||
def findLocations(data, single=False):
|
||||
"""Extract the path from a location tag
|
||||
|
||||
Args:
|
||||
data (elementthree):
|
||||
single (bool): One or more locations
|
||||
Args:
|
||||
data (xml.etree.ElementTree.Element): xml from PMS as Element
|
||||
single (bool, optional): Only return one
|
||||
|
||||
Returns:
|
||||
list: of filepaths
|
||||
Returns:
|
||||
filepath string if single is True else list of filepaths
|
||||
"""
|
||||
locations = []
|
||||
for elem in data:
|
||||
|
@ -248,11 +334,12 @@ def findLocations(data, single=False):
|
|||
def findPlayer(server, data):
|
||||
"""Find a player in a elementthee
|
||||
|
||||
Args:
|
||||
data (elementthree):
|
||||
Args:
|
||||
server (Plexserver): PMS your connected to
|
||||
data (xml.etree.ElementTree.Element): xml from pms as a element
|
||||
|
||||
Returns:
|
||||
PlexClient or None
|
||||
Returns:
|
||||
PlexClient or None
|
||||
"""
|
||||
elem = data.find('Player')
|
||||
if elem is not None:
|
||||
|
@ -264,6 +351,15 @@ def findPlayer(server, data):
|
|||
|
||||
|
||||
def findStreams(media, streamtype):
|
||||
"""Find streams.
|
||||
|
||||
Args:
|
||||
media (Show, Movie, Episode): A item where find streams
|
||||
streamtype (string): Possible options [movie, show, episode] # is this correct?
|
||||
|
||||
Returns:
|
||||
list: of streams
|
||||
"""
|
||||
streams = []
|
||||
for mediaitem in media:
|
||||
for part in mediaitem.parts:
|
||||
|
@ -274,6 +370,16 @@ def findStreams(media, streamtype):
|
|||
|
||||
|
||||
def findTranscodeSession(server, data):
|
||||
"""Find transcode session.
|
||||
|
||||
Args:
|
||||
server (Plexserver): PMS your connected to
|
||||
data (xml.etree.ElementTree.Element): XML response from PMS as Element
|
||||
|
||||
Returns:
|
||||
media.TranscodeSession or None
|
||||
"""
|
||||
|
||||
elem = data.find('TranscodeSession')
|
||||
if elem is not None:
|
||||
from plexapi import media
|
||||
|
@ -282,6 +388,14 @@ def findTranscodeSession(server, data):
|
|||
|
||||
|
||||
def findUsername(data):
|
||||
"""Find a username in a Element
|
||||
|
||||
Args:
|
||||
data (xml.etree.ElementTree.Element): XML from PMS as a Element
|
||||
|
||||
Returns:
|
||||
username or None
|
||||
"""
|
||||
elem = data.find('User')
|
||||
if elem is not None:
|
||||
return elem.attrib.get('title')
|
||||
|
@ -289,6 +403,7 @@ def findUsername(data):
|
|||
|
||||
|
||||
def isInt(string):
|
||||
"""Check of a string is a int"""
|
||||
try:
|
||||
int(string)
|
||||
return True
|
||||
|
@ -297,6 +412,15 @@ def isInt(string):
|
|||
|
||||
|
||||
def joinArgs(args):
|
||||
"""Builds a query string where only
|
||||
the value is quoted.
|
||||
|
||||
Args:
|
||||
args (dict): ex {'genre': 'action', 'type': 1337}
|
||||
|
||||
Returns:
|
||||
string: ?genre=action&type=1337
|
||||
"""
|
||||
if not args:
|
||||
return ''
|
||||
arglist = []
|
||||
|
@ -307,10 +431,31 @@ def joinArgs(args):
|
|||
|
||||
|
||||
def listChoices(server, path):
|
||||
"""ListChoices is by _cleanSort etc.
|
||||
|
||||
Args:
|
||||
server (Plexserver): Server your connected to
|
||||
path (string): Relative path to PMS
|
||||
|
||||
Returns:
|
||||
dict: title:key
|
||||
"""
|
||||
return {c.attrib['title']: c.attrib['key'] for c in server.query(path)}
|
||||
|
||||
|
||||
def listItems(server, path, libtype=None, watched=None, bytag=False):
|
||||
"""Return a list buildItem. See buildItem doc.
|
||||
|
||||
Args:
|
||||
server (Plexserver): PMS your connected to.
|
||||
path (string): 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
|
||||
|
||||
Returns:
|
||||
list: of buildItem
|
||||
"""
|
||||
items = []
|
||||
for elem in server.query(path):
|
||||
if libtype and elem.attrib.get('type') != libtype:
|
||||
|
@ -347,6 +492,19 @@ def rget(obj, attrstr, default=None, delim='.'):
|
|||
|
||||
|
||||
def searchType(libtype):
|
||||
"""Map search type name to int using SEACHTYPES
|
||||
Used when querying PMS.
|
||||
|
||||
Args:
|
||||
libtype (string): Possible options see SEARCHTYPES
|
||||
|
||||
Returns:
|
||||
int: fx 1
|
||||
|
||||
Raises:
|
||||
NotFound: Unknown libtype: libtype
|
||||
"""
|
||||
|
||||
libtype = str(libtype)
|
||||
if libtype in [str(v) for v in SEARCHTYPES.values()]:
|
||||
return libtype
|
||||
|
@ -356,6 +514,13 @@ def searchType(libtype):
|
|||
|
||||
|
||||
def threaded(callback, listargs):
|
||||
"""Run some function in threads.
|
||||
|
||||
Args:
|
||||
callback (function): funcion to run in thread
|
||||
listargs (list): args parssed to the callback
|
||||
|
||||
"""
|
||||
threads, results = [], []
|
||||
for args in listargs:
|
||||
args += [results, len(results)]
|
||||
|
@ -368,7 +533,15 @@ def threaded(callback, listargs):
|
|||
|
||||
|
||||
def toDatetime(value, format=None):
|
||||
"""Helper for datetime"""
|
||||
"""Helper for datetime
|
||||
|
||||
Args:
|
||||
value (string): value to use to make datetime
|
||||
format (None, optional): string as strptime.
|
||||
|
||||
Returns:
|
||||
datetime
|
||||
"""
|
||||
if value and value != NA:
|
||||
if format:
|
||||
value = datetime.strptime(value, format)
|
||||
|
|
151
plexapi/video.py
151
plexapi/video.py
|
@ -1,6 +1,10 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
PlexVideo
|
||||
|
||||
Attributes:
|
||||
NA (TYPE): Description
|
||||
"""
|
||||
from plexapi import media, utils
|
||||
from plexapi.utils import Playable, PlexPartialObject
|
||||
|
@ -11,15 +15,28 @@ class Video(PlexPartialObject):
|
|||
TYPE = None
|
||||
|
||||
def __init__(self, server, data, initpath):
|
||||
"""
|
||||
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
|
||||
|
||||
"""
|
||||
super(Video, self).__init__(data, initpath, server)
|
||||
|
||||
def _loadData(self, data):
|
||||
"""Used to set the attributes
|
||||
|
||||
Args:
|
||||
data (Element): Usually built from server.query
|
||||
"""
|
||||
self.listType = 'video'
|
||||
self.addedAt = utils.toDatetime(data.attrib.get('addedAt', 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.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)
|
||||
|
@ -33,26 +50,36 @@ class Video(PlexPartialObject):
|
|||
return self.server.url(self.thumb)
|
||||
|
||||
def analyze(self):
|
||||
""" The primary purpose of media analysis is to gather information about that media
|
||||
item. All of the media you add to a Library has properties that are useful to
|
||||
know–whether it's a video file, a music track, or one of your photos.
|
||||
"""The primary purpose of media analysis is to gather information about
|
||||
that mediaitem. All of the media you add to a Library has properties
|
||||
that are useful to know–whether it's a video file,
|
||||
a music track, or one of your photos.
|
||||
"""
|
||||
self.server.query('/%s/analyze' % self.key)
|
||||
|
||||
def markWatched(self):
|
||||
"""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.
|
||||
"""
|
||||
path = '/:/unscrobble?key=%s&identifier=com.plexapp.plugins.library' % self.ratingKey
|
||||
self.server.query(path)
|
||||
self.reload()
|
||||
|
||||
def refresh(self):
|
||||
self.server.query('%s/refresh' % self.key, method=self.server.session.put)
|
||||
"""Refresh a item.
|
||||
"""
|
||||
self.server.query('%s/refresh' %
|
||||
self.key, method=self.server.session.put)
|
||||
|
||||
def section(self):
|
||||
"""Library section.
|
||||
"""
|
||||
return self.server.library.sectionByID(self.librarySectionID)
|
||||
|
||||
|
||||
|
@ -61,17 +88,24 @@ class Movie(Video, Playable):
|
|||
TYPE = 'movie'
|
||||
|
||||
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)
|
||||
self.audienceRating = utils.cast(float, data.attrib.get('audienceRating', NA))
|
||||
self.audienceRating = utils.cast(
|
||||
float, data.attrib.get('audienceRating', NA))
|
||||
self.audienceRatingImage = data.attrib.get('audienceRatingImage', NA)
|
||||
self.chapterSource = data.attrib.get('chapterSource', NA)
|
||||
self.contentRating = data.attrib.get('contentRating', NA)
|
||||
self.duration = utils.cast(int, data.attrib.get('duration', NA))
|
||||
self.guid = data.attrib.get('guid', NA)
|
||||
self.originalTitle = data.attrib.get('originalTitle', 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.primaryExtraKey = data.attrib.get('primaryExtraKey', NA)
|
||||
self.rating = data.attrib.get('rating', NA)
|
||||
self.ratingImage = data.attrib.get('ratingImage', NA)
|
||||
|
@ -80,19 +114,29 @@ class Movie(Video, Playable):
|
|||
self.userRating = utils.cast(float, data.attrib.get('userRating', 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.collections = [media.Collection(self.server, e) for e in data if e.tag == media.Collection.TYPE]
|
||||
self.countries = [media.Country(self.server, e) for e in data if e.tag == media.Country.TYPE]
|
||||
self.directors = [media.Director(self.server, e) for e in data if e.tag == media.Director.TYPE]
|
||||
self.genres = [media.Genre(self.server, e) for e in data if e.tag == media.Genre.TYPE]
|
||||
self.media = [media.Media(self.server, e, self.initpath, self) for e in data if e.tag == media.Media.TYPE]
|
||||
self.producers = [media.Producer(self.server, e) for e in data if e.tag == media.Producer.TYPE]
|
||||
self.roles = [media.Role(self.server, e) for e in data if e.tag == media.Role.TYPE]
|
||||
self.writers = [media.Writer(self.server, e) for e in data if e.tag == media.Writer.TYPE]
|
||||
self.fields = [media.Field(e) for e in data if e.tag == media.Field.TYPE]
|
||||
if self.isFullObject(): # check this
|
||||
self.collections = [media.Collection(
|
||||
self.server, e) for e in data if e.tag == media.Collection.TYPE]
|
||||
self.countries = [media.Country(self.server, e)
|
||||
for e in data if e.tag == media.Country.TYPE]
|
||||
self.directors = [media.Director(
|
||||
self.server, e) for e in data if e.tag == media.Director.TYPE]
|
||||
self.genres = [media.Genre(self.server, e)
|
||||
for e in data if e.tag == media.Genre.TYPE]
|
||||
self.media = [media.Media(self.server, e, self.initpath, self)
|
||||
for e in data if e.tag == media.Media.TYPE]
|
||||
self.producers = [media.Producer(
|
||||
self.server, e) for e in data if e.tag == media.Producer.TYPE]
|
||||
self.roles = [media.Role(self.server, e)
|
||||
for e in data if e.tag == media.Role.TYPE]
|
||||
self.writers = [media.Writer(self.server, e)
|
||||
for e in data if e.tag == media.Writer.TYPE]
|
||||
self.fields = [media.Field(e)
|
||||
for e in data if e.tag == media.Field.TYPE]
|
||||
self.videoStreams = utils.findStreams(self.media, 'videostream')
|
||||
self.audioStreams = utils.findStreams(self.media, 'audiostream')
|
||||
self.subtitleStreams = utils.findStreams(self.media, 'subtitlestream')
|
||||
self.subtitleStreams = utils.findStreams(
|
||||
self.media, 'subtitlestream')
|
||||
|
||||
@property
|
||||
def actors(self):
|
||||
|
@ -108,6 +152,11 @@ class Show(Video):
|
|||
TYPE = 'show'
|
||||
|
||||
def _loadData(self, data):
|
||||
"""Used to set the attributes
|
||||
|
||||
Args:
|
||||
data (Element): Usually built from server.query
|
||||
"""
|
||||
Video._loadData(self, data)
|
||||
self.art = data.attrib.get('art', NA)
|
||||
self.banner = data.attrib.get('banner', NA)
|
||||
|
@ -118,15 +167,19 @@ class Show(Video):
|
|||
self.index = data.attrib.get('index', NA)
|
||||
self.leafCount = utils.cast(int, data.attrib.get('leafCount', NA))
|
||||
self.location = utils.findLocations(data, single=True)
|
||||
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.rating = utils.cast(float, data.attrib.get('rating', NA))
|
||||
self.studio = data.attrib.get('studio', NA)
|
||||
self.theme = data.attrib.get('theme', NA)
|
||||
self.viewedLeafCount = utils.cast(int, data.attrib.get('viewedLeafCount', NA))
|
||||
self.viewedLeafCount = utils.cast(
|
||||
int, data.attrib.get('viewedLeafCount', NA))
|
||||
self.year = utils.cast(int, data.attrib.get('year', NA))
|
||||
if self.isFullObject():
|
||||
self.genres = [media.Genre(self.server, e) for e in data if e.tag == media.Genre.TYPE]
|
||||
self.roles = [media.Role(self.server, e) for e in data if e.tag == media.Role.TYPE]
|
||||
self.genres = [media.Genre(self.server, e)
|
||||
for e in data if e.tag == media.Genre.TYPE]
|
||||
self.roles = [media.Role(self.server, e)
|
||||
for e in data if e.tag == media.Role.TYPE]
|
||||
|
||||
@property
|
||||
def actors(self):
|
||||
|
@ -137,6 +190,8 @@ class Show(Video):
|
|||
return bool(self.viewedLeafCount == self.leafCount)
|
||||
|
||||
def seasons(self):
|
||||
"""Returns a list of Season
|
||||
"""
|
||||
path = '/library/metadata/%s/children' % self.ratingKey
|
||||
return utils.listItems(self.server, path, Season.TYPE)
|
||||
|
||||
|
@ -153,15 +208,21 @@ class Show(Video):
|
|||
return utils.findItem(self.server, path, title)
|
||||
|
||||
def watched(self):
|
||||
"""Return a list of watched episodes
|
||||
"""
|
||||
return self.episodes(watched=True)
|
||||
|
||||
def unwatched(self):
|
||||
"""Return a list of unwatched episodes
|
||||
"""
|
||||
return self.episodes(watched=False)
|
||||
|
||||
def get(self, title):
|
||||
return self.episode(title)
|
||||
|
||||
def refresh(self):
|
||||
"""Refresh the metadata
|
||||
"""
|
||||
self.server.query('/library/metadata/%s/refresh' % self.ratingKey)
|
||||
|
||||
|
||||
|
@ -170,12 +231,18 @@ class Season(Video):
|
|||
TYPE = 'season'
|
||||
|
||||
def _loadData(self, data):
|
||||
"""Used to set the attributes
|
||||
|
||||
Args:
|
||||
data (Element): Usually built from server.query
|
||||
"""
|
||||
Video._loadData(self, data)
|
||||
self.leafCount = utils.cast(int, data.attrib.get('leafCount', NA))
|
||||
self.index = data.attrib.get('index', NA)
|
||||
self.parentKey = data.attrib.get('parentKey', NA)
|
||||
self.parentRatingKey = data.attrib.get('parentRatingKey', NA)
|
||||
self.viewedLeafCount = utils.cast(int, data.attrib.get('viewedLeafCount', NA))
|
||||
self.parentRatingKey = utils.cast(int, data.attrib.get('parentRatingKey', NA))
|
||||
self.viewedLeafCount = utils.cast(
|
||||
int, data.attrib.get('viewedLeafCount', NA))
|
||||
|
||||
@property
|
||||
def isWatched(self):
|
||||
|
@ -186,10 +253,20 @@ class Season(Video):
|
|||
return self.index
|
||||
|
||||
def episodes(self, watched=None):
|
||||
"""Return list of Episode
|
||||
|
||||
Args:
|
||||
watched (None, optional): Description
|
||||
"""
|
||||
childrenKey = '/library/metadata/%s/children' % self.ratingKey
|
||||
return utils.listItems(self.server, childrenKey, watched=watched)
|
||||
|
||||
def episode(self, title):
|
||||
"""Return Episode
|
||||
|
||||
Args:
|
||||
title (TYPE): Description
|
||||
"""
|
||||
path = '/library/metadata/%s/children' % self.ratingKey
|
||||
return utils.findItem(self.server, path, title)
|
||||
|
||||
|
@ -219,27 +296,31 @@ class Episode(Video, Playable):
|
|||
self.duration = utils.cast(int, data.attrib.get('duration', NA))
|
||||
self.grandparentArt = data.attrib.get('grandparentArt', NA)
|
||||
self.grandparentKey = data.attrib.get('grandparentKey', NA)
|
||||
self.grandparentRatingKey = data.attrib.get('grandparentRatingKey', NA)
|
||||
self.grandparentRatingKey = utils.cast(int, data.attrib.get('grandparentRatingKey', NA))
|
||||
self.grandparentTheme = data.attrib.get('grandparentTheme', NA)
|
||||
self.grandparentThumb = data.attrib.get('grandparentThumb', NA)
|
||||
self.grandparentTitle = data.attrib.get('grandparentTitle', NA)
|
||||
self.guid = data.attrib.get('guid', NA)
|
||||
self.index = data.attrib.get('index', 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.parentIndex = data.attrib.get('parentIndex', NA)
|
||||
self.parentKey = data.attrib.get('parentKey', NA)
|
||||
self.parentRatingKey = data.attrib.get('parentRatingKey', NA)
|
||||
self.parentRatingKey = utils.cast(int, data.attrib.get('parentRatingKey', NA))
|
||||
self.parentThumb = data.attrib.get('parentThumb', NA)
|
||||
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) for e in data if e.tag == media.Media.TYPE]
|
||||
self.writers = [media.Writer(self.server, e) for e in data if e.tag == media.Writer.TYPE]
|
||||
self.videoStreams = utils.findStreams(self.media, 'videostream')
|
||||
self.audioStreams = utils.findStreams(self.media, 'audiostream')
|
||||
self.subtitleStreams = utils.findStreams(self.media, 'subtitlestream')
|
||||
#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)
|
||||
for e in data if e.tag == media.Media.TYPE]
|
||||
self.writers = [media.Writer(self.server, e)
|
||||
for e in data if e.tag == media.Writer.TYPE]
|
||||
self.videoStreams = utils.findStreams(self.media, 'videostream')
|
||||
self.audioStreams = utils.findStreams(self.media, 'audiostream')
|
||||
self.subtitleStreams = utils.findStreams(self.media, 'subtitlestream')
|
||||
# data for active sessions and history
|
||||
self.sessionKey = utils.cast(int, data.attrib.get('sessionKey', NA))
|
||||
self.username = utils.findUsername(data)
|
||||
|
|
Loading…
Reference in a new issue