mirror of
https://github.com/Tautulli/Tautulli.git
synced 2025-07-07 21:51:14 -07:00
Update plexapi 3.6.0-tautulli
This commit is contained in:
parent
9b0caf2a47
commit
d589c57dd2
14 changed files with 319 additions and 168 deletions
|
@ -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.')
|
||||||
|
|
|
@ -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')
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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.')
|
||||||
|
|
|
@ -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')
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -21,7 +21,10 @@ class Settings(PlexObject):
|
||||||
|
|
||||||
def __getattr__(self, attr):
|
def __getattr__(self, attr):
|
||||||
if attr.startswith('_'):
|
if attr.startswith('_'):
|
||||||
return self.__dict__[attr]
|
try:
|
||||||
|
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):
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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')
|
||||||
|
|
|
@ -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]
|
||||||
|
@ -460,14 +461,14 @@ class Show(Video):
|
||||||
def episode(self, title=None, season=None, episode=None):
|
def episode(self, title=None, season=None, episode=None):
|
||||||
""" Find a episode using a title or season and episode.
|
""" Find a episode using a title or season and episode.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
title (str): Title of the episode to return
|
title (str): Title of the episode to return
|
||||||
season (int): Season number (default:None; required if title not specified).
|
season (int): Season number (default:None; required if title not specified).
|
||||||
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
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue