Update plexapi 3.6.0-tautulli

This commit is contained in:
JonnyWong16 2020-11-27 21:07:30 -08:00
parent 9b0caf2a47
commit d589c57dd2
No known key found for this signature in database
GPG key ID: B1F1F9807184697A
14 changed files with 319 additions and 168 deletions

View file

@ -54,7 +54,7 @@ class AlertListener(threading.Thread):
def stop(self): def stop(self):
""" Stop the AlertListener thread. Once the notifier is stopped, it cannot be directly """ Stop the AlertListener thread. Once the notifier is stopped, it cannot be directly
started again. You must call :func:`plexapi.server.PlexServer.startAlertListener()` started again. You must call :func:`~plexapi.server.PlexServer.startAlertListener`
from a PlexServer instance. from a PlexServer instance.
""" """
log.info('Stopping AlertListener.') log.info('Stopping AlertListener.')

View file

@ -10,6 +10,8 @@ class Audio(PlexPartialObject):
Attributes: Attributes:
addedAt (datetime): Datetime this item was added to the library. addedAt (datetime): Datetime this item was added to the library.
art (str): URL to artwork image.
artBlurHash (str): BlurHash string for artwork image.
index (sting): Index Number (often the track number). index (sting): Index Number (often the track number).
key (str): API URL (/library/metadata/<ratingkey>). key (str): API URL (/library/metadata/<ratingkey>).
lastViewedAt (datetime): Datetime item was last accessed. lastViewedAt (datetime): Datetime item was last accessed.
@ -18,6 +20,7 @@ class Audio(PlexPartialObject):
ratingKey (int): Unique key identifying this item. ratingKey (int): Unique key identifying this item.
summary (str): Summary of the artist, track, or album. summary (str): Summary of the artist, track, or album.
thumb (str): URL to thumbnail image. thumb (str): URL to thumbnail image.
thumbBlurHash (str): BlurHash string for thumbnail image.
title (str): Artist, Album or Track title. (Jason Mraz, We Sing, Lucky, etc.) title (str): Artist, Album or Track title. (Jason Mraz, We Sing, Lucky, etc.)
titleSort (str): Title to use when sorting (defaults to title). titleSort (str): Title to use when sorting (defaults to title).
type (str): 'artist', 'album', or 'track'. type (str): 'artist', 'album', or 'track'.
@ -32,6 +35,8 @@ class Audio(PlexPartialObject):
self._data = data self._data = data
self.listType = 'audio' self.listType = 'audio'
self.addedAt = utils.toDatetime(data.attrib.get('addedAt')) self.addedAt = utils.toDatetime(data.attrib.get('addedAt'))
self.art = data.attrib.get('art')
self.artBlurHash = data.attrib.get('artBlurHash')
self.index = data.attrib.get('index') self.index = data.attrib.get('index')
self.key = data.attrib.get('key') self.key = data.attrib.get('key')
self.lastViewedAt = utils.toDatetime(data.attrib.get('lastViewedAt')) self.lastViewedAt = utils.toDatetime(data.attrib.get('lastViewedAt'))
@ -41,6 +46,7 @@ class Audio(PlexPartialObject):
self.ratingKey = utils.cast(int, data.attrib.get('ratingKey')) self.ratingKey = utils.cast(int, data.attrib.get('ratingKey'))
self.summary = data.attrib.get('summary') self.summary = data.attrib.get('summary')
self.thumb = data.attrib.get('thumb') self.thumb = data.attrib.get('thumb')
self.thumbBlurHash = data.attrib.get('thumbBlurHash')
self.title = data.attrib.get('title') self.title = data.attrib.get('title')
self.titleSort = data.attrib.get('titleSort', self.title) self.titleSort = data.attrib.get('titleSort', self.title)
self.type = data.attrib.get('type') self.type = data.attrib.get('type')
@ -69,20 +75,20 @@ class Audio(PlexPartialObject):
def sync(self, bitrate, client=None, clientId=None, limit=None, title=None): def sync(self, bitrate, client=None, clientId=None, limit=None, title=None):
""" Add current audio (artist, album or track) as sync item for specified device. """ Add current audio (artist, album or track) as sync item for specified device.
See :func:`plexapi.myplex.MyPlexAccount.sync()` for possible exceptions. See :func:`~plexapi.myplex.MyPlexAccount.sync` for possible exceptions.
Parameters: Parameters:
bitrate (int): maximum bitrate for synchronized music, better use one of MUSIC_BITRATE_* values from the bitrate (int): maximum bitrate for synchronized music, better use one of MUSIC_BITRATE_* values from the
module :mod:`plexapi.sync`. module :mod:`~plexapi.sync`.
client (:class:`plexapi.myplex.MyPlexDevice`): sync destination, see client (:class:`~plexapi.myplex.MyPlexDevice`): sync destination, see
:func:`plexapi.myplex.MyPlexAccount.sync`. :func:`~plexapi.myplex.MyPlexAccount.sync`.
clientId (str): sync destination, see :func:`plexapi.myplex.MyPlexAccount.sync`. clientId (str): sync destination, see :func:`~plexapi.myplex.MyPlexAccount.sync`.
limit (int): maximum count of items to sync, unlimited if `None`. limit (int): maximum count of items to sync, unlimited if `None`.
title (str): descriptive title for the new :class:`plexapi.sync.SyncItem`, if empty the value would be title (str): descriptive title for the new :class:`~plexapi.sync.SyncItem`, if empty the value would be
generated from metadata of current media. generated from metadata of current media.
Returns: Returns:
:class:`plexapi.sync.SyncItem`: an instance of created syncItem. :class:`~plexapi.sync.SyncItem`: an instance of created syncItem.
""" """
from plexapi.sync import SyncItem, Policy, MediaSettings from plexapi.sync import SyncItem, Policy, MediaSettings
@ -111,7 +117,6 @@ class Artist(Audio):
Attributes: Attributes:
TAG (str): 'Directory' TAG (str): 'Directory'
TYPE (str): 'artist' TYPE (str): 'artist'
art (str): Artist artwork (/library/metadata/<ratingkey>/art/<artid>)
countries (list): List of :class:`~plexapi.media.Country` objects this artist respresents. countries (list): List of :class:`~plexapi.media.Country` objects this artist respresents.
genres (list): List of :class:`~plexapi.media.Genre` objects this artist respresents. genres (list): List of :class:`~plexapi.media.Genre` objects this artist respresents.
guid (str): Unknown (unique ID; com.plexapp.agents.plexmusic://gracenote/artist/05517B8701668D28?lang=en) guid (str): Unknown (unique ID; com.plexapp.agents.plexmusic://gracenote/artist/05517B8701668D28?lang=en)
@ -122,17 +127,10 @@ class Artist(Audio):
TAG = 'Directory' TAG = 'Directory'
TYPE = 'artist' TYPE = 'artist'
_include = ('?checkFiles=1&includeExtras=1&includeRelated=1'
'&includeOnDeck=1&includeChapters=1&includePopularLeaves=1'
'&includeMarkers=1&includeConcerts=1&includePreferences=1'
'&includeBandwidths=1&includeLoudnessRamps=1')
def _loadData(self, data): def _loadData(self, data):
""" Load attribute values from Plex XML response. """ """ Load attribute values from Plex XML response. """
Audio._loadData(self, data) Audio._loadData(self, data)
self.key = self.key.replace('/children', '') # FIX_BUG_50 self.key = self.key.replace('/children', '') # FIX_BUG_50
self._details_key = self.key + self._include
self.art = data.attrib.get('art')
self.guid = data.attrib.get('guid') self.guid = data.attrib.get('guid')
self.locations = self.listAttrs(data, 'path', etag='Location') self.locations = self.listAttrs(data, 'path', etag='Location')
self.countries = self.findItems(data, media.Country) self.countries = self.findItems(data, media.Country)
@ -187,7 +185,7 @@ class Artist(Audio):
keep_original_name (bool): Set True to keep the original filename as stored in keep_original_name (bool): Set True to keep the original filename as stored in
the Plex server. False will create a new filename with the format the Plex server. False will create a new filename with the format
"<Atrist> - <Album> <Track>". "<Atrist> - <Album> <Track>".
kwargs (dict): If specified, a :func:`~plexapi.audio.Track.getStreamURL()` will kwargs (dict): If specified, a :func:`~plexapi.audio.Track.getStreamURL` will
be returned and the additional arguments passed in will be sent to that be returned and the additional arguments passed in will be sent to that
function. If kwargs is not specified, the media items will be downloaded function. If kwargs is not specified, the media items will be downloaded
and saved to disk. and saved to disk.
@ -206,7 +204,6 @@ class Album(Audio):
Attributes: Attributes:
TAG (str): 'Directory' TAG (str): 'Directory'
TYPE (str): 'album' TYPE (str): 'album'
art (str): Album artwork (/library/metadata/<ratingkey>/art/<artid>)
genres (list): List of :class:`~plexapi.media.Genre` objects this album respresents. genres (list): List of :class:`~plexapi.media.Genre` objects this album respresents.
key (str): API URL (/library/metadata/<ratingkey>). key (str): API URL (/library/metadata/<ratingkey>).
originallyAvailableAt (datetime): Datetime this album was released. originallyAvailableAt (datetime): Datetime this album was released.
@ -227,7 +224,6 @@ class Album(Audio):
def _loadData(self, data): def _loadData(self, data):
""" Load attribute values from Plex XML response. """ """ Load attribute values from Plex XML response. """
Audio._loadData(self, data) Audio._loadData(self, data)
self.art = data.attrib.get('art')
self.guid = data.attrib.get('guid') self.guid = data.attrib.get('guid')
self.leafCount = utils.cast(int, data.attrib.get('leafCount')) self.leafCount = utils.cast(int, data.attrib.get('leafCount'))
self.loudnessAnalysisVersion = utils.cast(int, data.attrib.get('loudnessAnalysisVersion')) self.loudnessAnalysisVersion = utils.cast(int, data.attrib.get('loudnessAnalysisVersion'))
@ -279,7 +275,7 @@ class Album(Audio):
keep_original_name (bool): Set True to keep the original filename as stored in keep_original_name (bool): Set True to keep the original filename as stored in
the Plex server. False will create a new filename with the format the Plex server. False will create a new filename with the format
"<Atrist> - <Album> <Track>". "<Atrist> - <Album> <Track>".
kwargs (dict): If specified, a :func:`~plexapi.audio.Track.getStreamURL()` will kwargs (dict): If specified, a :func:`~plexapi.audio.Track.getStreamURL` will
be returned and the additional arguments passed in will be sent to that be returned and the additional arguments passed in will be sent to that
function. If kwargs is not specified, the media items will be downloaded function. If kwargs is not specified, the media items will be downloaded
and saved to disk. and saved to disk.
@ -301,7 +297,6 @@ class Track(Audio, Playable):
Attributes: Attributes:
TAG (str): 'Directory' TAG (str): 'Directory'
TYPE (str): 'track' TYPE (str): 'track'
art (str): Track artwork (/library/metadata/<ratingkey>/art/<artid>)
chapterSource (TYPE): Unknown chapterSource (TYPE): Unknown
duration (int): Length of this album in seconds. duration (int): Length of this album in seconds.
grandparentArt (str): Album artist artwork. grandparentArt (str): Album artist artwork.
@ -332,17 +327,10 @@ class Track(Audio, Playable):
TAG = 'Track' TAG = 'Track'
TYPE = 'track' TYPE = 'track'
_include = ('?checkFiles=1&includeExtras=1&includeRelated=1'
'&includeOnDeck=1&includeChapters=1&includePopularLeaves=1'
'&includeMarkers=1&includeConcerts=1&includePreferences=1'
'&includeBandwidths=1&includeLoudnessRamps=1')
def _loadData(self, data): def _loadData(self, data):
""" Load attribute values from Plex XML response. """ """ Load attribute values from Plex XML response. """
Audio._loadData(self, data) Audio._loadData(self, data)
Playable._loadData(self, data) Playable._loadData(self, data)
self._details_key = self.key + self._include
self.art = data.attrib.get('art')
self.chapterSource = data.attrib.get('chapterSource') self.chapterSource = data.attrib.get('chapterSource')
self.duration = utils.cast(int, data.attrib.get('duration')) self.duration = utils.cast(int, data.attrib.get('duration'))
self.grandparentArt = data.attrib.get('grandparentArt') self.grandparentArt = data.attrib.get('grandparentArt')

View file

@ -44,9 +44,9 @@ class PlexObject(object):
self._server = server self._server = server
self._data = data self._data = data
self._initpath = initpath or self.key self._initpath = initpath or self.key
self._details_key = ''
if data is not None: if data is not None:
self._loadData(data) self._loadData(data)
self._details_key = self._buildDetailsKey()
def __repr__(self): def __repr__(self):
uid = self._clean(self.firstAttr('_baseurl', 'key', 'id', 'playQueueID', 'uri')) uid = self._clean(self.firstAttr('_baseurl', 'key', 'id', 'playQueueID', 'uri'))
@ -81,7 +81,7 @@ class PlexObject(object):
raise UnknownType("Unknown library type <%s type='%s'../>" % (elem.tag, etype)) raise UnknownType("Unknown library type <%s type='%s'../>" % (elem.tag, etype))
def _buildItemOrNone(self, elem, cls=None, initpath=None): def _buildItemOrNone(self, elem, cls=None, initpath=None):
""" Calls :func:`~plexapi.base.PlexObject._buildItem()` but returns """ Calls :func:`~plexapi.base.PlexObject._buildItem` but returns
None if elem is an unknown type. None if elem is an unknown type.
""" """
try: try:
@ -89,6 +89,22 @@ class PlexObject(object):
except UnknownType: except UnknownType:
return None return None
def _buildDetailsKey(self, **kwargs):
""" Builds the details key with the XML include parameters.
All parameters are included by default with the option to override each parameter
or disable each parameter individually by setting it to False or 0.
"""
details_key = self.key
if hasattr(self, '_INCLUDES'):
includes = {}
for k, v in self._INCLUDES.items():
value = kwargs.get(k, v)
if value not in [False, 0, '0']:
includes[k] = 1 if value is True else value
if includes:
details_key += '?' + urlencode(sorted(includes.items()))
return details_key
def fetchItem(self, ekey, cls=None, **kwargs): def fetchItem(self, ekey, cls=None, **kwargs):
""" Load the specified key to find and build the first item with the """ Load the specified key to find and build the first item with the
specified tag and attrs. If no tag or attrs are specified then specified tag and attrs. If no tag or attrs are specified then
@ -203,9 +219,39 @@ class PlexObject(object):
results.append(elem.attrib.get(attr)) results.append(elem.attrib.get(attr))
return results return results
def reload(self, key=None): def reload(self, key=None, **kwargs):
""" Reload the data for this object from self.key. """ """ Reload the data for this object from self.key.
key = key or self._details_key or self.key
Parameters:
key (string, optional): Override the key to reload.
**kwargs (dict): A dictionary of XML include parameters to exclude or override.
All parameters are included by default with the option to override each parameter
or disable each parameter individually by setting it to False or 0.
See :class:`~plexapi.base.PlexPartialObject` for all the available include parameters.
Example:
.. code-block:: python
from plexapi.server import PlexServer
plex = PlexServer('http://localhost:32400', token='xxxxxxxxxxxxxxxxxxxx')
movie = plex.library.section('Movies').get('Cars')
# Partial reload of the movie without the `checkFiles` parameter.
# Excluding `checkFiles` will prevent the Plex server from reading the
# file to check if the file still exists and is accessible.
# The movie object will remain as a partial object.
movie.reload(checkFiles=False)
movie.isPartialObject() # Returns True
# Full reload of the movie with all include parameters.
# The movie object will be a full object.
movie.reload()
movie.isFullObject() # Returns True
"""
details_key = self._buildDetailsKey(**kwargs) if kwargs else self._details_key
key = key or details_key or self.key
if not key: if not key:
raise Unsupported('Cannot reload an object not built from a URL.') raise Unsupported('Cannot reload an object not built from a URL.')
self._initpath = key self._initpath = key
@ -281,6 +327,27 @@ class PlexPartialObject(PlexObject):
and if the specified value you request is None it will fetch the full object and if the specified value you request is None it will fetch the full object
automatically and update itself. automatically and update itself.
""" """
_INCLUDES = {
'checkFiles': 1,
'includeAllConcerts': 1,
'includeBandwidths': 1,
'includeChapters': 1,
'includeChildren': 1,
'includeConcerts': 1,
'includeExternalMedia': 1,
'includeExtras': 1,
'includeFields': 'thumbBlurHash,artBlurHash',
'includeGeolocation': 1,
'includeLoudnessRamps': 1,
'includeMarkers': 1,
'includeOnDeck': 1,
'includePopularLeaves': 1,
'includePreferences': 1,
'includeRelated': 1,
'includeRelatedCount': 1,
'includeReviews': 1,
'includeStations': 1
}
def __eq__(self, other): def __eq__(self, other):
return other is not None and self.key == other.key return other is not None and self.key == other.key
@ -332,7 +399,7 @@ class PlexPartialObject(PlexObject):
""" Retruns True if this is already a full object. A full object means all attributes """ 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 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 search result for a movie often only contain a portion of the attributes a full
object (main url) for that movie contain. object (main url) for that movie would contain.
""" """
return not self.key or (self._details_key or self.key) == self._initpath return not self.key or (self._details_key or self.key) == self._initpath
@ -608,14 +675,6 @@ class Playable(object):
self.accountID = utils.cast(int, data.attrib.get('accountID')) # history self.accountID = utils.cast(int, data.attrib.get('accountID')) # history
self.playlistItemID = utils.cast(int, data.attrib.get('playlistItemID')) # playlist self.playlistItemID = utils.cast(int, data.attrib.get('playlistItemID')) # playlist
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 self._details_key == self._initpath or not self.key
def getStreamURL(self, **params): def getStreamURL(self, **params):
""" Returns a stream url that may be used by external applications such as VLC. """ Returns a stream url that may be used by external applications such as VLC.
@ -625,7 +684,7 @@ class Playable(object):
offset, copyts, protocol, mediaIndex, platform. offset, copyts, protocol, mediaIndex, platform.
Raises: Raises:
:class:`plexapi.exceptions.Unsupported`: When the item doesn't support fetching a stream URL. :exc:`plexapi.exceptions.Unsupported`: When the item doesn't support fetching a stream URL.
""" """
if self.TYPE not in ('movie', 'episode', 'track'): 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)
@ -690,7 +749,7 @@ class Playable(object):
keep_original_name (bool): Set True to keep the original filename as stored in keep_original_name (bool): Set True to keep the original filename as stored in
the Plex server. False will create a new filename with the format the Plex server. False will create a new filename with the format
"<Artist> - <Album> <Track>". "<Artist> - <Album> <Track>".
kwargs (dict): If specified, a :func:`~plexapi.audio.Track.getStreamURL()` will kwargs (dict): If specified, a :func:`~plexapi.audio.Track.getStreamURL` will
be returned and the additional arguments passed in will be sent to that be returned and the additional arguments passed in will be sent to that
function. If kwargs is not specified, the media items will be downloaded function. If kwargs is not specified, the media items will be downloaded
and saved to disk. and saved to disk.

View file

@ -53,7 +53,7 @@ class PlexClient(PlexObject):
_token (str): Token used to access this client. _token (str): Token used to access this client.
_session (obj): Requests session object used to access this client. _session (obj): Requests session object used to access this client.
_proxyThroughServer (bool): Set to True after calling _proxyThroughServer (bool): Set to True after calling
:func:`~plexapi.client.PlexClient.proxyThroughServer()` (default False). :func:`~plexapi.client.PlexClient.proxyThroughServer` (default False).
""" """
TAG = 'Player' TAG = 'Player'
key = '/resources' key = '/resources'
@ -138,7 +138,7 @@ class PlexClient(PlexObject):
value (bool): Enable or disable proxying (optional, default True). value (bool): Enable or disable proxying (optional, default True).
Raises: Raises:
:class:`plexapi.exceptions.Unsupported`: Cannot use client proxy with unknown server. :exc:`plexapi.exceptions.Unsupported`: Cannot use client proxy with unknown server.
""" """
if server: if server:
self._server = server self._server = server
@ -171,7 +171,7 @@ class PlexClient(PlexObject):
return ElementTree.fromstring(data) if data.strip() else None return ElementTree.fromstring(data) if data.strip() else None
def sendCommand(self, command, proxy=None, **params): def sendCommand(self, command, proxy=None, **params):
""" Convenience wrapper around :func:`~plexapi.client.PlexClient.query()` to more easily """ Convenience wrapper around :func:`~plexapi.client.PlexClient.query` to more easily
send simple commands to the client. Returns an ElementTree object containing send simple commands to the client. Returns an ElementTree object containing
the response. the response.
@ -181,7 +181,7 @@ class PlexClient(PlexObject):
**params (dict): Additional GET parameters to include with the command. **params (dict): Additional GET parameters to include with the command.
Raises: Raises:
:class:`plexapi.exceptions.Unsupported`: When we detect the client doesn't support this capability. :exc:`plexapi.exceptions.Unsupported`: When we detect the client doesn't support this capability.
""" """
command = command.strip('/') command = command.strip('/')
controller = command.split('/')[0] controller = command.split('/')[0]
@ -296,7 +296,7 @@ class PlexClient(PlexObject):
**params (dict): Additional GET parameters to include with the command. **params (dict): Additional GET parameters to include with the command.
Raises: Raises:
:class:`plexapi.exceptions.Unsupported`: When no PlexServer specified in this object. :exc:`plexapi.exceptions.Unsupported`: When no PlexServer specified in this object.
""" """
if not self._server: if not self._server:
raise Unsupported('A server must be specified before using this command.') raise Unsupported('A server must be specified before using this command.')
@ -466,7 +466,7 @@ class PlexClient(PlexObject):
also: https://github.com/plexinc/plex-media-player/wiki/Remote-control-API#modified-commands also: https://github.com/plexinc/plex-media-player/wiki/Remote-control-API#modified-commands
Raises: Raises:
:class:`plexapi.exceptions.Unsupported`: When no PlexServer specified in this object. :exc:`plexapi.exceptions.Unsupported`: When no PlexServer specified in this object.
""" """
if not self._server: if not self._server:
raise Unsupported('A server must be specified before using this command.') raise Unsupported('A server must be specified before using this command.')

View file

@ -455,7 +455,7 @@ class LibrarySection(PlexObject):
return self.fetchItems(key, **kwargs) return self.fetchItems(key, **kwargs)
def agents(self): def agents(self):
""" Returns a list of available `:class:`~plexapi.media.Agent` for this library section. """ Returns a list of available :class:`~plexapi.media.Agent` for this library section.
""" """
return self._server.agents(utils.searchType(self.type)) return self._server.agents(utils.searchType(self.type))
@ -517,7 +517,7 @@ class LibrarySection(PlexObject):
def listChoices(self, category, libtype=None, **kwargs): def listChoices(self, category, libtype=None, **kwargs):
""" Returns a list of :class:`~plexapi.library.FilterChoice` objects for the """ Returns a list of :class:`~plexapi.library.FilterChoice` objects for the
specified category and libtype. kwargs can be any of the same kwargs in specified category and libtype. kwargs can be any of the same kwargs in
:func:`plexapi.library.LibraySection.search()` to help narrow down the choices :func:`~plexapi.library.LibraySection.search` to help narrow down the choices
to only those that matter in your current context. to only those that matter in your current context.
Parameters: Parameters:
@ -526,7 +526,7 @@ class LibrarySection(PlexObject):
**kwargs (dict): Additional kwargs to narrow down the choices. **kwargs (dict): Additional kwargs to narrow down the choices.
Raises: Raises:
:class:`plexapi.exceptions.BadRequest`: Cannot include kwarg equal to specified category. :exc:`plexapi.exceptions.BadRequest`: Cannot include kwarg equal to specified category.
""" """
# TODO: Should this be moved to base? # TODO: Should this be moved to base?
if category in kwargs: if category in kwargs:
@ -573,7 +573,7 @@ class LibrarySection(PlexObject):
* year: List of years to search within ([yyyy, ...]). [all] * year: List of years to search within ([yyyy, ...]). [all]
Raises: Raises:
:class:`plexapi.exceptions.BadRequest`: when applying unknown filter :exc:`plexapi.exceptions.BadRequest`: when applying unknown filter
""" """
# cleanup the core arguments # cleanup the core arguments
args = {} args = {}
@ -659,20 +659,20 @@ class LibrarySection(PlexObject):
def sync(self, policy, mediaSettings, client=None, clientId=None, title=None, sort=None, libtype=None, def sync(self, policy, mediaSettings, client=None, clientId=None, title=None, sort=None, libtype=None,
**kwargs): **kwargs):
""" Add current library section as sync item for specified device. """ Add current library section as sync item for specified device.
See description of :func:`~plexapi.library.LibrarySection.search()` for details about filtering / sorting See description of :func:`~plexapi.library.LibrarySection.search` for details about filtering / sorting
and :func:`plexapi.myplex.MyPlexAccount.sync()` for possible exceptions. and :func:`~plexapi.myplex.MyPlexAccount.sync` for possible exceptions.
Parameters: Parameters:
policy (:class:`plexapi.sync.Policy`): policy of syncing the media (how many items to sync and process policy (:class:`~plexapi.sync.Policy`): policy of syncing the media (how many items to sync and process
watched media or not), generated automatically when method watched media or not), generated automatically when method
called on specific LibrarySection object. called on specific LibrarySection object.
mediaSettings (:class:`plexapi.sync.MediaSettings`): Transcoding settings used for the media, generated mediaSettings (:class:`~plexapi.sync.MediaSettings`): Transcoding settings used for the media, generated
automatically when method called on specific automatically when method called on specific
LibrarySection object. LibrarySection object.
client (:class:`plexapi.myplex.MyPlexDevice`): sync destination, see client (:class:`~plexapi.myplex.MyPlexDevice`): sync destination, see
:func:`plexapi.myplex.MyPlexAccount.sync`. :func:`~plexapi.myplex.MyPlexAccount.sync`.
clientId (str): sync destination, see :func:`plexapi.myplex.MyPlexAccount.sync`. clientId (str): sync destination, see :func:`~plexapi.myplex.MyPlexAccount.sync`.
title (str): descriptive title for the new :class:`plexapi.sync.SyncItem`, if empty the value would be title (str): descriptive title for the new :class:`~plexapi.sync.SyncItem`, if empty the value would be
generated from metadata of current media. generated from metadata of current media.
sort (str): formatted as `column:dir`; column can be any of {`addedAt`, `originallyAvailableAt`, sort (str): formatted as `column:dir`; column can be any of {`addedAt`, `originallyAvailableAt`,
`lastViewedAt`, `titleSort`, `rating`, `mediaHeight`, `duration`}. dir can be `asc` or `lastViewedAt`, `titleSort`, `rating`, `mediaHeight`, `duration`}. dir can be `asc` or
@ -681,10 +681,10 @@ class LibrarySection(PlexObject):
`track`). `track`).
Returns: Returns:
:class:`plexapi.sync.SyncItem`: an instance of created syncItem. :class:`~plexapi.sync.SyncItem`: an instance of created syncItem.
Raises: Raises:
:class:`plexapi.exceptions.BadRequest`: when the library is not allowed to sync :exc:`plexapi.exceptions.BadRequest`: when the library is not allowed to sync
Example: Example:
@ -784,17 +784,17 @@ class MovieSection(LibrarySection):
def sync(self, videoQuality, limit=None, unwatched=False, **kwargs): def sync(self, videoQuality, limit=None, unwatched=False, **kwargs):
""" Add current Movie library section as sync item for specified device. """ Add current Movie library section as sync item for specified device.
See description of :func:`plexapi.library.LibrarySection.search()` for details about filtering / sorting and See description of :func:`~plexapi.library.LibrarySection.search` for details about filtering / sorting and
:func:`plexapi.library.LibrarySection.sync()` for details on syncing libraries and possible exceptions. :func:`~plexapi.library.LibrarySection.sync` for details on syncing libraries and possible exceptions.
Parameters: Parameters:
videoQuality (int): idx of quality of the video, one of VIDEO_QUALITY_* values defined in videoQuality (int): idx of quality of the video, one of VIDEO_QUALITY_* values defined in
:mod:`plexapi.sync` module. :mod:`~plexapi.sync` module.
limit (int): maximum count of movies to sync, unlimited if `None`. limit (int): maximum count of movies to sync, unlimited if `None`.
unwatched (bool): if `True` watched videos wouldn't be synced. unwatched (bool): if `True` watched videos wouldn't be synced.
Returns: Returns:
:class:`plexapi.sync.SyncItem`: an instance of created syncItem. :class:`~plexapi.sync.SyncItem`: an instance of created syncItem.
Example: Example:
@ -843,11 +843,11 @@ class ShowSection(LibrarySection):
CONTENT_TYPE = 'video' CONTENT_TYPE = 'video'
def searchShows(self, **kwargs): def searchShows(self, **kwargs):
""" Search for a show. See :func:`~plexapi.library.LibrarySection.search()` for usage. """ """ Search for a show. See :func:`~plexapi.library.LibrarySection.search` for usage. """
return self.search(libtype='show', **kwargs) return self.search(libtype='show', **kwargs)
def searchEpisodes(self, **kwargs): def searchEpisodes(self, **kwargs):
""" Search for an episode. See :func:`~plexapi.library.LibrarySection.search()` for usage. """ """ Search for an episode. See :func:`~plexapi.library.LibrarySection.search` for usage. """
return self.search(libtype='episode', **kwargs) return self.search(libtype='episode', **kwargs)
def recentlyAdded(self, libtype='episode', maxresults=50): def recentlyAdded(self, libtype='episode', maxresults=50):
@ -877,17 +877,17 @@ class ShowSection(LibrarySection):
def sync(self, videoQuality, limit=None, unwatched=False, **kwargs): def sync(self, videoQuality, limit=None, unwatched=False, **kwargs):
""" Add current Show library section as sync item for specified device. """ Add current Show library section as sync item for specified device.
See description of :func:`plexapi.library.LibrarySection.search()` for details about filtering / sorting and See description of :func:`~plexapi.library.LibrarySection.search` for details about filtering / sorting and
:func:`plexapi.library.LibrarySection.sync()` for details on syncing libraries and possible exceptions. :func:`~plexapi.library.LibrarySection.sync` for details on syncing libraries and possible exceptions.
Parameters: Parameters:
videoQuality (int): idx of quality of the video, one of VIDEO_QUALITY_* values defined in videoQuality (int): idx of quality of the video, one of VIDEO_QUALITY_* values defined in
:mod:`plexapi.sync` module. :mod:`~plexapi.sync` module.
limit (int): maximum count of episodes to sync, unlimited if `None`. limit (int): maximum count of episodes to sync, unlimited if `None`.
unwatched (bool): if `True` watched videos wouldn't be synced. unwatched (bool): if `True` watched videos wouldn't be synced.
Returns: Returns:
:class:`plexapi.sync.SyncItem`: an instance of created syncItem. :class:`~plexapi.sync.SyncItem`: an instance of created syncItem.
Example: Example:
@ -941,15 +941,15 @@ class MusicSection(LibrarySection):
return self.fetchItems(key) return self.fetchItems(key)
def searchArtists(self, **kwargs): def searchArtists(self, **kwargs):
""" Search for an artist. See :func:`~plexapi.library.LibrarySection.search()` for usage. """ """ Search for an artist. See :func:`~plexapi.library.LibrarySection.search` for usage. """
return self.search(libtype='artist', **kwargs) return self.search(libtype='artist', **kwargs)
def searchAlbums(self, **kwargs): def searchAlbums(self, **kwargs):
""" Search for an album. See :func:`~plexapi.library.LibrarySection.search()` for usage. """ """ Search for an album. See :func:`~plexapi.library.LibrarySection.search` for usage. """
return self.search(libtype='album', **kwargs) return self.search(libtype='album', **kwargs)
def searchTracks(self, **kwargs): def searchTracks(self, **kwargs):
""" Search for a track. See :func:`~plexapi.library.LibrarySection.search()` for usage. """ """ Search for a track. See :func:`~plexapi.library.LibrarySection.search` for usage. """
return self.search(libtype='track', **kwargs) return self.search(libtype='track', **kwargs)
def all(self, libtype='artist', **kwargs): def all(self, libtype='artist', **kwargs):
@ -971,16 +971,16 @@ class MusicSection(LibrarySection):
def sync(self, bitrate, limit=None, **kwargs): def sync(self, bitrate, limit=None, **kwargs):
""" Add current Music library section as sync item for specified device. """ Add current Music library section as sync item for specified device.
See description of :func:`plexapi.library.LibrarySection.search()` for details about filtering / sorting and See description of :func:`~plexapi.library.LibrarySection.search` for details about filtering / sorting and
:func:`plexapi.library.LibrarySection.sync()` for details on syncing libraries and possible exceptions. :func:`~plexapi.library.LibrarySection.sync` for details on syncing libraries and possible exceptions.
Parameters: Parameters:
bitrate (int): maximum bitrate for synchronized music, better use one of MUSIC_BITRATE_* values from the bitrate (int): maximum bitrate for synchronized music, better use one of MUSIC_BITRATE_* values from the
module :mod:`plexapi.sync`. module :mod:`~plexapi.sync`.
limit (int): maximum count of tracks to sync, unlimited if `None`. limit (int): maximum count of tracks to sync, unlimited if `None`.
Returns: Returns:
:class:`plexapi.sync.SyncItem`: an instance of created syncItem. :class:`~plexapi.sync.SyncItem`: an instance of created syncItem.
Example: Example:
@ -1023,11 +1023,11 @@ class PhotoSection(LibrarySection):
METADATA_TYPE = 'photo' METADATA_TYPE = 'photo'
def searchAlbums(self, title, **kwargs): def searchAlbums(self, title, **kwargs):
""" Search for an album. See :func:`~plexapi.library.LibrarySection.search()` for usage. """ """ Search for an album. See :func:`~plexapi.library.LibrarySection.search` for usage. """
return self.search(libtype='photoalbum', title=title, **kwargs) return self.search(libtype='photoalbum', title=title, **kwargs)
def searchPhotos(self, title, **kwargs): def searchPhotos(self, title, **kwargs):
""" Search for a photo. See :func:`~plexapi.library.LibrarySection.search()` for usage. """ """ Search for a photo. See :func:`~plexapi.library.LibrarySection.search` for usage. """
return self.search(libtype='photo', title=title, **kwargs) return self.search(libtype='photo', title=title, **kwargs)
def all(self, libtype='photoalbum', **kwargs): def all(self, libtype='photoalbum', **kwargs):
@ -1043,16 +1043,16 @@ class PhotoSection(LibrarySection):
def sync(self, resolution, limit=None, **kwargs): def sync(self, resolution, limit=None, **kwargs):
""" Add current Music library section as sync item for specified device. """ Add current Music library section as sync item for specified device.
See description of :func:`plexapi.library.LibrarySection.search()` for details about filtering / sorting and See description of :func:`~plexapi.library.LibrarySection.search` for details about filtering / sorting and
:func:`plexapi.library.LibrarySection.sync()` for details on syncing libraries and possible exceptions. :func:`~plexapi.library.LibrarySection.sync` for details on syncing libraries and possible exceptions.
Parameters: Parameters:
resolution (str): maximum allowed resolution for synchronized photos, see PHOTO_QUALITY_* values in the resolution (str): maximum allowed resolution for synchronized photos, see PHOTO_QUALITY_* values in the
module :mod:`plexapi.sync`. module :mod:`~plexapi.sync`.
limit (int): maximum count of tracks to sync, unlimited if `None`. limit (int): maximum count of tracks to sync, unlimited if `None`.
Returns: Returns:
:class:`plexapi.sync.SyncItem`: an instance of created syncItem. :class:`~plexapi.sync.SyncItem`: an instance of created syncItem.
Example: Example:
@ -1079,7 +1079,7 @@ class PhotoSection(LibrarySection):
class FilterChoice(PlexObject): class FilterChoice(PlexObject):
""" Represents a single filter choice. These objects are gathered when using filters """ Represents a single filter choice. These objects are gathered when using filters
while searching for library items and is the object returned in the result set of while searching for library items and is the object returned in the result set of
:func:`~plexapi.library.LibrarySection.listChoices()`. :func:`~plexapi.library.LibrarySection.listChoices`.
Attributes: Attributes:
TAG (str): 'Directory' TAG (str): 'Directory'
@ -1140,6 +1140,8 @@ class Collections(PlexPartialObject):
TYPE (str): 'collection' TYPE (str): 'collection'
ratingKey (int): Unique key identifying this item. ratingKey (int): Unique key identifying this item.
addedAt (datetime): Datetime this item was added to the library. addedAt (datetime): Datetime this item was added to the library.
art (str): URL to artwork image.
artBlurHash (str): BlurHash string for artwork image.
childCount (int): Count of child object(s) childCount (int): Count of child object(s)
collectionMode (str): How the items in the collection are displayed. collectionMode (str): How the items in the collection are displayed.
collectionSort (str): How to sort the items in the collection. collectionSort (str): How to sort the items in the collection.
@ -1157,6 +1159,7 @@ class Collections(PlexPartialObject):
subtype (str): Media type subtype (str): Media type
summary (str): Summary of the collection summary (str): Summary of the collection
thumb (str): URL to thumbnail image. thumb (str): URL to thumbnail image.
thumbBlurHash (str): BlurHash string for thumbnail image.
title (str): Collection Title title (str): Collection Title
titleSort (str): Title to use when sorting (defaults to title). titleSort (str): Title to use when sorting (defaults to title).
type (str): Hardcoded 'collection' type (str): Hardcoded 'collection'
@ -1165,14 +1168,13 @@ class Collections(PlexPartialObject):
TAG = 'Directory' TAG = 'Directory'
TYPE = 'collection' TYPE = 'collection'
_include = "?includeExternalMedia=1&includePreferences=1"
def _loadData(self, data): def _loadData(self, data):
self.ratingKey = utils.cast(int, data.attrib.get('ratingKey')) self.ratingKey = utils.cast(int, data.attrib.get('ratingKey'))
self.key = data.attrib.get('key').replace('/children', '') # FIX_BUG_50 self.key = data.attrib.get('key').replace('/children', '') # FIX_BUG_50
self._details_key = self.key + self._include
self.addedAt = utils.toDatetime(data.attrib.get('addedAt')) self.addedAt = utils.toDatetime(data.attrib.get('addedAt'))
self.art = data.attrib.get('art') self.art = data.attrib.get('art')
self.artBlurHash = data.attrib.get('artBlurHash')
self.childCount = utils.cast(int, data.attrib.get('childCount')) self.childCount = utils.cast(int, data.attrib.get('childCount'))
self.collectionMode = utils.cast(int, data.attrib.get('collectionMode')) self.collectionMode = utils.cast(int, data.attrib.get('collectionMode'))
self.collectionSort = utils.cast(int, data.attrib.get('collectionSort')) self.collectionSort = utils.cast(int, data.attrib.get('collectionSort'))
@ -1189,6 +1191,7 @@ class Collections(PlexPartialObject):
self.subtype = data.attrib.get('subtype') self.subtype = data.attrib.get('subtype')
self.summary = data.attrib.get('summary') self.summary = data.attrib.get('summary')
self.thumb = data.attrib.get('thumb') self.thumb = data.attrib.get('thumb')
self.thumbBlurHash = data.attrib.get('thumbBlurHash')
self.title = data.attrib.get('title') self.title = data.attrib.get('title')
self.titleSort = data.attrib.get('titleSort') self.titleSort = data.attrib.get('titleSort')
self.type = data.attrib.get('type') self.type = data.attrib.get('type')
@ -1226,7 +1229,7 @@ class Collections(PlexPartialObject):
collection = 'plexapi.library.Collections' collection = 'plexapi.library.Collections'
collection.updateMode(mode="hide") collection.updateMode(mode="hide")
""" """
mode_dict = {'default': '-2', mode_dict = {'default': '-1',
'hide': '0', 'hide': '0',
'hideItems': '1', 'hideItems': '1',
'showItems': '2'} 'showItems': '2'}
@ -1293,3 +1296,54 @@ class Collections(PlexPartialObject):
# def edit(self, **kwargs): # def edit(self, **kwargs):
# TODO # TODO
@utils.registerPlexObject
class Path(PlexObject):
""" Represents a single directory Path.
Attributes:
TAG (str): 'Path'
home (bool): True if the path is the home directory
key (str): API URL (/services/browse/<base64path>)
network (bool): True if path is a network location
path (str): Full path to folder
title (str): Folder name
"""
TAG = 'Path'
def _loadData(self, data):
self.home = utils.cast(bool, data.attrib.get('home'))
self.key = data.attrib.get('key')
self.network = utils.cast(bool, data.attrib.get('network'))
self.path = data.attrib.get('path')
self.title = data.attrib.get('title')
def browse(self, includeFiles=True):
""" Alias for :func:`~plexapi.server.PlexServer.browse`. """
return self._server.browse(self, includeFiles)
def walk(self):
""" Alias for :func:`~plexapi.server.PlexServer.walk`. """
for path, paths, files in self._server.walk(self):
yield path, paths, files
@utils.registerPlexObject
class File(PlexObject):
""" Represents a single File.
Attributes:
TAG (str): 'File'
key (str): API URL (/services/browse/<base64path>)
path (str): Full path to file
title (str): File name
"""
TAG = 'File'
def _loadData(self, data):
self.key = data.attrib.get('key')
self.path = data.attrib.get('path')
self.title = data.attrib.get('title')

View file

@ -562,7 +562,7 @@ class MediaTag(PlexObject):
tag (str): Name of the tag. This will be Animation, SciFi etc for Genres. The name of tag (str): Name of the tag. This will be Animation, SciFi etc for Genres. The name of
person for Directors and Roles (ex: Animation, Stephen Graham, etc). person for Directors and Roles (ex: Animation, Stephen Graham, etc).
<Hub_Search_Attributes>: Attributes only applicable in search results from <Hub_Search_Attributes>: Attributes only applicable in search results from
PlexServer :func:`~plexapi.server.PlexServer.search()`. They provide details of which PlexServer :func:`~plexapi.server.PlexServer.search`. They provide details of which
library section the tag was found as well as the url to dig deeper into the results. library section the tag was found as well as the url to dig deeper into the results.
* key (str): API URL to dig deeper into this tag (ex: /library/sections/1/all?actor=9081). * key (str): API URL to dig deeper into this tag (ex: /library/sections/1/all?actor=9081).
@ -589,7 +589,7 @@ class MediaTag(PlexObject):
def items(self, *args, **kwargs): def items(self, *args, **kwargs):
""" Return the list of items within this tag. This function is only applicable """ Return the list of items within this tag. This function is only applicable
in search results from PlexServer :func:`~plexapi.server.PlexServer.search()`. in search results from PlexServer :func:`~plexapi.server.PlexServer.search`.
""" """
if not self.key: if not self.key:
raise BadRequest('Key is not defined for this tag: %s' % self.tag) raise BadRequest('Key is not defined for this tag: %s' % self.tag)

View file

@ -544,7 +544,7 @@ class MyPlexAccount(PlexObject):
return self.query(url, method=self._session.put, data=params) return self.query(url, method=self._session.put, data=params)
def syncItems(self, client=None, clientId=None): def syncItems(self, client=None, clientId=None):
""" Returns an instance of :class:`plexapi.sync.SyncList` for specified client. """ Returns an instance of :class:`~plexapi.sync.SyncList` for specified client.
Parameters: Parameters:
client (:class:`~plexapi.myplex.MyPlexDevice`): a client to query SyncItems for. client (:class:`~plexapi.myplex.MyPlexDevice`): a client to query SyncItems for.
@ -564,22 +564,22 @@ class MyPlexAccount(PlexObject):
def sync(self, sync_item, client=None, clientId=None): def sync(self, sync_item, client=None, clientId=None):
""" Adds specified sync item for the client. It's always easier to use methods defined directly in the media """ Adds specified sync item for the client. It's always easier to use methods defined directly in the media
objects, e.g. :func:`plexapi.video.Video.sync`, :func:`plexapi.audio.Audio.sync`. objects, e.g. :func:`~plexapi.video.Video.sync`, :func:`~plexapi.audio.Audio.sync`.
Parameters: Parameters:
client (:class:`~plexapi.myplex.MyPlexDevice`): a client for which you need to add SyncItem to. client (:class:`~plexapi.myplex.MyPlexDevice`): a client for which you need to add SyncItem to.
clientId (str): an identifier of a client for which you need to add SyncItem to. clientId (str): an identifier of a client for which you need to add SyncItem to.
sync_item (:class:`plexapi.sync.SyncItem`): prepared SyncItem object with all fields set. sync_item (:class:`~plexapi.sync.SyncItem`): prepared SyncItem object with all fields set.
If both `client` and `clientId` provided the client would be preferred. If both `client` and `clientId` provided the client would be preferred.
If neither `client` nor `clientId` provided the clientId would be set to current clients`s identifier. If neither `client` nor `clientId` provided the clientId would be set to current clients`s identifier.
Returns: Returns:
:class:`plexapi.sync.SyncItem`: an instance of created syncItem. :class:`~plexapi.sync.SyncItem`: an instance of created syncItem.
Raises: Raises:
:class:`plexapi.exceptions.BadRequest`: when client with provided clientId wasn`t found. :exc:`plexapi.exceptions.BadRequest`: when client with provided clientId wasn`t found.
:class:`plexapi.exceptions.BadRequest`: provided client doesn`t provides `sync-target`. :exc:`plexapi.exceptions.BadRequest`: provided client doesn`t provides `sync-target`.
""" """
if not client and not clientId: if not client and not clientId:
clientId = X_PLEX_IDENTIFIER clientId = X_PLEX_IDENTIFIER
@ -686,7 +686,7 @@ class MyPlexAccount(PlexObject):
class MyPlexUser(PlexObject): class MyPlexUser(PlexObject):
""" This object represents non-signed in users such as friends and linked """ This object represents non-signed in users such as friends and linked
accounts. NOTE: This should not be confused with the :class:`~myplex.MyPlexAccount` accounts. NOTE: This should not be confused with the :class:`~plexapi.myplex.MyPlexAccount`
which is your specific account. The raw xml for the data presented here which is your specific account. The raw xml for the data presented here
can be found at: https://plex.tv/api/users/ can be found at: https://plex.tv/api/users/
@ -885,7 +885,7 @@ class MyPlexResource(PlexObject):
key (str): 'https://plex.tv/api/resources?includeHttps=1&includeRelay=1' key (str): 'https://plex.tv/api/resources?includeHttps=1&includeRelay=1'
accessToken (str): This resources accesstoken. accessToken (str): This resources accesstoken.
clientIdentifier (str): Unique ID for this resource. clientIdentifier (str): Unique ID for this resource.
connections (list): List of :class:`~myplex.ResourceConnection` objects connections (list): List of :class:`~plexapi.myplex.ResourceConnection` objects
for this resource. for this resource.
createdAt (datetime): Timestamp this resource first connected to your server. createdAt (datetime): Timestamp this resource first connected to your server.
device (str): Best guess on the type of device this is (PS, iPhone, Linux, etc). device (str): Best guess on the type of device this is (PS, iPhone, Linux, etc).
@ -930,7 +930,7 @@ class MyPlexResource(PlexObject):
self.sourceTitle = data.attrib.get('sourceTitle') # owners plex username. self.sourceTitle = data.attrib.get('sourceTitle') # owners plex username.
def connect(self, ssl=None, timeout=None): def connect(self, ssl=None, timeout=None):
""" Returns a new :class:`~server.PlexServer` or :class:`~client.PlexClient` object. """ Returns a new :class:`~plexapi.server.PlexServer` or :class:`~plexapi.client.PlexClient` object.
Often times there is more than one address specified for a server or client. Often times there is more than one address specified for a server or client.
This function will prioritize local connections before remote and HTTPS before HTTP. This function will prioritize local connections before remote and HTTPS before HTTP.
After trying to connect to all available addresses for this resource and After trying to connect to all available addresses for this resource and
@ -942,7 +942,7 @@ class MyPlexResource(PlexObject):
HTTP or HTTPS connection. HTTP or HTTPS connection.
Raises: Raises:
:class:`plexapi.exceptions.NotFound`: When unable to connect to any addresses for this resource. :exc:`plexapi.exceptions.NotFound`: When unable to connect to any addresses for this resource.
""" """
# Sort connections from (https, local) to (http, remote) # Sort connections from (https, local) to (http, remote)
# Only check non-local connections unless we own the resource # Only check non-local connections unless we own the resource
@ -965,7 +965,7 @@ class MyPlexResource(PlexObject):
class ResourceConnection(PlexObject): class ResourceConnection(PlexObject):
""" Represents a Resource Connection object found within the """ Represents a Resource Connection object found within the
:class:`~myplex.MyPlexResource` objects. :class:`~plexapi.myplex.MyPlexResource` objects.
Attributes: Attributes:
TAG (str): 'Connection' TAG (str): 'Connection'
@ -1049,7 +1049,7 @@ class MyPlexDevice(PlexObject):
at least one connection was successful, the PlexClient object is built and returned. at least one connection was successful, the PlexClient object is built and returned.
Raises: Raises:
:class:`plexapi.exceptions.NotFound`: When unable to connect to any addresses for this device. :exc:`plexapi.exceptions.NotFound`: When unable to connect to any addresses for this device.
""" """
cls = PlexServer if 'server' in self.provides else PlexClient cls = PlexServer if 'server' in self.provides else PlexClient
listargs = [[cls, url, self.token, timeout] for url in self.connections] listargs = [[cls, url, self.token, timeout] for url in self.connections]
@ -1063,10 +1063,10 @@ class MyPlexDevice(PlexObject):
self._server.query(key, self._server._session.delete) self._server.query(key, self._server._session.delete)
def syncItems(self): def syncItems(self):
""" Returns an instance of :class:`plexapi.sync.SyncList` for current device. """ Returns an instance of :class:`~plexapi.sync.SyncList` for current device.
Raises: Raises:
:class:`plexapi.exceptions.BadRequest`: when the device doesn`t provides `sync-target`. :exc:`plexapi.exceptions.BadRequest`: when the device doesn`t provides `sync-target`.
""" """
if 'sync-target' not in self.provides: if 'sync-target' not in self.provides:
raise BadRequest('Requested syncList for device which do not provides sync-target') raise BadRequest('Requested syncList for device which do not provides sync-target')
@ -1082,12 +1082,12 @@ class MyPlexPinLogin(object):
This helper class supports a polling, threaded and callback approach. This helper class supports a polling, threaded and callback approach.
- The polling approach expects the developer to periodically check if the PIN login was - The polling approach expects the developer to periodically check if the PIN login was
successful using :func:`plexapi.myplex.MyPlexPinLogin.checkLogin`. successful using :func:`~plexapi.myplex.MyPlexPinLogin.checkLogin`.
- The threaded approach expects the developer to call - The threaded approach expects the developer to call
:func:`plexapi.myplex.MyPlexPinLogin.run` and then at a later time call :func:`~plexapi.myplex.MyPlexPinLogin.run` and then at a later time call
:func:`plexapi.myplex.MyPlexPinLogin.waitForLogin` to wait for and check the result. :func:`~plexapi.myplex.MyPlexPinLogin.waitForLogin` to wait for and check the result.
- The callback approach is an extension of the threaded approach and expects the developer - The callback approach is an extension of the threaded approach and expects the developer
to pass the `callback` parameter to the call to :func:`plexapi.myplex.MyPlexPinLogin.run`. to pass the `callback` parameter to the call to :func:`~plexapi.myplex.MyPlexPinLogin.run`.
The callback will be called when the thread waiting for the PIN login to succeed either The callback will be called when the thread waiting for the PIN login to succeed either
finishes or expires. The parameter passed to the callback is the received authentication finishes or expires. The parameter passed to the callback is the received authentication
token or `None` if the login expired. token or `None` if the login expired.

View file

@ -168,20 +168,20 @@ class Photo(PlexPartialObject):
def sync(self, resolution, client=None, clientId=None, limit=None, title=None): def sync(self, resolution, client=None, clientId=None, limit=None, title=None):
""" Add current photo as sync item for specified device. """ Add current photo as sync item for specified device.
See :func:`plexapi.myplex.MyPlexAccount.sync()` for possible exceptions. See :func:`~plexapi.myplex.MyPlexAccount.sync` for possible exceptions.
Parameters: Parameters:
resolution (str): maximum allowed resolution for synchronized photos, see PHOTO_QUALITY_* values in the resolution (str): maximum allowed resolution for synchronized photos, see PHOTO_QUALITY_* values in the
module :mod:`plexapi.sync`. module :mod:`~plexapi.sync`.
client (:class:`plexapi.myplex.MyPlexDevice`): sync destination, see client (:class:`~plexapi.myplex.MyPlexDevice`): sync destination, see
:func:`plexapi.myplex.MyPlexAccount.sync`. :func:`~plexapi.myplex.MyPlexAccount.sync`.
clientId (str): sync destination, see :func:`plexapi.myplex.MyPlexAccount.sync`. clientId (str): sync destination, see :func:`~plexapi.myplex.MyPlexAccount.sync`.
limit (int): maximum count of items to sync, unlimited if `None`. limit (int): maximum count of items to sync, unlimited if `None`.
title (str): descriptive title for the new :class:`plexapi.sync.SyncItem`, if empty the value would be title (str): descriptive title for the new :class:`~plexapi.sync.SyncItem`, if empty the value would be
generated from metadata of current photo. generated from metadata of current photo.
Returns: Returns:
:class:`plexapi.sync.SyncItem`: an instance of created syncItem. :class:`~plexapi.sync.SyncItem`: an instance of created syncItem.
""" """
from plexapi.sync import SyncItem, Policy, MediaSettings from plexapi.sync import SyncItem, Policy, MediaSettings

View file

@ -163,7 +163,7 @@ class Playlist(PlexPartialObject, Playable):
**kwargs (dict): is passed to the filters. For a example see the search method. **kwargs (dict): is passed to the filters. For a example see the search method.
Returns: Returns:
:class:`plexapi.playlist.Playlist`: an instance of created Playlist. :class:`~plexapi.playlist.Playlist`: an instance of created Playlist.
""" """
if smart: if smart:
return cls._createSmart(server, title, section, limit, **kwargs) return cls._createSmart(server, title, section, limit, **kwargs)
@ -217,29 +217,29 @@ class Playlist(PlexPartialObject, Playable):
def sync(self, videoQuality=None, photoResolution=None, audioBitrate=None, client=None, clientId=None, limit=None, def sync(self, videoQuality=None, photoResolution=None, audioBitrate=None, client=None, clientId=None, limit=None,
unwatched=False, title=None): unwatched=False, title=None):
""" Add current playlist as sync item for specified device. """ Add current playlist as sync item for specified device.
See :func:`plexapi.myplex.MyPlexAccount.sync()` for possible exceptions. See :func:`~plexapi.myplex.MyPlexAccount.sync` for possible exceptions.
Parameters: Parameters:
videoQuality (int): idx of quality of the video, one of VIDEO_QUALITY_* values defined in videoQuality (int): idx of quality of the video, one of VIDEO_QUALITY_* values defined in
:mod:`plexapi.sync` module. Used only when playlist contains video. :mod:`~plexapi.sync` module. Used only when playlist contains video.
photoResolution (str): maximum allowed resolution for synchronized photos, see PHOTO_QUALITY_* values in photoResolution (str): maximum allowed resolution for synchronized photos, see PHOTO_QUALITY_* values in
the module :mod:`plexapi.sync`. Used only when playlist contains photos. the module :mod:`~plexapi.sync`. Used only when playlist contains photos.
audioBitrate (int): maximum bitrate for synchronized music, better use one of MUSIC_BITRATE_* values audioBitrate (int): maximum bitrate for synchronized music, better use one of MUSIC_BITRATE_* values
from the module :mod:`plexapi.sync`. Used only when playlist contains audio. from the module :mod:`~plexapi.sync`. Used only when playlist contains audio.
client (:class:`plexapi.myplex.MyPlexDevice`): sync destination, see client (:class:`~plexapi.myplex.MyPlexDevice`): sync destination, see
:func:`plexapi.myplex.MyPlexAccount.sync`. :func:`~plexapi.myplex.MyPlexAccount.sync`.
clientId (str): sync destination, see :func:`plexapi.myplex.MyPlexAccount.sync`. clientId (str): sync destination, see :func:`~plexapi.myplex.MyPlexAccount.sync`.
limit (int): maximum count of items to sync, unlimited if `None`. limit (int): maximum count of items to sync, unlimited if `None`.
unwatched (bool): if `True` watched videos wouldn't be synced. unwatched (bool): if `True` watched videos wouldn't be synced.
title (str): descriptive title for the new :class:`plexapi.sync.SyncItem`, if empty the value would be title (str): descriptive title for the new :class:`~plexapi.sync.SyncItem`, if empty the value would be
generated from metadata of current photo. generated from metadata of current photo.
Raises: Raises:
:class:`plexapi.exceptions.BadRequest`: when playlist is not allowed to sync. :exc:`plexapi.exceptions.BadRequest`: when playlist is not allowed to sync.
:class:`plexapi.exceptions.Unsupported`: when playlist content is unsupported. :exc:`plexapi.exceptions.Unsupported`: when playlist content is unsupported.
Returns: Returns:
:class:`plexapi.sync.SyncItem`: an instance of created syncItem. :class:`~plexapi.sync.SyncItem`: an instance of created syncItem.
""" """
if not self.allowSync: if not self.allowSync:

View file

@ -8,7 +8,7 @@ from plexapi.base import PlexObject
from plexapi.client import PlexClient from plexapi.client import PlexClient
from plexapi.compat import ElementTree, urlencode from plexapi.compat import ElementTree, urlencode
from plexapi.exceptions import BadRequest, NotFound, Unauthorized from plexapi.exceptions import BadRequest, NotFound, Unauthorized
from plexapi.library import Library, Hub from plexapi.library import Hub, Library, Path, File
from plexapi.settings import Settings from plexapi.settings import Settings
from plexapi.playlist import Playlist from plexapi.playlist import Playlist
from plexapi.playqueue import PlayQueue from plexapi.playqueue import PlayQueue
@ -185,7 +185,7 @@ class PlexServer(PlexObject):
return Account(self, data) return Account(self, data)
def agents(self, mediaType=None): def agents(self, mediaType=None):
""" Returns the `:class:`~plexapi.media.Agent` objects this server has available. """ """ Returns the :class:`~plexapi.media.Agent` objects this server has available. """
key = '/system/agents' key = '/system/agents'
if mediaType: if mediaType:
key += '?mediaType=%s' % mediaType key += '?mediaType=%s' % mediaType
@ -233,6 +233,53 @@ class PlexServer(PlexObject):
log.warning('Unable to fetch client ports from myPlex: %s', err) log.warning('Unable to fetch client ports from myPlex: %s', err)
return ports return ports
def browse(self, path=None, includeFiles=True):
""" Browse the system file path using the Plex API.
Returns list of :class:`~plexapi.library.Path` and :class:`~plexapi.library.File` objects.
Parameters:
path (:class:`~plexapi.library.Path` or str, optional): Full path to browse.
includeFiles (bool): True to include files when browsing (Default).
False to only return folders.
"""
if isinstance(path, Path):
key = path.key
elif path is not None:
base64path = utils.base64str(path)
key = '/services/browse/%s' % base64path
else:
key = '/services/browse'
if includeFiles:
key += '?includeFiles=1'
return self.fetchItems(key)
def walk(self, path=None):
""" Walk the system file tree using the Plex API similar to `os.walk`.
Yields a 3-tuple `(path, paths, files)` where
`path` is a string of the directory path,
`paths` is a list of :class:`~plexapi.library.Path` objects, and
`files` is a list of :class:`~plexapi.library.File` objects.
Parameters:
path (:class:`~plexapi.library.Path` or str, optional): Full path to walk.
"""
paths = []
files = []
for item in self.browse(path):
if isinstance(item, Path):
paths.append(item)
elif isinstance(item, File):
files.append(item)
if isinstance(path, Path):
path = path.path
yield path or '', paths, files
for _path in paths:
for path, paths, files in self.walk(_path):
yield path, paths, files
def clients(self): def clients(self):
""" Returns list of all :class:`~plexapi.client.PlexClient` objects connected to server. """ """ Returns list of all :class:`~plexapi.client.PlexClient` objects connected to server. """
items = [] items = []
@ -256,7 +303,7 @@ class PlexServer(PlexObject):
name (str): Name of the client to return. name (str): Name of the client to return.
Raises: Raises:
:class:`plexapi.exceptions.NotFound`: Unknown client name :exc:`plexapi.exceptions.NotFound`: Unknown client name
""" """
for client in self.clients(): for client in self.clients():
if client and client.title == name: if client and client.title == name:
@ -379,7 +426,7 @@ class PlexServer(PlexObject):
title (str): Title of the playlist to return. title (str): Title of the playlist to return.
Raises: Raises:
:class:`plexapi.exceptions.NotFound`: Invalid playlist title :exc:`plexapi.exceptions.NotFound`: Invalid playlist title
""" """
return self.fetchItem('/playlists', title=title) return self.fetchItem('/playlists', title=title)
@ -480,8 +527,8 @@ class PlexServer(PlexObject):
Parameters: Parameters:
callback (func): Callback function to call on recieved messages. callback (func): Callback function to call on recieved messages.
raises: Raises:
:class:`plexapi.exception.Unsupported`: Websocket-client not installed. :exc:`plexapi.exception.Unsupported`: Websocket-client not installed.
""" """
notifier = AlertListener(self, callback) notifier = AlertListener(self, callback)
notifier.start() notifier.start()

View file

@ -21,7 +21,10 @@ class Settings(PlexObject):
def __getattr__(self, attr): def __getattr__(self, attr):
if attr.startswith('_'): if attr.startswith('_'):
try:
return self.__dict__[attr] return self.__dict__[attr]
except KeyError:
raise AttributeError
return self.get(attr).value return self.get(attr).value
def __setattr__(self, attr, value): def __setattr__(self, attr, value):

View file

@ -78,7 +78,7 @@ class SyncItem(PlexObject):
self.location = data.find('Location').attrib.get('uri', '') self.location = data.find('Location').attrib.get('uri', '')
def server(self): def server(self):
""" Returns :class:`plexapi.myplex.MyPlexResource` with server of current item. """ """ Returns :class:`~plexapi.myplex.MyPlexResource` with server of current item. """
server = [s for s in self._server.resources() if s.clientIdentifier == self.machineIdentifier] server = [s for s in self._server.resources() if s.clientIdentifier == self.machineIdentifier]
if len(server) == 0: if len(server) == 0:
raise NotFound('Unable to find server with uuid %s' % self.machineIdentifier) raise NotFound('Unable to find server with uuid %s' % self.machineIdentifier)
@ -201,7 +201,7 @@ class MediaSettings(object):
videoQuality (int): idx of quality of the video, one of VIDEO_QUALITY_* values defined in this module. videoQuality (int): idx of quality of the video, one of VIDEO_QUALITY_* values defined in this module.
Raises: Raises:
:class:`plexapi.exceptions.BadRequest`: when provided unknown video quality. :exc:`plexapi.exceptions.BadRequest`: when provided unknown video quality.
""" """
if videoQuality == VIDEO_QUALITY_ORIGINAL: if videoQuality == VIDEO_QUALITY_ORIGINAL:
return MediaSettings('', '', '') return MediaSettings('', '', '')
@ -231,7 +231,7 @@ class MediaSettings(object):
module. module.
Raises: Raises:
:class:`plexapi.exceptions.BadRequest` when provided unknown video quality. :exc:`plexapi.exceptions.BadRequest` when provided unknown video quality.
""" """
if resolution in PHOTO_QUALITIES: if resolution in PHOTO_QUALITIES:
return MediaSettings(photoQuality=PHOTO_QUALITIES[resolution], photoResolution=resolution) return MediaSettings(photoQuality=PHOTO_QUALITIES[resolution], photoResolution=resolution)

View file

@ -1,4 +1,5 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import base64
import logging import logging
import os import os
import re import re
@ -147,7 +148,7 @@ def searchType(libtype):
libtype (str): LibType to lookup (movie, show, season, episode, artist, album, track, libtype (str): LibType to lookup (movie, show, season, episode, artist, album, track,
collection) collection)
Raises: Raises:
:class:`plexapi.exceptions.NotFound`: Unknown libtype :exc:`plexapi.exceptions.NotFound`: Unknown libtype
""" """
libtype = compat.ustr(libtype) libtype = compat.ustr(libtype)
if libtype in [compat.ustr(v) for v in SEARCHTYPES.values()]: if libtype in [compat.ustr(v) for v in SEARCHTYPES.values()]:
@ -399,3 +400,7 @@ def getAgentIdentifier(section, agent):
agents += identifiers agents += identifiers
raise NotFound('Couldnt find "%s" in agents list (%s)' % raise NotFound('Couldnt find "%s" in agents list (%s)' %
(agent, ', '.join(agents))) (agent, ', '.join(agents)))
def base64str(text):
return base64.b64encode(text.encode('utf-8')).decode('utf-8')

View file

@ -13,6 +13,8 @@ class Video(PlexPartialObject):
Attributes: Attributes:
addedAt (datetime): Datetime this item was added to the library. addedAt (datetime): Datetime this item was added to the library.
art (str): URL to artwork image.
artBlurHash (str): BlurHash string for artwork image.
key (str): API URL (/library/metadata/<ratingkey>). key (str): API URL (/library/metadata/<ratingkey>).
lastViewedAt (datetime): Datetime item was last accessed. lastViewedAt (datetime): Datetime item was last accessed.
librarySectionID (int): :class:`~plexapi.library.LibrarySection` ID. librarySectionID (int): :class:`~plexapi.library.LibrarySection` ID.
@ -20,6 +22,7 @@ class Video(PlexPartialObject):
ratingKey (int): Unique key identifying this item. ratingKey (int): Unique key identifying this item.
summary (str): Summary of the artist, track, or album. summary (str): Summary of the artist, track, or album.
thumb (str): URL to thumbnail image. thumb (str): URL to thumbnail image.
thumbBlurHash (str): BlurHash string for thumbnail image.
title (str): Artist, Album or Track title. (Jason Mraz, We Sing, Lucky, etc.) title (str): Artist, Album or Track title. (Jason Mraz, We Sing, Lucky, etc.)
titleSort (str): Title to use when sorting (defaults to title). titleSort (str): Title to use when sorting (defaults to title).
type (str): 'artist', 'album', or 'track'. type (str): 'artist', 'album', or 'track'.
@ -32,6 +35,8 @@ class Video(PlexPartialObject):
self._data = data self._data = data
self.listType = 'video' self.listType = 'video'
self.addedAt = utils.toDatetime(data.attrib.get('addedAt')) self.addedAt = utils.toDatetime(data.attrib.get('addedAt'))
self.art = data.attrib.get('art')
self.artBlurHash = data.attrib.get('artBlurHash')
self.key = data.attrib.get('key', '') self.key = data.attrib.get('key', '')
self.lastViewedAt = utils.toDatetime(data.attrib.get('lastViewedAt')) self.lastViewedAt = utils.toDatetime(data.attrib.get('lastViewedAt'))
self.librarySectionID = data.attrib.get('librarySectionID') self.librarySectionID = data.attrib.get('librarySectionID')
@ -40,6 +45,7 @@ class Video(PlexPartialObject):
self.ratingKey = utils.cast(int, data.attrib.get('ratingKey')) self.ratingKey = utils.cast(int, data.attrib.get('ratingKey'))
self.summary = data.attrib.get('summary') self.summary = data.attrib.get('summary')
self.thumb = data.attrib.get('thumb') self.thumb = data.attrib.get('thumb')
self.thumbBlurHash = data.attrib.get('thumbBlurHash')
self.title = data.attrib.get('title') self.title = data.attrib.get('title')
self.titleSort = data.attrib.get('titleSort', self.title) self.titleSort = data.attrib.get('titleSort', self.title)
self.type = data.attrib.get('type') self.type = data.attrib.get('type')
@ -201,21 +207,21 @@ class Video(PlexPartialObject):
def sync(self, videoQuality, client=None, clientId=None, limit=None, unwatched=False, title=None): def sync(self, videoQuality, client=None, clientId=None, limit=None, unwatched=False, title=None):
""" Add current video (movie, tv-show, season or episode) as sync item for specified device. """ Add current video (movie, tv-show, season or episode) as sync item for specified device.
See :func:`plexapi.myplex.MyPlexAccount.sync()` for possible exceptions. See :func:`~plexapi.myplex.MyPlexAccount.sync` for possible exceptions.
Parameters: Parameters:
videoQuality (int): idx of quality of the video, one of VIDEO_QUALITY_* values defined in videoQuality (int): idx of quality of the video, one of VIDEO_QUALITY_* values defined in
:mod:`plexapi.sync` module. :mod:`~plexapi.sync` module.
client (:class:`plexapi.myplex.MyPlexDevice`): sync destination, see client (:class:`~plexapi.myplex.MyPlexDevice`): sync destination, see
:func:`plexapi.myplex.MyPlexAccount.sync`. :func:`~plexapi.myplex.MyPlexAccount.sync`.
clientId (str): sync destination, see :func:`plexapi.myplex.MyPlexAccount.sync`. clientId (str): sync destination, see :func:`~plexapi.myplex.MyPlexAccount.sync`.
limit (int): maximum count of items to sync, unlimited if `None`. limit (int): maximum count of items to sync, unlimited if `None`.
unwatched (bool): if `True` watched videos wouldn't be synced. unwatched (bool): if `True` watched videos wouldn't be synced.
title (str): descriptive title for the new :class:`plexapi.sync.SyncItem`, if empty the value would be title (str): descriptive title for the new :class:`~plexapi.sync.SyncItem`, if empty the value would be
generated from metadata of current media. generated from metadata of current media.
Returns: Returns:
:class:`plexapi.sync.SyncItem`: an instance of created syncItem. :class:`~plexapi.sync.SyncItem`: an instance of created syncItem.
""" """
from plexapi.sync import SyncItem, Policy, MediaSettings from plexapi.sync import SyncItem, Policy, MediaSettings
@ -277,17 +283,12 @@ class Movie(Playable, Video):
TAG = 'Video' TAG = 'Video'
TYPE = 'movie' TYPE = 'movie'
METADATA_TYPE = 'movie' METADATA_TYPE = 'movie'
_include = ('?checkFiles=1&includeExtras=1&includeRelated=1'
'&includeOnDeck=1&includeChapters=1&includePopularLeaves=1'
'&includeConcerts=1&includePreferences=1'
'&includeBandwidths=1')
def _loadData(self, data): def _loadData(self, data):
""" Load attribute values from Plex XML response. """ """ Load attribute values from Plex XML response. """
Video._loadData(self, data) Video._loadData(self, data)
Playable._loadData(self, data) Playable._loadData(self, data)
self._details_key = self.key + self._include
self.art = data.attrib.get('art') self.art = data.attrib.get('art')
self.audienceRating = utils.cast(float, data.attrib.get('audienceRating')) self.audienceRating = utils.cast(float, data.attrib.get('audienceRating'))
self.audienceRatingImage = data.attrib.get('audienceRatingImage') self.audienceRatingImage = data.attrib.get('audienceRatingImage')
@ -343,7 +344,7 @@ class Movie(Playable, Video):
savepath (str): Defaults to current working dir. savepath (str): Defaults to current working dir.
keep_original_name (bool): True to keep the original file name otherwise keep_original_name (bool): True to keep the original file name otherwise
a friendlier is generated. a friendlier is generated.
**kwargs: Additional options passed into :func:`~plexapi.base.PlexObject.getStreamURL()`. **kwargs: Additional options passed into :func:`~plexapi.base.PlexObject.getStreamURL`.
""" """
filepaths = [] filepaths = []
locations = [i for i in self.iterParts() if i] locations = [i for i in self.iterParts() if i]
@ -466,8 +467,8 @@ class Show(Video):
episode (int): Episode number (default:None; required if title not specified). episode (int): Episode number (default:None; required if title not specified).
Raises: Raises:
:class:`plexapi.exceptions.BadRequest`: If season and episode is missing. :exc:`plexapi.exceptions.BadRequest`: If season and episode is missing.
:class:`plexapi.exceptions.NotFound`: If the episode is missing. :exc:`plexapi.exceptions.NotFound`: If the episode is missing.
""" """
if title: if title:
key = '/library/metadata/%s/allLeaves' % self.ratingKey key = '/library/metadata/%s/allLeaves' % self.ratingKey
@ -488,7 +489,7 @@ class Show(Video):
return self.episodes(viewCount=0) return self.episodes(viewCount=0)
def get(self, title=None, season=None, episode=None): def get(self, title=None, season=None, episode=None):
""" Alias to :func:`~plexapi.video.Show.episode()`. """ """ Alias to :func:`~plexapi.video.Show.episode`. """
return self.episode(title, season, episode) return self.episode(title, season, episode)
def download(self, savepath=None, keep_original_name=False, **kwargs): def download(self, savepath=None, keep_original_name=False, **kwargs):
@ -498,7 +499,7 @@ class Show(Video):
savepath (str): Defaults to current working dir. savepath (str): Defaults to current working dir.
keep_original_name (bool): True to keep the original file name otherwise keep_original_name (bool): True to keep the original file name otherwise
a friendlier is generated. a friendlier is generated.
**kwargs: Additional options passed into :func:`~plexapi.base.PlexObject.getStreamURL()`. **kwargs: Additional options passed into :func:`~plexapi.base.PlexObject.getStreamURL`.
""" """
filepaths = [] filepaths = []
for episode in self.episodes(): for episode in self.episodes():
@ -585,7 +586,7 @@ class Season(Video):
return self.fetchItem(key, parentIndex=self.index, index=episode) return self.fetchItem(key, parentIndex=self.index, index=episode)
def get(self, title=None, episode=None): def get(self, title=None, episode=None):
""" Alias to :func:`~plexapi.video.Season.episode()`. """ """ Alias to :func:`~plexapi.video.Season.episode`. """
return self.episode(title, episode) return self.episode(title, episode)
def show(self): def show(self):
@ -607,7 +608,7 @@ class Season(Video):
savepath (str): Defaults to current working dir. savepath (str): Defaults to current working dir.
keep_original_name (bool): True to keep the original file name otherwise keep_original_name (bool): True to keep the original file name otherwise
a friendlier is generated. a friendlier is generated.
**kwargs: Additional options passed into :func:`~plexapi.base.PlexObject.getStreamURL()`. **kwargs: Additional options passed into :func:`~plexapi.base.PlexObject.getStreamURL`.
""" """
filepaths = [] filepaths = []
for episode in self.episodes(): for episode in self.episodes():
@ -656,16 +657,10 @@ class Episode(Playable, Video):
TYPE = 'episode' TYPE = 'episode'
METADATA_TYPE = 'episode' METADATA_TYPE = 'episode'
_include = ('?checkFiles=1&includeExtras=1&includeRelated=1'
'&includeOnDeck=1&includeChapters=1&includePopularLeaves=1'
'&includeMarkers=1&includeConcerts=1&includePreferences=1'
'&includeBandwidths=1')
def _loadData(self, data): def _loadData(self, data):
""" Load attribute values from Plex XML response. """ """ Load attribute values from Plex XML response. """
Video._loadData(self, data) Video._loadData(self, data)
Playable._loadData(self, data) Playable._loadData(self, data)
self._details_key = self.key + self._include
self._seasonNumber = None # cached season number self._seasonNumber = None # cached season number
art = data.attrib.get('art') art = data.attrib.get('art')
self.art = art if art and str(self.ratingKey) in art else None self.art = art if art and str(self.ratingKey) in art else None