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):
|
||||
""" 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.')
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.')
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue