Update python-plexapi-4.7.2

This commit is contained in:
JonnyWong16 2021-10-14 22:44:12 -07:00
parent c8b02b06d6
commit 0770a301c7
No known key found for this signature in database
GPG key ID: B1F1F9807184697A
12 changed files with 283 additions and 42 deletions

View file

@ -6,6 +6,7 @@ from platform import uname
from uuid import getnode
from plexapi.config import PlexConfig, reset_base_headers
import plexapi.const as const
from plexapi.utils import SecretsFilter
# Load User Defined Config
@ -15,7 +16,7 @@ CONFIG = PlexConfig(CONFIG_PATH)
# PlexAPI Settings
PROJECT = 'PlexAPI'
VERSION = '4.6.1'
VERSION = __version__ = const.__version__
TIMEOUT = CONFIG.get('plexapi.timeout', 30, int)
X_PLEX_CONTAINER_SIZE = CONFIG.get('plexapi.container_size', 100, int)
X_PLEX_ENABLE_FAST_CONNECT = CONFIG.get('plexapi.enable_fast_connect', False, bool)

View file

@ -28,6 +28,7 @@ class Audio(PlexPartialObject):
librarySectionTitle (str): :class:`~plexapi.library.LibrarySection` title.
listType (str): Hardcoded as 'audio' (useful for search filters).
moods (List<:class:`~plexapi.media.Mood`>): List of mood objects.
musicAnalysisVersion (int): The Plex music analysis version for the item.
ratingKey (int): Unique key identifying the item.
summary (str): Summary of the artist, album, or track.
thumb (str): URL to thumbnail image (/library/metadata/<ratingKey>/thumb/<thumbid>).
@ -59,6 +60,7 @@ class Audio(PlexPartialObject):
self.librarySectionTitle = data.attrib.get('librarySectionTitle')
self.listType = 'audio'
self.moods = self.findItems(data, media.Mood)
self.musicAnalysisVersion = utils.cast(int, data.attrib.get('musicAnalysisVersion'))
self.ratingKey = utils.cast(int, data.attrib.get('ratingKey'))
self.summary = data.attrib.get('summary')
self.thumb = data.attrib.get('thumb')
@ -78,6 +80,11 @@ class Audio(PlexPartialObject):
""" Returns str, default title for a new syncItem. """
return self.title
@property
def hasSonicAnalysis(self):
""" Returns True if the audio has been sonically analyzed. """
return self.musicAnalysisVersion == 1
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.
@ -227,6 +234,7 @@ class Album(Audio, ArtMixin, PosterMixin, RatingMixin, UnmatchMatchMixin,
TAG (str): 'Directory'
TYPE (str): 'album'
collections (List<:class:`~plexapi.media.Collection`>): List of collection objects.
formats (List<:class:`~plexapi.media.Format`>): List of format objects.
genres (List<:class:`~plexapi.media.Genre`>): List of genre objects.
key (str): API URL (/library/metadata/<ratingkey>).
labels (List<:class:`~plexapi.media.Label`>): List of label objects.
@ -241,6 +249,7 @@ class Album(Audio, ArtMixin, PosterMixin, RatingMixin, UnmatchMatchMixin,
rating (float): Album rating (7.9; 9.8; 8.1).
studio (str): Studio that released the album.
styles (List<:class:`~plexapi.media.Style`>): List of style objects.
subformats (List<:class:`~plexapi.media.Subformat`>): List of subformat objects.
viewedLeafCount (int): Number of items marked as played in the album view.
year (int): Year the album was released.
"""
@ -251,6 +260,7 @@ class Album(Audio, ArtMixin, PosterMixin, RatingMixin, UnmatchMatchMixin,
""" Load attribute values from Plex XML response. """
Audio._loadData(self, data)
self.collections = self.findItems(data, media.Collection)
self.formats = self.findItems(data, media.Format)
self.genres = self.findItems(data, media.Genre)
self.key = self.key.replace('/children', '') # FIX_BUG_50
self.labels = self.findItems(data, media.Label)
@ -265,6 +275,7 @@ class Album(Audio, ArtMixin, PosterMixin, RatingMixin, UnmatchMatchMixin,
self.rating = utils.cast(float, data.attrib.get('rating'))
self.studio = data.attrib.get('studio')
self.styles = self.findItems(data, media.Style)
self.subformats = self.findItems(data, media.Subformat)
self.viewedLeafCount = utils.cast(int, data.attrib.get('viewedLeafCount'))
self.year = utils.cast(int, data.attrib.get('year'))
@ -415,3 +426,7 @@ class Track(Audio, Playable, ArtUrlMixin, PosterUrlMixin, RatingMixin,
def _defaultSyncTitle(self):
""" Returns str, default title for a new syncItem. """
return '%s - %s - %s' % (self.grandparentTitle, self.parentTitle, self.title)
def _getWebURL(self, base=None):
""" Get the Plex Web URL with the correct parameters. """
return self._server._buildWebURL(base=base, endpoint='details', key=self.parentKey)

View file

@ -505,6 +505,17 @@ class PlexPartialObject(PlexObject):
""" Returns True if this is not a full object. """
return not self.isFullObject()
def _edit(self, **kwargs):
""" Actually edit an object. """
if 'id' not in kwargs:
kwargs['id'] = self.ratingKey
if 'type' not in kwargs:
kwargs['type'] = utils.searchType(self.type)
part = '/library/sections/%s/all?%s' % (self.librarySectionID,
urlencode(kwargs))
self._server.query(part, method=self._server._session.put)
def edit(self, **kwargs):
""" Edit an object.
@ -517,14 +528,7 @@ class PlexPartialObject(PlexObject):
'collection[0].tag.tag': 'Super',
'collection.locked': 0}
"""
if 'id' not in kwargs:
kwargs['id'] = self.ratingKey
if 'type' not in kwargs:
kwargs['type'] = utils.searchType(self.type)
part = '/library/sections/%s/all?%s' % (self.librarySectionID,
urlencode(kwargs))
self._server.query(part, method=self._server._session.put)
self._edit(**kwargs)
def _edit_tags(self, tag, items, locked=True, remove=False):
""" Helper to edit tags.
@ -575,12 +579,28 @@ class PlexPartialObject(PlexObject):
def history(self, maxresults=9999999, mindate=None):
""" Get Play History for a media item.
Parameters:
maxresults (int): Only return the specified number of results (optional).
mindate (datetime): Min datetime to return results from.
"""
return self._server.history(maxresults=maxresults, mindate=mindate, ratingKey=self.ratingKey)
def _getWebURL(self, base=None):
""" Get the Plex Web URL with the correct parameters.
Private method to allow overriding parameters from subclasses.
"""
return self._server._buildWebURL(base=base, endpoint='details', key=self.key)
def getWebURL(self, base=None):
""" Returns the Plex Web URL for a media item.
Parameters:
base (str): The base URL before the fragment (``#!``).
Default is https://app.plex.tv/desktop.
"""
return self._getWebURL(base=base)
class Playable(object):
""" This is a general place to store functions specific to media that is Playable.

View file

@ -511,7 +511,9 @@ class PlexClient(PlexObject):
playqueue = media if isinstance(media, PlayQueue) else self._server.createPlayQueue(media)
self.sendCommand('playback/playMedia', **dict({
'providerIdentifier': 'com.plexapp.plugins.library',
'machineIdentifier': self._server.machineIdentifier,
'protocol': server_url[0],
'address': server_url[1].strip('/'),
'port': server_port,
'offset': offset,

View file

@ -442,7 +442,7 @@ class Collection(PlexPartialObject, AdvancedSettingsMixin, ArtMixin, PosterMixin
smart (bool): True to create a smart collection. Default False.
limit (int): Smart collections only, limit the number of items in the collection.
libtype (str): Smart collections only, the specific type of content to filter
(movie, show, season, episode, artist, album, track, photoalbum, photo, collection).
(movie, show, season, episode, artist, album, track, photoalbum, photo).
sort (str or list, optional): Smart collections only, a string of comma separated sort fields
or a list of sort fields in the format ``column:dir``.
See :func:`~plexapi.library.LibrarySection.search` for more info.

9
lib/plexapi/const.py Normal file
View file

@ -0,0 +1,9 @@
# -*- coding: utf-8 -*-
"""Constants used by plexapi."""
# Library version
MAJOR_VERSION = 4
MINOR_VERSION = 7
PATCH_VERSION = 2
__short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}"
__version__ = f"{__short_version__}.{PATCH_VERSION}"

View file

@ -456,10 +456,45 @@ class LibrarySection(PlexObject):
Parameters:
title (str): Title of the item to return.
Raises:
:exc:`~plexapi.exceptions.NotFound`: The title is not found in the library.
"""
key = '/library/sections/%s/all?title=%s' % (self.key, quote(title, safe=''))
key = '/library/sections/%s/all?includeGuids=1&title=%s' % (self.key, quote(title, safe=''))
return self.fetchItem(key, title__iexact=title)
def getGuid(self, guid):
""" Returns the media item with the specified external IMDB, TMDB, or TVDB ID.
Note: This search uses a PlexAPI operator so performance may be slow. All items from the
entire Plex library need to be retrieved for each guid search. It is recommended to create
your own lookup dictionary if you are searching for a lot of external guids.
Parameters:
guid (str): The external guid of the item to return.
Examples: IMDB ``imdb://tt0944947``, TMDB ``tmdb://1399``, TVDB ``tvdb://121361``.
Raises:
:exc:`~plexapi.exceptions.NotFound`: The guid is not found in the library.
Example:
.. code-block:: python
# This will retrieve all items in the entire library 3 times
result1 = library.getGuid('imdb://tt0944947')
result2 = library.getGuid('tmdb://1399')
result3 = library.getGuid('tvdb://121361')
# This will only retrieve all items in the library once to create a lookup dictionary
guidLookup = {guid.id: item for item in library.all() for guid in item.guids}
result1 = guidLookup['imdb://tt0944947']
result2 = guidLookup['tmdb://1399']
result3 = guidLookup['tvdb://121361']
"""
key = '/library/sections/%s/all?includeGuids=1' % self.key
return self.fetchItem(key, Guid__id__iexact=guid)
def all(self, libtype=None, **kwargs):
""" Returns a list of all items from this library section.
See description of :func:`~plexapi.library.LibrarySection.search()` for details about filtering / sorting.
@ -979,6 +1014,8 @@ class LibrarySection(PlexObject):
"""
args = {}
filter_args = []
args['includeGuids'] = int(bool(kwargs.pop('includeGuids', True)))
for field, values in list(kwargs.items()):
if field.split('__')[-1] not in OPERATORS:
filter_args.append(self._validateFilterField(field, values, libtype))
@ -1405,10 +1442,14 @@ class LibrarySection(PlexObject):
Parameters:
title (str): Title of the item to return.
Raises:
:exc:`~plexapi.exceptions.NotFound`: Unable to find collection.
"""
results = self.collections(title__iexact=title)
if results:
return results[0]
try:
return self.collections(title=title, title__iexact=title)[0]
except IndexError:
raise NotFound('Unable to find collection with title "%s".' % title) from None
def collections(self, **kwargs):
""" Returns a list of collections from this library section.
@ -1430,15 +1471,19 @@ class LibrarySection(PlexObject):
Parameters:
title (str): Title of the item to return.
"""
results = self.playlists(title__iexact=title)
if results:
return results[0]
def playlists(self, **kwargs):
Raises:
:exc:`~plexapi.exceptions.NotFound`: Unable to find playlist.
"""
try:
return self.playlists(title=title, title__iexact=title)[0]
except IndexError:
raise NotFound('Unable to find playlist with title "%s".' % title) from None
def playlists(self, sort=None, **kwargs):
""" Returns a list of playlists from this library section. """
key = '/playlists?type=15&playlistType=%s&sectionID=%s' % (self.CONTENT_TYPE, self.key)
return self.fetchItems(key, **kwargs)
return self._server.playlists(
playlistType=self.CONTENT_TYPE, sectionId=self.key, sort=sort, **kwargs)
@deprecated('use "listFields" instead')
def filterFields(self, mediaType=None):
@ -1448,6 +1493,23 @@ class LibrarySection(PlexObject):
def listChoices(self, category, libtype=None, **kwargs):
return self.listFilterChoices(field=category, libtype=libtype)
def getWebURL(self, base=None, tab=None, key=None):
""" Returns the Plex Web URL for the library.
Parameters:
base (str): The base URL before the fragment (``#!``).
Default is https://app.plex.tv/desktop.
tab (str): The library tab (recommended, library, collections, playlists, timeline).
key (str): A hub key.
"""
params = {'source': self.key}
if tab is not None:
params['pivot'] = tab
if key is not None:
params['key'] = key
params['pageType'] = 'list'
return self._server._buildWebURL(base=base, **params)
class MovieSection(LibrarySection):
""" Represents a :class:`~plexapi.library.LibrarySection` section containing movies.
@ -1849,6 +1911,7 @@ class Hub(PlexObject):
self.style = data.attrib.get('style')
self.title = data.attrib.get('title')
self.type = data.attrib.get('type')
self._section = None # cache for self.section
def __len__(self):
return self.size
@ -1860,6 +1923,13 @@ class Hub(PlexObject):
self.more = False
self.size = len(self.items)
def section(self):
""" Returns the :class:`~plexapi.library.LibrarySection` this hub belongs to.
"""
if self._section is None:
self._section = self._server.library.sectionByID(self.librarySectionID)
return self._section
class HubMediaTag(PlexObject):
""" Base class of hub media tag search results.

View file

@ -174,7 +174,7 @@ class MediaPart(PlexObject):
return [stream for stream in self.streams if isinstance(stream, SubtitleStream)]
def lyricStreams(self):
""" Returns a list of :class:`~plexapi.media.SubtitleStream` objects in this MediaPart. """
""" Returns a list of :class:`~plexapi.media.LyricStream` objects in this MediaPart. """
return [stream for stream in self.streams if isinstance(stream, LyricStream)]
def setDefaultAudioStream(self, stream):
@ -731,6 +731,18 @@ class Director(MediaTag):
FILTER = 'director'
@utils.registerPlexObject
class Format(MediaTag):
""" Represents a single Format media tag.
Attributes:
TAG (str): 'Format'
FILTER (str): 'format'
"""
TAG = 'Format'
FILTER = 'format'
@utils.registerPlexObject
class Genre(MediaTag):
""" Represents a single Genre media tag.
@ -815,6 +827,18 @@ class Style(MediaTag):
FILTER = 'style'
@utils.registerPlexObject
class Subformat(MediaTag):
""" Represents a single Subformat media tag.
Attributes:
TAG (str): 'Subformat'
FILTER (str): 'subformat'
"""
TAG = 'Subformat'
FILTER = 'subformat'
@utils.registerPlexObject
class Tag(MediaTag):
""" Represents a single Tag media tag.

View file

@ -97,6 +97,14 @@ class ArtMixin(ArtUrlMixin):
"""
art.select()
def lockArt(self):
""" Lock the background artwork for a Plex object. """
self._edit(**{'art.locked': 1})
def unlockArt(self):
""" Unlock the background artwork for a Plex object. """
self._edit(**{'art.locked': 0})
class BannerUrlMixin(object):
""" Mixin for Plex objects that can have a banner url. """
@ -138,6 +146,14 @@ class BannerMixin(BannerUrlMixin):
"""
banner.select()
def lockBanner(self):
""" Lock the banner for a Plex object. """
self._edit(**{'banner.locked': 1})
def unlockBanner(self):
""" Unlock the banner for a Plex object. """
self._edit(**{'banner.locked': 0})
class PosterUrlMixin(object):
""" Mixin for Plex objects that can have a poster url. """
@ -184,6 +200,14 @@ class PosterMixin(PosterUrlMixin):
"""
poster.select()
def lockPoster(self):
""" Lock the poster for a Plex object. """
self._edit(**{'thumb.locked': 1})
def unlockPoster(self):
""" Unlock the poster for a Plex object. """
self._edit(**{'thumb.locked': 0})
class RatingMixin(object):
""" Mixin for Plex objects that can have user star ratings. """
@ -577,7 +601,9 @@ class SmartFilterMixin(object):
key += '='
value = value[1:]
if key == 'type':
if key == 'includeGuids':
filters['includeGuids'] = int(value)
elif key == 'type':
filters['libtype'] = utils.reverseSearchType(value)
elif key == 'sort':
filters['sort'] = value.split(',')

View file

@ -137,6 +137,10 @@ class Photoalbum(PlexPartialObject, ArtMixin, PosterMixin, RatingMixin):
filepaths.append(filepath)
return filepaths
def _getWebURL(self, base=None):
""" Get the Plex Web URL with the correct parameters. """
return self._server._buildWebURL(base=base, endpoint='details', key=self.key, legacy=1)
@utils.registerPlexObject
class Photo(PlexPartialObject, Playable, ArtUrlMixin, PosterUrlMixin, RatingMixin, TagMixin):
@ -301,3 +305,7 @@ class Photo(PlexPartialObject, Playable, ArtUrlMixin, PosterUrlMixin, RatingMixi
if filepath:
filepaths.append(filepath)
return filepaths
def _getWebURL(self, base=None):
""" Get the Plex Web URL with the correct parameters. """
return self._server._buildWebURL(base=base, endpoint='details', key=self.parentKey, legacy=1)

View file

@ -2,7 +2,7 @@
import re
from urllib.parse import quote_plus, unquote
from plexapi import utils
from plexapi import media, utils
from plexapi.base import Playable, PlexPartialObject
from plexapi.exceptions import BadRequest, NotFound, Unsupported
from plexapi.library import LibrarySection
@ -24,6 +24,7 @@ class Playlist(PlexPartialObject, Playable, ArtMixin, PosterMixin, SmartFilterMi
content (str): The filter URI string for smart playlists.
duration (int): Duration of the playlist in milliseconds.
durationInSeconds (int): Duration of the playlist in seconds.
fields (List<:class:`~plexapi.media.Field`>): List of field objects.
guid (str): Plex GUID for the playlist (com.plexapp.agents.none://XXXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXX).
icon (str): Icon URI string for smart playlists.
key (str): API URL (/playlist/<ratingkey>).
@ -48,8 +49,9 @@ class Playlist(PlexPartialObject, Playable, ArtMixin, PosterMixin, SmartFilterMi
self.content = data.attrib.get('content')
self.duration = utils.cast(int, data.attrib.get('duration'))
self.durationInSeconds = utils.cast(int, data.attrib.get('durationInSeconds'))
self.icon = data.attrib.get('icon')
self.fields = self.findItems(data, media.Field)
self.guid = data.attrib.get('guid')
self.icon = data.attrib.get('icon')
self.key = data.attrib.get('key', '').replace('/items', '') # FIX_BUG_50
self.leafCount = utils.cast(int, data.attrib.get('leafCount'))
self.playlistType = data.attrib.get('playlistType')
@ -288,6 +290,11 @@ class Playlist(PlexPartialObject, Playable, ArtMixin, PosterMixin, SmartFilterMi
}))
self._server.query(key, method=self._server._session.put)
def _edit(self, **kwargs):
""" Actually edit the playlist. """
key = '%s%s' % (self.key, utils.joinArgs(kwargs))
self._server.query(key, method=self._server._session.put)
def edit(self, title=None, summary=None):
""" Edit the playlist.
@ -300,9 +307,7 @@ class Playlist(PlexPartialObject, Playable, ArtMixin, PosterMixin, SmartFilterMi
args['title'] = title
if summary:
args['summary'] = summary
key = '%s%s' % (self.key, utils.joinArgs(args))
self._server.query(key, method=self._server._session.put)
self._edit(**args)
def delete(self):
""" Delete the playlist. """
@ -341,13 +346,15 @@ class Playlist(PlexPartialObject, Playable, ArtMixin, PosterMixin, SmartFilterMi
return cls(server, data, initpath=key)
@classmethod
def _createSmart(cls, server, title, section, limit=None, sort=None, filters=None, **kwargs):
def _createSmart(cls, server, title, section, limit=None, libtype=None, sort=None, filters=None, **kwargs):
""" Create a smart playlist. """
if not isinstance(section, LibrarySection):
section = server.library.section(section)
libtype = libtype or section.METADATA_TYPE
searchKey = section._buildSearchKey(
sort=sort, libtype=section.METADATA_TYPE, limit=limit, filters=filters, **kwargs)
sort=sort, libtype=libtype, limit=limit, filters=filters, **kwargs)
uri = '%s%s' % (server._uriRoot(), searchKey)
key = '/playlists%s' % utils.joinArgs({
@ -361,7 +368,7 @@ class Playlist(PlexPartialObject, Playable, ArtMixin, PosterMixin, SmartFilterMi
@classmethod
def create(cls, server, title, section=None, items=None, smart=False, limit=None,
sort=None, filters=None, **kwargs):
libtype=None, sort=None, filters=None, **kwargs):
""" Create a playlist.
Parameters:
@ -373,6 +380,8 @@ class Playlist(PlexPartialObject, Playable, ArtMixin, PosterMixin, SmartFilterMi
:class:`~plexapi.video.Video`, or :class:`~plexapi.photo.Photo` objects to be added to the playlist.
smart (bool): True to create a smart playlist. Default False.
limit (int): Smart playlists only, limit the number of items in the playlist.
libtype (str): Smart playlists only, the specific type of content to filter
(movie, show, season, episode, artist, album, track, photoalbum, photo).
sort (str or list, optional): Smart playlists only, a string of comma separated sort fields
or a list of sort fields in the format ``column:dir``.
See :func:`~plexapi.library.LibrarySection.search` for more info.
@ -389,7 +398,7 @@ class Playlist(PlexPartialObject, Playable, ArtMixin, PosterMixin, SmartFilterMi
:class:`~plexapi.playlist.Playlist`: A new instance of the created Playlist.
"""
if smart:
return cls._createSmart(server, title, section, limit, sort, filters, **kwargs)
return cls._createSmart(server, title, section, limit, libtype, sort, filters, **kwargs)
else:
return cls._create(server, title, items)
@ -455,3 +464,7 @@ class Playlist(PlexPartialObject, Playable, ArtMixin, PosterMixin, SmartFilterMi
raise Unsupported('Unsupported playlist content')
return myplex.sync(sync_item, client=client, clientId=clientId)
def _getWebURL(self, base=None):
""" Get the Plex Web URL with the correct parameters. """
return self._server._buildWebURL(base=base, endpoint='playlist', key=self.key)

View file

@ -427,7 +427,7 @@ class PlexServer(PlexObject):
smart (bool): True to create a smart collection. Default False.
limit (int): Smart collections only, limit the number of items in the collection.
libtype (str): Smart collections only, the specific type of content to filter
(movie, show, season, episode, artist, album, track, photoalbum, photo, collection).
(movie, show, season, episode, artist, album, track, photoalbum, photo).
sort (str or list, optional): Smart collections only, a string of comma separated sort fields
or a list of sort fields in the format ``column:dir``.
See :func:`~plexapi.library.LibrarySection.search` for more info.
@ -448,7 +448,7 @@ class PlexServer(PlexObject):
libtype=libtype, sort=sort, filters=filters, **kwargs)
def createPlaylist(self, title, section=None, items=None, smart=False, limit=None,
sort=None, filters=None, **kwargs):
libtype=None, sort=None, filters=None, **kwargs):
""" Creates and returns a new :class:`~plexapi.playlist.Playlist`.
Parameters:
@ -459,6 +459,8 @@ class PlexServer(PlexObject):
:class:`~plexapi.video.Video`, or :class:`~plexapi.photo.Photo` objects to be added to the playlist.
smart (bool): True to create a smart playlist. Default False.
limit (int): Smart playlists only, limit the number of items in the playlist.
libtype (str): Smart playlists only, the specific type of content to filter
(movie, show, season, episode, artist, album, track, photoalbum, photo).
sort (str or list, optional): Smart playlists only, a string of comma separated sort fields
or a list of sort fields in the format ``column:dir``.
See :func:`~plexapi.library.LibrarySection.search` for more info.
@ -476,7 +478,7 @@ class PlexServer(PlexObject):
"""
return Playlist.create(
self, title, section=section, items=items, smart=smart, limit=limit,
sort=sort, filters=filters, **kwargs)
libtype=libtype, sort=sort, filters=filters, **kwargs)
def createPlayQueue(self, item, **kwargs):
""" Creates and returns a new :class:`~plexapi.playqueue.PlayQueue`.
@ -575,17 +577,29 @@ class PlexServer(PlexObject):
args['X-Plex-Container-Start'] += args['X-Plex-Container-Size']
return results
def playlists(self, playlistType=None):
def playlists(self, playlistType=None, sectionId=None, title=None, sort=None, **kwargs):
""" Returns a list of all :class:`~plexapi.playlist.Playlist` objects on the server.
Parameters:
playlistType (str, optional): The type of playlists to return (audio, video, photo).
Default returns all playlists.
sectionId (int, optional): The section ID (key) of the library to search within.
title (str, optional): General string query to search for. Partial string matches are allowed.
sort (str or list, optional): A string of comma separated sort fields in the format ``column:dir``.
"""
key = '/playlists'
if playlistType:
key = '%s?playlistType=%s' % (key, playlistType)
return self.fetchItems(key)
args = {}
if playlistType is not None:
args['playlistType'] = playlistType
if sectionId is not None:
args['sectionID'] = sectionId
if title is not None:
args['title'] = title
if sort is not None:
# TODO: Automatically retrieve and validate sort field similar to LibrarySection.search()
args['sort'] = sort
key = '/playlists%s' % utils.joinArgs(args)
return self.fetchItems(key, **kwargs)
def playlist(self, title):
""" Returns the :class:`~plexapi.client.Playlist` that matches the specified title.
@ -594,9 +608,12 @@ class PlexServer(PlexObject):
title (str): Title of the playlist to return.
Raises:
:exc:`~plexapi.exceptions.NotFound`: Invalid playlist title.
:exc:`~plexapi.exceptions.NotFound`: Unable to find playlist.
"""
return self.fetchItem('/playlists', title=title)
try:
return self.playlists(title=title, title__iexact=title)[0]
except IndexError:
raise NotFound('Unable to find playlist with title "%s".' % title) from None
def optimizedItems(self, removeAll=None):
""" Returns list of all :class:`~plexapi.media.Optimized` objects connected to server. """
@ -873,6 +890,42 @@ class PlexServer(PlexObject):
key = '/statistics/resources?timespan=6'
return self.fetchItems(key, StatisticsResources)
def _buildWebURL(self, base=None, endpoint=None, **kwargs):
""" Build the Plex Web URL for the object.
Parameters:
base (str): The base URL before the fragment (``#!``).
Default is https://app.plex.tv/desktop.
endpoint (str): The Plex Web URL endpoint.
None for server, 'playlist' for playlists, 'details' for all other media types.
**kwargs (dict): Dictionary of URL parameters.
"""
if base is None:
base = 'https://app.plex.tv/desktop/'
if endpoint:
return '%s#!/server/%s/%s%s' % (
base, self.machineIdentifier, endpoint, utils.joinArgs(kwargs)
)
else:
return '%s#!/media/%s/com.plexapp.plugins.library%s' % (
base, self.machineIdentifier, utils.joinArgs(kwargs)
)
def getWebURL(self, base=None, playlistTab=None):
""" Returns the Plex Web URL for the server.
Parameters:
base (str): The base URL before the fragment (``#!``).
Default is https://app.plex.tv/desktop.
playlistTab (str): The playlist tab (audio, video, photo). Only used for the playlist URL.
"""
if playlistTab is not None:
params = {'source': 'playlists', 'pivot': 'playlists.%s' % playlistTab}
else:
params = {'key': '/hubs', 'pageType': 'hub'}
return self._buildWebURL(base=base, **params)
class Account(PlexObject):
""" Contains the locally cached MyPlex account information. The properties provided don't