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):
""" 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.
"""
log.info('Stopping AlertListener.')

View file

@ -10,6 +10,8 @@ class Audio(PlexPartialObject):
Attributes:
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).
key (str): API URL (/library/metadata/<ratingkey>).
lastViewedAt (datetime): Datetime item was last accessed.
@ -18,6 +20,7 @@ class Audio(PlexPartialObject):
ratingKey (int): Unique key identifying this item.
summary (str): Summary of the artist, track, or album.
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.)
titleSort (str): Title to use when sorting (defaults to title).
type (str): 'artist', 'album', or 'track'.
@ -32,6 +35,8 @@ class Audio(PlexPartialObject):
self._data = data
self.listType = 'audio'
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.key = data.attrib.get('key')
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.summary = data.attrib.get('summary')
self.thumb = data.attrib.get('thumb')
self.thumbBlurHash = data.attrib.get('thumbBlurHash')
self.title = data.attrib.get('title')
self.titleSort = data.attrib.get('titleSort', self.title)
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):
""" 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:
bitrate (int): maximum bitrate for synchronized music, better use one of MUSIC_BITRATE_* values from the
module :mod:`plexapi.sync`.
client (:class:`plexapi.myplex.MyPlexDevice`): sync destination, see
:func:`plexapi.myplex.MyPlexAccount.sync`.
clientId (str): sync destination, see :func:`plexapi.myplex.MyPlexAccount.sync`.
module :mod:`~plexapi.sync`.
client (:class:`~plexapi.myplex.MyPlexDevice`): 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`.
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.
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
@ -111,7 +117,6 @@ class Artist(Audio):
Attributes:
TAG (str): 'Directory'
TYPE (str): 'artist'
art (str): Artist artwork (/library/metadata/<ratingkey>/art/<artid>)
countries (list): List of :class:`~plexapi.media.Country` 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)
@ -122,17 +127,10 @@ class Artist(Audio):
TAG = 'Directory'
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):
""" Load attribute values from Plex XML response. """
Audio._loadData(self, data)
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.locations = self.listAttrs(data, 'path', etag='Location')
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
the Plex server. False will create a new filename with the format
"<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
function. If kwargs is not specified, the media items will be downloaded
and saved to disk.
@ -206,7 +204,6 @@ class Album(Audio):
Attributes:
TAG (str): 'Directory'
TYPE (str): 'album'
art (str): Album artwork (/library/metadata/<ratingkey>/art/<artid>)
genres (list): List of :class:`~plexapi.media.Genre` objects this album respresents.
key (str): API URL (/library/metadata/<ratingkey>).
originallyAvailableAt (datetime): Datetime this album was released.
@ -227,7 +224,6 @@ class Album(Audio):
def _loadData(self, data):
""" Load attribute values from Plex XML response. """
Audio._loadData(self, data)
self.art = data.attrib.get('art')
self.guid = data.attrib.get('guid')
self.leafCount = utils.cast(int, data.attrib.get('leafCount'))
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
the Plex server. False will create a new filename with the format
"<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
function. If kwargs is not specified, the media items will be downloaded
and saved to disk.
@ -301,7 +297,6 @@ class Track(Audio, Playable):
Attributes:
TAG (str): 'Directory'
TYPE (str): 'track'
art (str): Track artwork (/library/metadata/<ratingkey>/art/<artid>)
chapterSource (TYPE): Unknown
duration (int): Length of this album in seconds.
grandparentArt (str): Album artist artwork.
@ -332,17 +327,10 @@ class Track(Audio, Playable):
TAG = '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):
""" Load attribute values from Plex XML response. """
Audio._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.duration = utils.cast(int, data.attrib.get('duration'))
self.grandparentArt = data.attrib.get('grandparentArt')

View file

@ -44,9 +44,9 @@ class PlexObject(object):
self._server = server
self._data = data
self._initpath = initpath or self.key
self._details_key = ''
if data is not None:
self._loadData(data)
self._details_key = self._buildDetailsKey()
def __repr__(self):
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))
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.
"""
try:
@ -89,6 +89,22 @@ class PlexObject(object):
except UnknownType:
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):
""" 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
@ -203,9 +219,39 @@ class PlexObject(object):
results.append(elem.attrib.get(attr))
return results
def reload(self, key=None):
""" Reload the data for this object from self.key. """
key = key or self._details_key or self.key
def reload(self, key=None, **kwargs):
""" Reload the data for this object from 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:
raise Unsupported('Cannot reload an object not built from a URL.')
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
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):
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
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.
object (main url) for that movie would contain.
"""
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.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):
""" 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.
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'):
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
the Plex server. False will create a new filename with the format
"<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
function. If kwargs is not specified, the media items will be downloaded
and saved to disk.

View file

@ -53,7 +53,7 @@ class PlexClient(PlexObject):
_token (str): Token used to access this client.
_session (obj): Requests session object used to access this client.
_proxyThroughServer (bool): Set to True after calling
:func:`~plexapi.client.PlexClient.proxyThroughServer()` (default False).
:func:`~plexapi.client.PlexClient.proxyThroughServer` (default False).
"""
TAG = 'Player'
key = '/resources'
@ -138,7 +138,7 @@ class PlexClient(PlexObject):
value (bool): Enable or disable proxying (optional, default True).
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:
self._server = server
@ -171,7 +171,7 @@ class PlexClient(PlexObject):
return ElementTree.fromstring(data) if data.strip() else None
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
the response.
@ -181,7 +181,7 @@ class PlexClient(PlexObject):
**params (dict): Additional GET parameters to include with the command.
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('/')
controller = command.split('/')[0]
@ -296,7 +296,7 @@ class PlexClient(PlexObject):
**params (dict): Additional GET parameters to include with the command.
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:
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
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:
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)
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))
@ -517,7 +517,7 @@ class LibrarySection(PlexObject):
def listChoices(self, category, libtype=None, **kwargs):
""" Returns a list of :class:`~plexapi.library.FilterChoice` objects for the
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.
Parameters:
@ -526,7 +526,7 @@ class LibrarySection(PlexObject):
**kwargs (dict): Additional kwargs to narrow down the choices.
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?
if category in kwargs:
@ -573,7 +573,7 @@ class LibrarySection(PlexObject):
* year: List of years to search within ([yyyy, ...]). [all]
Raises:
:class:`plexapi.exceptions.BadRequest`: when applying unknown filter
:exc:`plexapi.exceptions.BadRequest`: when applying unknown filter
"""
# cleanup the core arguments
args = {}
@ -659,20 +659,20 @@ class LibrarySection(PlexObject):
def sync(self, policy, mediaSettings, client=None, clientId=None, title=None, sort=None, libtype=None,
**kwargs):
""" Add current library section as sync item for specified device.
See description of :func:`~plexapi.library.LibrarySection.search()` for details about filtering / sorting
and :func:`plexapi.myplex.MyPlexAccount.sync()` for possible exceptions.
See description of :func:`~plexapi.library.LibrarySection.search` for details about filtering / sorting
and :func:`~plexapi.myplex.MyPlexAccount.sync` for possible exceptions.
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
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
LibrarySection object.
client (:class:`plexapi.myplex.MyPlexDevice`): 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
client (:class:`~plexapi.myplex.MyPlexDevice`): 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
generated from metadata of current media.
sort (str): formatted as `column:dir`; column can be any of {`addedAt`, `originallyAvailableAt`,
`lastViewedAt`, `titleSort`, `rating`, `mediaHeight`, `duration`}. dir can be `asc` or
@ -681,10 +681,10 @@ class LibrarySection(PlexObject):
`track`).
Returns:
:class:`plexapi.sync.SyncItem`: an instance of created syncItem.
:class:`~plexapi.sync.SyncItem`: an instance of created syncItem.
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:
@ -784,17 +784,17 @@ class MovieSection(LibrarySection):
def sync(self, videoQuality, limit=None, unwatched=False, **kwargs):
""" 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
:func:`plexapi.library.LibrarySection.sync()` for details on syncing libraries and possible exceptions.
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.
Parameters:
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`.
unwatched (bool): if `True` watched videos wouldn't be synced.
Returns:
:class:`plexapi.sync.SyncItem`: an instance of created syncItem.
:class:`~plexapi.sync.SyncItem`: an instance of created syncItem.
Example:
@ -843,11 +843,11 @@ class ShowSection(LibrarySection):
CONTENT_TYPE = 'video'
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)
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)
def recentlyAdded(self, libtype='episode', maxresults=50):
@ -877,17 +877,17 @@ class ShowSection(LibrarySection):
def sync(self, videoQuality, limit=None, unwatched=False, **kwargs):
""" 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
:func:`plexapi.library.LibrarySection.sync()` for details on syncing libraries and possible exceptions.
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.
Parameters:
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`.
unwatched (bool): if `True` watched videos wouldn't be synced.
Returns:
:class:`plexapi.sync.SyncItem`: an instance of created syncItem.
:class:`~plexapi.sync.SyncItem`: an instance of created syncItem.
Example:
@ -941,15 +941,15 @@ class MusicSection(LibrarySection):
return self.fetchItems(key)
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)
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)
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)
def all(self, libtype='artist', **kwargs):
@ -971,16 +971,16 @@ class MusicSection(LibrarySection):
def sync(self, bitrate, limit=None, **kwargs):
""" 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
:func:`plexapi.library.LibrarySection.sync()` for details on syncing libraries and possible exceptions.
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.
Parameters:
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`.
Returns:
:class:`plexapi.sync.SyncItem`: an instance of created syncItem.
:class:`~plexapi.sync.SyncItem`: an instance of created syncItem.
Example:
@ -1023,11 +1023,11 @@ class PhotoSection(LibrarySection):
METADATA_TYPE = 'photo'
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)
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)
def all(self, libtype='photoalbum', **kwargs):
@ -1043,16 +1043,16 @@ class PhotoSection(LibrarySection):
def sync(self, resolution, limit=None, **kwargs):
""" 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
:func:`plexapi.library.LibrarySection.sync()` for details on syncing libraries and possible exceptions.
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.
Parameters:
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`.
Returns:
:class:`plexapi.sync.SyncItem`: an instance of created syncItem.
:class:`~plexapi.sync.SyncItem`: an instance of created syncItem.
Example:
@ -1079,7 +1079,7 @@ class PhotoSection(LibrarySection):
class FilterChoice(PlexObject):
""" 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
:func:`~plexapi.library.LibrarySection.listChoices()`.
:func:`~plexapi.library.LibrarySection.listChoices`.
Attributes:
TAG (str): 'Directory'
@ -1140,6 +1140,8 @@ class Collections(PlexPartialObject):
TYPE (str): 'collection'
ratingKey (int): Unique key identifying this item.
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)
collectionMode (str): How the items in the collection are displayed.
collectionSort (str): How to sort the items in the collection.
@ -1157,6 +1159,7 @@ class Collections(PlexPartialObject):
subtype (str): Media type
summary (str): Summary of the collection
thumb (str): URL to thumbnail image.
thumbBlurHash (str): BlurHash string for thumbnail image.
title (str): Collection Title
titleSort (str): Title to use when sorting (defaults to title).
type (str): Hardcoded 'collection'
@ -1165,14 +1168,13 @@ class Collections(PlexPartialObject):
TAG = 'Directory'
TYPE = 'collection'
_include = "?includeExternalMedia=1&includePreferences=1"
def _loadData(self, data):
self.ratingKey = utils.cast(int, data.attrib.get('ratingKey'))
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.art = data.attrib.get('art')
self.artBlurHash = data.attrib.get('artBlurHash')
self.childCount = utils.cast(int, data.attrib.get('childCount'))
self.collectionMode = utils.cast(int, data.attrib.get('collectionMode'))
self.collectionSort = utils.cast(int, data.attrib.get('collectionSort'))
@ -1189,6 +1191,7 @@ class Collections(PlexPartialObject):
self.subtype = data.attrib.get('subtype')
self.summary = data.attrib.get('summary')
self.thumb = data.attrib.get('thumb')
self.thumbBlurHash = data.attrib.get('thumbBlurHash')
self.title = data.attrib.get('title')
self.titleSort = data.attrib.get('titleSort')
self.type = data.attrib.get('type')
@ -1226,7 +1229,7 @@ class Collections(PlexPartialObject):
collection = 'plexapi.library.Collections'
collection.updateMode(mode="hide")
"""
mode_dict = {'default': '-2',
mode_dict = {'default': '-1',
'hide': '0',
'hideItems': '1',
'showItems': '2'}
@ -1293,3 +1296,54 @@ class Collections(PlexPartialObject):
# def edit(self, **kwargs):
# 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
person for Directors and Roles (ex: Animation, Stephen Graham, etc).
<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.
* 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):
""" 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:
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)
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:
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):
""" 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:
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.
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 neither `client` nor `clientId` provided the clientId would be set to current clients`s identifier.
Returns:
:class:`plexapi.sync.SyncItem`: an instance of created syncItem.
:class:`~plexapi.sync.SyncItem`: an instance of created syncItem.
Raises:
:class:`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`: when client with provided clientId wasn`t found.
:exc:`plexapi.exceptions.BadRequest`: provided client doesn`t provides `sync-target`.
"""
if not client and not clientId:
clientId = X_PLEX_IDENTIFIER
@ -686,7 +686,7 @@ class MyPlexAccount(PlexObject):
class MyPlexUser(PlexObject):
""" 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
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'
accessToken (str): This resources accesstoken.
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.
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).
@ -930,7 +930,7 @@ class MyPlexResource(PlexObject):
self.sourceTitle = data.attrib.get('sourceTitle') # owners plex username.
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.
This function will prioritize local connections before remote and HTTPS before HTTP.
After trying to connect to all available addresses for this resource and
@ -942,7 +942,7 @@ class MyPlexResource(PlexObject):
HTTP or HTTPS connection.
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)
# Only check non-local connections unless we own the resource
@ -965,7 +965,7 @@ class MyPlexResource(PlexObject):
class ResourceConnection(PlexObject):
""" Represents a Resource Connection object found within the
:class:`~myplex.MyPlexResource` objects.
:class:`~plexapi.myplex.MyPlexResource` objects.
Attributes:
TAG (str): 'Connection'
@ -1049,7 +1049,7 @@ class MyPlexDevice(PlexObject):
at least one connection was successful, the PlexClient object is built and returned.
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
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)
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:
: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:
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.
- 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
: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.run` and then at a later time call
: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
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
finishes or expires. The parameter passed to the callback is the received authentication
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):
""" 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:
resolution (str): maximum allowed resolution for synchronized photos, see PHOTO_QUALITY_* values in the
module :mod:`plexapi.sync`.
client (:class:`plexapi.myplex.MyPlexDevice`): sync destination, see
:func:`plexapi.myplex.MyPlexAccount.sync`.
clientId (str): sync destination, see :func:`plexapi.myplex.MyPlexAccount.sync`.
module :mod:`~plexapi.sync`.
client (:class:`~plexapi.myplex.MyPlexDevice`): 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`.
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.
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

View file

@ -163,7 +163,7 @@ class Playlist(PlexPartialObject, Playable):
**kwargs (dict): is passed to the filters. For a example see the search method.
Returns:
:class:`plexapi.playlist.Playlist`: an instance of created Playlist.
:class:`~plexapi.playlist.Playlist`: an instance of created Playlist.
"""
if smart:
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,
unwatched=False, title=None):
""" 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:
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
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
from the module :mod:`plexapi.sync`. Used only when playlist contains audio.
client (:class:`plexapi.myplex.MyPlexDevice`): sync destination, see
:func:`plexapi.myplex.MyPlexAccount.sync`.
clientId (str): sync destination, see :func:`plexapi.myplex.MyPlexAccount.sync`.
from the module :mod:`~plexapi.sync`. Used only when playlist contains audio.
client (:class:`~plexapi.myplex.MyPlexDevice`): 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`.
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.
Raises:
:class:`plexapi.exceptions.BadRequest`: when playlist is not allowed to sync.
:class:`plexapi.exceptions.Unsupported`: when playlist content is unsupported.
:exc:`plexapi.exceptions.BadRequest`: when playlist is not allowed to sync.
:exc:`plexapi.exceptions.Unsupported`: when playlist content is unsupported.
Returns:
:class:`plexapi.sync.SyncItem`: an instance of created syncItem.
:class:`~plexapi.sync.SyncItem`: an instance of created syncItem.
"""
if not self.allowSync:

View file

@ -8,7 +8,7 @@ from plexapi.base import PlexObject
from plexapi.client import PlexClient
from plexapi.compat import ElementTree, urlencode
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.playlist import Playlist
from plexapi.playqueue import PlayQueue
@ -185,7 +185,7 @@ class PlexServer(PlexObject):
return Account(self, data)
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'
if mediaType:
key += '?mediaType=%s' % mediaType
@ -233,6 +233,53 @@ class PlexServer(PlexObject):
log.warning('Unable to fetch client ports from myPlex: %s', err)
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):
""" Returns list of all :class:`~plexapi.client.PlexClient` objects connected to server. """
items = []
@ -256,7 +303,7 @@ class PlexServer(PlexObject):
name (str): Name of the client to return.
Raises:
:class:`plexapi.exceptions.NotFound`: Unknown client name
:exc:`plexapi.exceptions.NotFound`: Unknown client name
"""
for client in self.clients():
if client and client.title == name:
@ -379,7 +426,7 @@ class PlexServer(PlexObject):
title (str): Title of the playlist to return.
Raises:
:class:`plexapi.exceptions.NotFound`: Invalid playlist title
:exc:`plexapi.exceptions.NotFound`: Invalid playlist title
"""
return self.fetchItem('/playlists', title=title)
@ -480,8 +527,8 @@ class PlexServer(PlexObject):
Parameters:
callback (func): Callback function to call on recieved messages.
raises:
:class:`plexapi.exception.Unsupported`: Websocket-client not installed.
Raises:
:exc:`plexapi.exception.Unsupported`: Websocket-client not installed.
"""
notifier = AlertListener(self, callback)
notifier.start()

View file

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

View file

@ -78,7 +78,7 @@ class SyncItem(PlexObject):
self.location = data.find('Location').attrib.get('uri', '')
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]
if len(server) == 0:
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.
Raises:
:class:`plexapi.exceptions.BadRequest`: when provided unknown video quality.
:exc:`plexapi.exceptions.BadRequest`: when provided unknown video quality.
"""
if videoQuality == VIDEO_QUALITY_ORIGINAL:
return MediaSettings('', '', '')
@ -231,7 +231,7 @@ class MediaSettings(object):
module.
Raises:
:class:`plexapi.exceptions.BadRequest` when provided unknown video quality.
:exc:`plexapi.exceptions.BadRequest` when provided unknown video quality.
"""
if resolution in PHOTO_QUALITIES:
return MediaSettings(photoQuality=PHOTO_QUALITIES[resolution], photoResolution=resolution)

View file

@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
import base64
import logging
import os
import re
@ -147,7 +148,7 @@ def searchType(libtype):
libtype (str): LibType to lookup (movie, show, season, episode, artist, album, track,
collection)
Raises:
:class:`plexapi.exceptions.NotFound`: Unknown libtype
:exc:`plexapi.exceptions.NotFound`: Unknown libtype
"""
libtype = compat.ustr(libtype)
if libtype in [compat.ustr(v) for v in SEARCHTYPES.values()]:
@ -399,3 +400,7 @@ def getAgentIdentifier(section, agent):
agents += identifiers
raise NotFound('Couldnt find "%s" in agents list (%s)' %
(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:
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>).
lastViewedAt (datetime): Datetime item was last accessed.
librarySectionID (int): :class:`~plexapi.library.LibrarySection` ID.
@ -20,6 +22,7 @@ class Video(PlexPartialObject):
ratingKey (int): Unique key identifying this item.
summary (str): Summary of the artist, track, or album.
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.)
titleSort (str): Title to use when sorting (defaults to title).
type (str): 'artist', 'album', or 'track'.
@ -32,6 +35,8 @@ class Video(PlexPartialObject):
self._data = data
self.listType = 'video'
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.lastViewedAt = utils.toDatetime(data.attrib.get('lastViewedAt'))
self.librarySectionID = data.attrib.get('librarySectionID')
@ -40,6 +45,7 @@ class Video(PlexPartialObject):
self.ratingKey = utils.cast(int, data.attrib.get('ratingKey'))
self.summary = data.attrib.get('summary')
self.thumb = data.attrib.get('thumb')
self.thumbBlurHash = data.attrib.get('thumbBlurHash')
self.title = data.attrib.get('title')
self.titleSort = data.attrib.get('titleSort', self.title)
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):
""" 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:
videoQuality (int): idx of quality of the video, one of VIDEO_QUALITY_* values defined in
:mod:`plexapi.sync` module.
client (:class:`plexapi.myplex.MyPlexDevice`): sync destination, see
:func:`plexapi.myplex.MyPlexAccount.sync`.
clientId (str): sync destination, see :func:`plexapi.myplex.MyPlexAccount.sync`.
:mod:`~plexapi.sync` module.
client (:class:`~plexapi.myplex.MyPlexDevice`): 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`.
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.
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
@ -277,17 +283,12 @@ class Movie(Playable, Video):
TAG = 'Video'
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):
""" Load attribute values from Plex XML response. """
Video._loadData(self, data)
Playable._loadData(self, data)
self._details_key = self.key + self._include
self.art = data.attrib.get('art')
self.audienceRating = utils.cast(float, data.attrib.get('audienceRating'))
self.audienceRatingImage = data.attrib.get('audienceRatingImage')
@ -343,7 +344,7 @@ class Movie(Playable, Video):
savepath (str): Defaults to current working dir.
keep_original_name (bool): True to keep the original file name otherwise
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 = []
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).
Raises:
:class:`plexapi.exceptions.BadRequest`: If season and episode is missing.
:class:`plexapi.exceptions.NotFound`: If the episode is missing.
:exc:`plexapi.exceptions.BadRequest`: If season and episode is missing.
:exc:`plexapi.exceptions.NotFound`: If the episode is missing.
"""
if title:
key = '/library/metadata/%s/allLeaves' % self.ratingKey
@ -488,7 +489,7 @@ class Show(Video):
return self.episodes(viewCount=0)
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)
def download(self, savepath=None, keep_original_name=False, **kwargs):
@ -498,7 +499,7 @@ class Show(Video):
savepath (str): Defaults to current working dir.
keep_original_name (bool): True to keep the original file name otherwise
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 = []
for episode in self.episodes():
@ -585,7 +586,7 @@ class Season(Video):
return self.fetchItem(key, parentIndex=self.index, index=episode)
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)
def show(self):
@ -607,7 +608,7 @@ class Season(Video):
savepath (str): Defaults to current working dir.
keep_original_name (bool): True to keep the original file name otherwise
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 = []
for episode in self.episodes():
@ -656,16 +657,10 @@ class Episode(Playable, Video):
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):
""" Load attribute values from Plex XML response. """
Video._loadData(self, data)
Playable._loadData(self, data)
self._details_key = self.key + self._include
self._seasonNumber = None # cached season number
art = data.attrib.get('art')
self.art = art if art and str(self.ratingKey) in art else None