mirror of
https://github.com/pkkid/python-plexapi
synced 2024-11-22 11:43:13 +00:00
Finish documenting utils.py
This commit is contained in:
parent
b61d1888b4
commit
355f4686c9
2 changed files with 136 additions and 186 deletions
|
@ -1,5 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from plexapi import log, utils
|
||||
from plexapi import X_PLEX_CONTAINER_SIZE
|
||||
from plexapi.compat import unquote
|
||||
|
@ -156,7 +155,7 @@ class LibrarySection(object):
|
|||
""" Base class for a single library section.
|
||||
|
||||
Parameters:
|
||||
server (:class:`~plexapi.server.PlexServer`): PlexServer this client is connected to (optional)
|
||||
server (:class:`~plexapi.server.PlexServer`): PlexServer object this library section is from.
|
||||
data (:class:`ElementTree`): Response from PlexServer used to build this object (optional).
|
||||
initpath (str): Relative path requested when retrieving specified `data` (optional).
|
||||
|
||||
|
|
319
plexapi/utils.py
319
plexapi/utils.py
|
@ -7,9 +7,8 @@ from plexapi.exceptions import NotFound, UnknownType, Unsupported
|
|||
from threading import Thread
|
||||
|
||||
# Search Types - Plex uses these to filter specific media types when searching.
|
||||
SEARCHTYPES = {'movie': 1, 'show': 2, 'season': 3,
|
||||
'episode': 4, 'artist': 8, 'album': 9, 'track': 10}
|
||||
|
||||
SEARCHTYPES = {'movie': 1, 'show': 2, 'season': 3, 'episode': 4,
|
||||
'artist': 8, 'album': 9, 'track': 10}
|
||||
LIBRARY_TYPES = {}
|
||||
|
||||
|
||||
|
@ -45,132 +44,102 @@ NA = _NA()
|
|||
|
||||
|
||||
class PlexPartialObject(object):
|
||||
"""Not all objects in the Plex listings return the complete list of elements
|
||||
for the object.This object will allow you to assume each object is complete,
|
||||
and if the specified value you request is None it will fetch the full object
|
||||
automatically and update itself.
|
||||
""" Not all objects in the Plex listings return the complete list of elements
|
||||
for the object. This object will allow you to assume each object is complete,
|
||||
and if the specified value you request is None it will fetch the full object
|
||||
automatically and update itself.
|
||||
|
||||
Attributes:
|
||||
initpath (str): Relative url to PMS
|
||||
server (): Description
|
||||
Attributes:
|
||||
data (:class:`ElementTree`): Response from PlexServer used to build this object (optional).
|
||||
initpath (str): Relative path requested when retrieving specified `data` (optional).
|
||||
server (:class:`~plexapi.server.PlexServer`): PlexServer object this is from.
|
||||
"""
|
||||
|
||||
def __init__(self, data, initpath, server=None):
|
||||
"""
|
||||
Args:
|
||||
data (xml.etree.ElementTree.Element): passed from server.query
|
||||
initpath (str): 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):
|
||||
"""Pretty repr."""
|
||||
clsname = self.__class__.__name__
|
||||
key = self.key.replace('/library/metadata/', '') if self.key else 'NA'
|
||||
title = self.title.replace(' ', '.')[0:20].encode('utf8')
|
||||
return '<%s:%s:%s>' % (clsname, key, title)
|
||||
|
||||
def __getattr__(self, attr):
|
||||
"""Auto reload self, if the attribute is NA
|
||||
|
||||
Args:
|
||||
attr (str): fx key
|
||||
"""
|
||||
# Auto reload self, from the full key (path) when needed.
|
||||
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 (str): fx key
|
||||
value (TYPE): Description
|
||||
"""
|
||||
if value != NA or self.isFullObject():
|
||||
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):
|
||||
""" Retruns True if this is already a full object. A full object means all attributes
|
||||
were populated from the api path representing only this item. For example, the
|
||||
search result for a movie often only contain a portion of the attributes a full
|
||||
object (main url) for that movie contain.
|
||||
"""
|
||||
return not self.key or self.key == self.initpath
|
||||
|
||||
def isPartialObject(self):
|
||||
""" Returns True if this is NOT a full 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: # todo
|
||||
player (Plexclient): Player
|
||||
playlistItemID (int): Playlist item id
|
||||
sessionKey (int): 1223
|
||||
transcodeSession (str): 12312312
|
||||
username (str): Fx Hellowlol
|
||||
viewedAt (datetime): viewed at.
|
||||
Attributes:
|
||||
player (:class:`~plexapi.client.PlexClient`): Client object playing this item (for active sessions).
|
||||
playlistItemID (int): Playlist item ID (only populated for :class:`~plexapi.playlist.Playlist` items).
|
||||
sessionKey (int): Active session key.
|
||||
transcodeSession (:class:`~plexapi.media.TranscodeSession`): Transcode Session object
|
||||
if item is being transcoded (None otherwise).
|
||||
username (str): Username of the person playing this item (for active sessions).
|
||||
viewedAt (datetime): Datetime item was last viewed (history).
|
||||
"""
|
||||
|
||||
def _loadData(self, data):
|
||||
"""Set the class attributes
|
||||
|
||||
Args:
|
||||
data (xml.etree.ElementTree.Element): usually from server.query
|
||||
"""
|
||||
# data for active sessions (/status/sessions)
|
||||
# Load data for active sessions (/status/sessions)
|
||||
self.sessionKey = cast(int, data.attrib.get('sessionKey', NA))
|
||||
self.username = findUsername(data)
|
||||
self.player = findPlayer(self.server, data)
|
||||
self.transcodeSession = findTranscodeSession(self.server, data)
|
||||
# data for history details (/status/sessions/history/all)
|
||||
# Load data for history details (/status/sessions/history/all)
|
||||
self.viewedAt = toDatetime(data.attrib.get('viewedAt', NA))
|
||||
# data for playlist items
|
||||
# Load data for playlist items
|
||||
self.playlistItemID = cast(int, data.attrib.get('playlistItemID', NA))
|
||||
|
||||
def getStreamURL(self, **params):
|
||||
"""Make a stream url that can be used by vlc.
|
||||
""" Returns a stream url that may be used by external applications such as VLC.
|
||||
|
||||
Args:
|
||||
**params (dict): Description
|
||||
Parameters:
|
||||
**params (dict): optional parameters to manipulate the playback when accessing
|
||||
the stream. A few known parameters include: maxVideoBitrate, videoResolution
|
||||
offset, copyts, protocol, mediaIndex, platform.
|
||||
|
||||
Returns:
|
||||
string: ''
|
||||
|
||||
Raises:
|
||||
Unsupported: Raises a error is the type is wrong.
|
||||
Raises:
|
||||
Unsupported: When the item doesn't support fetching a stream URL.
|
||||
"""
|
||||
if self.TYPE not in ('movie', 'episode', 'track'):
|
||||
raise Unsupported(
|
||||
'Fetching stream URL for %s is unsupported.' % self.TYPE)
|
||||
raise Unsupported('Fetching stream URL for %s is unsupported.' % self.TYPE)
|
||||
mvb = params.get('maxVideoBitrate')
|
||||
vr = params.get('videoResolution', '')
|
||||
params = {
|
||||
|
@ -189,32 +158,32 @@ class Playable(object):
|
|||
return self.server.url('/%s/:/transcode/universal/start.m3u8?%s' % (streamtype, urlencode(params)))
|
||||
|
||||
def iterParts(self):
|
||||
"""Yield parts."""
|
||||
""" Iterates over the parts of this media item. """
|
||||
for item in self.media:
|
||||
for part in item.parts:
|
||||
yield part
|
||||
|
||||
def play(self, client):
|
||||
"""Start playback on a client.
|
||||
""" Start playback on the specified client.
|
||||
|
||||
Args:
|
||||
client (PlexClient): The client to start playing on.
|
||||
Parameters:
|
||||
client (:class:`~plexapi.client.PlexClient`): Client to start playing on.
|
||||
"""
|
||||
client.playMedia(self)
|
||||
|
||||
|
||||
def buildItem(server, elem, initpath, bytag=False):
|
||||
"""Build classes used by the plexapi.
|
||||
""" Factory function to build the objects used within the PlexAPI.
|
||||
|
||||
Args:
|
||||
server (Plexserver): Your connected to.
|
||||
elem (xml.etree.ElementTree.Element): xml from PMS
|
||||
initpath (str): Relative path
|
||||
bytag (bool, optional): Description # figure out what this do
|
||||
|
||||
Raises:
|
||||
UnknownType: Unknown library type libtype
|
||||
Parameters:
|
||||
server (:class:`~plexapi.server.PlexServer`): PlexServer object this is from.
|
||||
elem (ElementTree): XML data needed to build the object.
|
||||
initpath (str): Relative path requested when retrieving specified `data` (optional).
|
||||
bytag (bool): Creates the object from the name specified by the tag instead of the
|
||||
default which builds the object specified by the type attribute. <tag type='foo' />
|
||||
|
||||
Raises:
|
||||
UnknownType: Unknown library type.
|
||||
"""
|
||||
libtype = elem.tag if bytag else elem.attrib.get('type')
|
||||
if libtype == 'photo' and elem.tag == 'Directory':
|
||||
|
@ -226,11 +195,11 @@ def buildItem(server, elem, initpath, bytag=False):
|
|||
|
||||
|
||||
def cast(func, value):
|
||||
"""Helper to change to the correct type
|
||||
""" Cast the specified value to the specified type (returned by func).
|
||||
|
||||
Args:
|
||||
func (function): function to used [int, bool float]
|
||||
value (string, int, float): value to cast
|
||||
Parameters:
|
||||
func (func): Calback function to used cast to type (int, bool, float, etc).
|
||||
value (any): value to be cast and returned.
|
||||
"""
|
||||
if value not in [None, NA]:
|
||||
if func == bool:
|
||||
|
@ -245,14 +214,14 @@ 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
|
||||
Parameters:
|
||||
server (:class:`~plexapi.server.PlexServer`): PlexServer object this is from.
|
||||
key (int): ratingKey to find and return.
|
||||
|
||||
Raises:
|
||||
NotFound: Unable to find key. Key
|
||||
Raises:
|
||||
NotFound: Unable to find key
|
||||
"""
|
||||
path = '/library/metadata/{0}'.format(key)
|
||||
try:
|
||||
|
@ -264,15 +233,15 @@ 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 (str): Relative path
|
||||
title (str): Fx 16 blocks
|
||||
Parameters:
|
||||
server (:class:`~plexapi.server.PlexServer`): PlexServer object this is from.
|
||||
path (str): API path that returns item to search title for.
|
||||
title (str): Title of the item to find and return.
|
||||
|
||||
Raises:
|
||||
NotFound: Unable to find item: title
|
||||
Raises:
|
||||
NotFound: Unable to find item.
|
||||
"""
|
||||
for elem in server.query(path):
|
||||
if elem.attrib.get('title').lower() == title.lower():
|
||||
|
@ -281,14 +250,12 @@ def findItem(server, path, title):
|
|||
|
||||
|
||||
def findLocations(data, single=False):
|
||||
"""Extract the path from a location tag
|
||||
""" Returns a list of filepaths from a location tag.
|
||||
|
||||
Args:
|
||||
data (xml.etree.ElementTree.Element): xml from PMS as Element
|
||||
single (bool, optional): Only return one
|
||||
|
||||
Returns:
|
||||
filepath string if single is True else list of filepaths
|
||||
Parameters:
|
||||
data (ElementTree): XML object to search for locations in.
|
||||
single (bool): Set True to only return the first location found.
|
||||
Return type will be a string if this is set to True.
|
||||
"""
|
||||
locations = []
|
||||
for elem in data:
|
||||
|
@ -300,33 +267,26 @@ def findLocations(data, single=False):
|
|||
|
||||
|
||||
def findPlayer(server, data):
|
||||
"""Find a player in a elementthee
|
||||
""" Returns the :class:`~plexapi.client.PlexClient` object found in the specified data.
|
||||
|
||||
Args:
|
||||
server (Plexserver): PMS your connected to
|
||||
data (xml.etree.ElementTree.Element): xml from pms as a element
|
||||
|
||||
Returns:
|
||||
PlexClient or None
|
||||
Parameters:
|
||||
server (:class:`~plexapi.server.PlexServer`): PlexServer object this is from.
|
||||
data (:class:`ElementTree`): XML data to find Player in.
|
||||
"""
|
||||
elem = data.find('Player')
|
||||
if elem is not None:
|
||||
from plexapi.client import PlexClient
|
||||
baseurl = 'http://%s:%s' % (elem.attrib.get('address'),
|
||||
elem.attrib.get('port'))
|
||||
baseurl = 'http://%s:%s' % (elem.attrib.get('address'), elem.attrib.get('port'))
|
||||
return PlexClient(baseurl, server=server, data=elem)
|
||||
return None
|
||||
|
||||
|
||||
def findStreams(media, streamtype):
|
||||
"""Find streams.
|
||||
""" Returns a list of streams (str) found in media that match the specified streamtype.
|
||||
|
||||
Args:
|
||||
media (Show, Movie, Episode): A item where find streams
|
||||
streamtype (str): Possible options [movie, show, episode] # is this correct?
|
||||
|
||||
Returns:
|
||||
list: of streams
|
||||
Parameters:
|
||||
media (:class:`~plexapi.utils.Playable`): Item to search for streams (show, movie, episode).
|
||||
streamtype (str): Streamtype to return (videostream, audiostream, subtitlestream).
|
||||
"""
|
||||
streams = []
|
||||
for mediaitem in media:
|
||||
|
@ -338,14 +298,12 @@ def findStreams(media, streamtype):
|
|||
|
||||
|
||||
def findTranscodeSession(server, data):
|
||||
"""Find transcode session.
|
||||
""" Returns a :class:`~plexapi.media.TranscodeSession` object if found within the specified
|
||||
XML data.
|
||||
|
||||
Args:
|
||||
server (Plexserver): PMS your connected to
|
||||
data (xml.etree.ElementTree.Element): XML response from PMS as Element
|
||||
|
||||
Returns:
|
||||
media.TranscodeSession or None
|
||||
Parameters:
|
||||
server (:class:`~plexapi.server.PlexServer`): PlexServer object this is from.
|
||||
data (:class:`ElementTree`): XML data to find TranscodeSession in.
|
||||
"""
|
||||
|
||||
elem = data.find('TranscodeSession')
|
||||
|
@ -356,13 +314,10 @@ def findTranscodeSession(server, data):
|
|||
|
||||
|
||||
def findUsername(data):
|
||||
"""Find a username in a Element
|
||||
""" Returns the username if found in the specified XML data. Returns None if not found.
|
||||
|
||||
Args:
|
||||
data (xml.etree.ElementTree.Element): XML from PMS as a Element
|
||||
|
||||
Returns:
|
||||
username or None
|
||||
Parameters:
|
||||
data (:class:`ElementTree`): XML data to find username in.
|
||||
"""
|
||||
elem = data.find('User')
|
||||
if elem is not None:
|
||||
|
@ -371,7 +326,7 @@ def findUsername(data):
|
|||
|
||||
|
||||
def isInt(str):
|
||||
"""Check of a string is a int"""
|
||||
""" Returns True if the specified string passes as an int. """
|
||||
try:
|
||||
int(str)
|
||||
return True
|
||||
|
@ -380,14 +335,11 @@ def isInt(str):
|
|||
|
||||
|
||||
def joinArgs(args):
|
||||
"""Builds a query string where only
|
||||
the value is quoted.
|
||||
""" Returns a query string (uses for HTTP URLs) where only the value is URL encoded.
|
||||
Example return value: '?genre=action&type=1337'.
|
||||
|
||||
Args:
|
||||
args (dict): ex {'genre': 'action', 'type': 1337}
|
||||
|
||||
Returns:
|
||||
string: ?genre=action&type=1337
|
||||
Parameters:
|
||||
args (dict): Arguments to include in query string.
|
||||
"""
|
||||
if not args:
|
||||
return ''
|
||||
|
@ -399,30 +351,25 @@ def joinArgs(args):
|
|||
|
||||
|
||||
def listChoices(server, path):
|
||||
"""ListChoices is by _cleanSort etc.
|
||||
""" Returns a dict of {title:key} for all simple choices in a search filter.
|
||||
|
||||
Args:
|
||||
server (Plexserver): Server your connected to
|
||||
path (str): Relative path to PMS
|
||||
|
||||
Returns:
|
||||
dict: title:key
|
||||
Parameters:
|
||||
server (:class:`~plexapi.server.PlexServer`): PlexServer object this is from.
|
||||
path (str): Relative path to request XML data from.
|
||||
"""
|
||||
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.
|
||||
""" Returns a list of object built from :func:`~plexapi.utils.buildItem()` found
|
||||
within the specified path.
|
||||
|
||||
Args:
|
||||
server (Plexserver): PMS your connected to.
|
||||
path (str): Relative path to PMS
|
||||
libtype (None or string, optional): [movie, show, episode, music] # check me
|
||||
watched (None, True, False, optional): Skip or include watched items
|
||||
bytag (bool, optional): Dunno wtf this is used for # todo
|
||||
|
||||
Returns:
|
||||
list: of buildItem
|
||||
Parameters:
|
||||
server (:class:`~plexapi.server.PlexServer`): PlexServer object this is from.
|
||||
path (str): Relative path to request XML data from.
|
||||
libtype (str): Optionally return only the specified library type.
|
||||
watched (bool): Optionally return only watched or unwatched items.
|
||||
bytag (bool): Set true if libtype is found in the XML tag (and not the 'type' attribute).
|
||||
"""
|
||||
items = []
|
||||
for elem in server.query(path):
|
||||
|
@ -440,6 +387,17 @@ def listItems(server, path, libtype=None, watched=None, bytag=False):
|
|||
|
||||
|
||||
def rget(obj, attrstr, default=None, delim='.'):
|
||||
""" Returns the value at the specified attrstr location within a nexted tree of
|
||||
dicts, lists, tuples, functions, classes, etc. The lookup is done recursivley
|
||||
for each key in attrstr (split by by the delimiter) This function is heavily
|
||||
influenced by the lookups used in Django templates.
|
||||
|
||||
Parameters:
|
||||
obj (any): Object to start the lookup in (dict, obj, list, tuple, etc).
|
||||
attrstr (str): String to lookup (ex: 'foo.bar.baz.value')
|
||||
default (any): Default value to return if not found.
|
||||
delim (str): Delimiter separating keys in attrstr.
|
||||
"""
|
||||
try:
|
||||
parts = attrstr.split(delim, 1)
|
||||
attr = parts[0]
|
||||
|
@ -460,19 +418,15 @@ def rget(obj, attrstr, default=None, delim='.'):
|
|||
|
||||
|
||||
def searchType(libtype):
|
||||
"""Map search type name to int using SEACHTYPES
|
||||
Used when querying PMS.
|
||||
""" Returns the integer value of the library string type.
|
||||
|
||||
Args:
|
||||
libtype (str): Possible options see SEARCHTYPES
|
||||
Parameters:
|
||||
libtype (str): Library type to lookup (movie, show, season, episode,
|
||||
artist, album, track)
|
||||
|
||||
Returns:
|
||||
int: fx 1
|
||||
|
||||
Raises:
|
||||
NotFound: Unknown libtype: libtype
|
||||
Raises:
|
||||
NotFound: Unknown libtype
|
||||
"""
|
||||
|
||||
libtype = str(libtype)
|
||||
if libtype in [str(v) for v in SEARCHTYPES.values()]:
|
||||
return libtype
|
||||
|
@ -482,12 +436,12 @@ 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
|
||||
""" Returns the result of <callback> for each set of *args in listargs. Each call
|
||||
to <callback. is called concurrently in their own separate threads.
|
||||
|
||||
Parameters:
|
||||
callback (func): Callback function to apply to each set of *args.
|
||||
listargs (list): List of lists; *args to pass each thread.
|
||||
"""
|
||||
threads, results = [], []
|
||||
for args in listargs:
|
||||
|
@ -501,14 +455,11 @@ def threaded(callback, listargs):
|
|||
|
||||
|
||||
def toDatetime(value, format=None):
|
||||
"""Helper for datetime.
|
||||
""" Returns a datetime object from the specified value.
|
||||
|
||||
Args:
|
||||
value (str): value to use to make datetime
|
||||
format (None, optional): string as strptime.
|
||||
|
||||
Returns:
|
||||
datetime
|
||||
Parameters:
|
||||
value (str): value to return as a datetime
|
||||
format (str): Format to pass strftime (optional; if value is a str).
|
||||
"""
|
||||
if value and value != NA:
|
||||
if format:
|
||||
|
|
Loading…
Reference in a new issue