mirror of
https://github.com/Tautulli/Tautulli.git
synced 2025-07-06 13:11:15 -07:00
Update python-plexapi-4.7.2
This commit is contained in:
parent
c8b02b06d6
commit
0770a301c7
12 changed files with 283 additions and 42 deletions
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
9
lib/plexapi/const.py
Normal 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}"
|
|
@ -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§ionID=%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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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(',')
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue