Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion plexapi/alert.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ def run(self):

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.')
Expand Down
30 changes: 15 additions & 15 deletions plexapi/audio.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,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.
fields (list): List of :class:`~plexapi.media.Field`.
index (sting): Index Number (often the track number).
key (str): API URL (/library/metadata/<ratingkey>).
Expand All @@ -20,6 +22,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'.
Expand All @@ -34,6 +37,8 @@ def _loadData(self, data):
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.fields = self.findItems(data, etag='Field')
self.index = data.attrib.get('index')
self.key = data.attrib.get('key')
Expand All @@ -42,6 +47,7 @@ def _loadData(self, data):
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')
Expand Down Expand Up @@ -70,20 +76,20 @@ def _defaultSyncTitle(self):

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
Expand Down Expand Up @@ -112,7 +118,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)
Expand All @@ -126,7 +131,6 @@ class Artist(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.key = self.key.replace('/children', '') # FIX_BUG_50
self.locations = self.listAttrs(data, 'path', etag='Location')
Expand Down Expand Up @@ -179,7 +183,7 @@ def download(self, savepath=None, keep_original_name=False, **kwargs):
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.
Expand All @@ -198,7 +202,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.
Expand All @@ -219,7 +222,6 @@ def __iter__(self):
def _loadData(self, data):
""" Load attribute values from Plex XML response. """
Audio._loadData(self, data)
self.art = data.attrib.get('art')
self.key = self.key.replace('/children', '') # fixes bug #50
self.originallyAvailableAt = utils.toDatetime(data.attrib.get('originallyAvailableAt'), '%Y-%m-%d')
self.parentKey = data.attrib.get('parentKey')
Expand Down Expand Up @@ -262,7 +264,7 @@ def download(self, savepath=None, keep_original_name=False, **kwargs):
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.
Expand All @@ -284,7 +286,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.
Expand Down Expand Up @@ -319,7 +320,6 @@ def _loadData(self, data):
""" Load attribute values from Plex XML response. """
Audio._loadData(self, data)
Playable._loadData(self, data)
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')
Expand Down
81 changes: 74 additions & 7 deletions plexapi/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,9 @@ def __init__(self, server, data, initpath=None):
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'))
Expand Down Expand Up @@ -81,14 +81,30 @@ def _buildItem(self, elem, cls=None, initpath=None):
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:
return self._buildItem(elem, cls, initpath)
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
Expand Down Expand Up @@ -203,9 +219,39 @@ def listAttrs(self, data, attr, **kwargs):
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
Expand Down Expand Up @@ -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 not in [None, []] and self.key == other.key
Expand Down Expand Up @@ -629,7 +696,7 @@ def getStreamURL(self, **params):
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)
Expand Down Expand Up @@ -694,7 +761,7 @@ def download(self, savepath=None, keep_original_name=False, **kwargs):
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.
Expand Down
12 changes: 6 additions & 6 deletions plexapi/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -140,7 +140,7 @@ def proxyThroughServer(self, value=True, server=None):
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
Expand Down Expand Up @@ -173,7 +173,7 @@ def query(self, path, method=None, headers=None, timeout=None, **kwargs):
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.

Expand All @@ -183,7 +183,7 @@ def sendCommand(self, command, proxy=None, **params):
**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]
Expand Down Expand Up @@ -299,7 +299,7 @@ def goToMedia(self, media, **params):
**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.')
Expand Down Expand Up @@ -469,7 +469,7 @@ def playMedia(self, media, offset=0, **params):
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.')
Expand Down
Loading