mirror of
https://github.com/Tautulli/Tautulli.git
synced 2025-07-11 07:46:07 -07:00
Bump plexapi from 4.8.0 to 4.9.1 (#1627)
* Bump plexapi from 4.8.0 to 4.9.1 Bumps [plexapi](https://github.com/pkkid/python-plexapi) from 4.8.0 to 4.9.1. - [Release notes](https://github.com/pkkid/python-plexapi/releases) - [Commits](https://github.com/pkkid/python-plexapi/compare/4.8.0...4.9.1) --- updated-dependencies: - dependency-name: plexapi dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> * Update plexapi==4.9.1 Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com>
This commit is contained in:
parent
a4ab5ab9be
commit
7f0abe0fe6
9 changed files with 254 additions and 76 deletions
|
@ -8,6 +8,7 @@ from plexapi.exceptions import BadRequest
|
||||||
from plexapi.mixins import AdvancedSettingsMixin, ArtUrlMixin, ArtMixin, PosterUrlMixin, PosterMixin
|
from plexapi.mixins import AdvancedSettingsMixin, ArtUrlMixin, ArtMixin, PosterUrlMixin, PosterMixin
|
||||||
from plexapi.mixins import RatingMixin, SplitMergeMixin, UnmatchMatchMixin
|
from plexapi.mixins import RatingMixin, SplitMergeMixin, UnmatchMatchMixin
|
||||||
from plexapi.mixins import CollectionMixin, CountryMixin, GenreMixin, LabelMixin, MoodMixin, SimilarArtistMixin, StyleMixin
|
from plexapi.mixins import CollectionMixin, CountryMixin, GenreMixin, LabelMixin, MoodMixin, SimilarArtistMixin, StyleMixin
|
||||||
|
from plexapi.playlist import Playlist
|
||||||
|
|
||||||
|
|
||||||
class Audio(PlexPartialObject):
|
class Audio(PlexPartialObject):
|
||||||
|
@ -222,6 +223,11 @@ class Artist(Audio, AdvancedSettingsMixin, ArtMixin, PosterMixin, RatingMixin, S
|
||||||
filepaths += track.download(_savepath, keep_original_name, **kwargs)
|
filepaths += track.download(_savepath, keep_original_name, **kwargs)
|
||||||
return filepaths
|
return filepaths
|
||||||
|
|
||||||
|
def station(self):
|
||||||
|
""" Returns a :class:`~plexapi.playlist.Playlist` artist radio station or `None`. """
|
||||||
|
key = '%s?includeStations=1' % self.key
|
||||||
|
return next(iter(self.fetchItems(key, cls=Playlist, rtag="Stations")), None)
|
||||||
|
|
||||||
|
|
||||||
@utils.registerPlexObject
|
@utils.registerPlexObject
|
||||||
class Album(Audio, ArtMixin, PosterMixin, RatingMixin, UnmatchMatchMixin,
|
class Album(Audio, ArtMixin, PosterMixin, RatingMixin, UnmatchMatchMixin,
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
# Library version
|
# Library version
|
||||||
MAJOR_VERSION = 4
|
MAJOR_VERSION = 4
|
||||||
MINOR_VERSION = 8
|
MINOR_VERSION = 9
|
||||||
PATCH_VERSION = 0
|
PATCH_VERSION = 1
|
||||||
__short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}"
|
__short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}"
|
||||||
__version__ = f"{__short_version__}.{PATCH_VERSION}"
|
__version__ = f"{__short_version__}.{PATCH_VERSION}"
|
||||||
|
|
|
@ -82,6 +82,28 @@ class Library(PlexObject):
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise NotFound('Invalid library sectionID: %s' % sectionID) from None
|
raise NotFound('Invalid library sectionID: %s' % sectionID) from None
|
||||||
|
|
||||||
|
def hubs(self, sectionID=None, identifier=None, **kwargs):
|
||||||
|
""" Returns a list of :class:`~plexapi.library.Hub` across all library sections.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
sectionID (int or str or list, optional):
|
||||||
|
IDs of the sections to limit results or "playlists".
|
||||||
|
identifier (str or list, optional):
|
||||||
|
Names of identifiers to limit results.
|
||||||
|
Available on `Hub` instances as the `hubIdentifier` attribute.
|
||||||
|
Examples: 'home.continue' or 'home.ondeck'
|
||||||
|
"""
|
||||||
|
if sectionID:
|
||||||
|
if not isinstance(sectionID, list):
|
||||||
|
sectionID = [sectionID]
|
||||||
|
kwargs['contentDirectoryID'] = ",".join(map(str, sectionID))
|
||||||
|
if identifier:
|
||||||
|
if not isinstance(identifier, list):
|
||||||
|
identifier = [identifier]
|
||||||
|
kwargs['identifier'] = ",".join(identifier)
|
||||||
|
key = '/hubs%s' % utils.joinArgs(kwargs)
|
||||||
|
return self.fetchItems(key)
|
||||||
|
|
||||||
def all(self, **kwargs):
|
def all(self, **kwargs):
|
||||||
""" Returns a list of all media from all library sections.
|
""" Returns a list of all media from all library sections.
|
||||||
This may be a very large dataset to retrieve.
|
This may be a very large dataset to retrieve.
|
||||||
|
@ -169,7 +191,7 @@ class Library(PlexObject):
|
||||||
name (str): Name of the library
|
name (str): Name of the library
|
||||||
agent (str): Example com.plexapp.agents.imdb
|
agent (str): Example com.plexapp.agents.imdb
|
||||||
type (str): movie, show, # check me
|
type (str): movie, show, # check me
|
||||||
location (str): /path/to/files
|
location (str or list): /path/to/files, ["/path/to/files", "/path/to/morefiles"]
|
||||||
language (str): Two letter language fx en
|
language (str): Two letter language fx en
|
||||||
kwargs (dict): Advanced options should be passed as a dict. where the id is the key.
|
kwargs (dict): Advanced options should be passed as a dict. where the id is the key.
|
||||||
|
|
||||||
|
@ -308,8 +330,16 @@ class Library(PlexObject):
|
||||||
40:South Africa, 41:Spain, 42:Sweden, 43:Switzerland, 44:Taiwan, 45:Trinidad,
|
40:South Africa, 41:Spain, 42:Sweden, 43:Switzerland, 44:Taiwan, 45:Trinidad,
|
||||||
46:United Kingdom, 47:United States, 48:Uruguay, 49:Venezuela.
|
46:United Kingdom, 47:United States, 48:Uruguay, 49:Venezuela.
|
||||||
"""
|
"""
|
||||||
part = '/library/sections?name=%s&type=%s&agent=%s&scanner=%s&language=%s&location=%s' % (
|
if isinstance(location, str):
|
||||||
quote_plus(name), type, agent, quote_plus(scanner), language, quote_plus(location)) # noqa E126
|
location = [location]
|
||||||
|
locations = []
|
||||||
|
for path in location:
|
||||||
|
if not self._server.isBrowsable(path):
|
||||||
|
raise BadRequest('Path: %s does not exist.' % path)
|
||||||
|
locations.append(('location', path))
|
||||||
|
|
||||||
|
part = '/library/sections?name=%s&type=%s&agent=%s&scanner=%s&language=%s&%s' % (
|
||||||
|
quote_plus(name), type, agent, quote_plus(scanner), language, urlencode(locations, doseq=True)) # noqa E126
|
||||||
if kwargs:
|
if kwargs:
|
||||||
part += urlencode(kwargs)
|
part += urlencode(kwargs)
|
||||||
return self._server.query(part, method=self._server._session.post)
|
return self._server.query(part, method=self._server._session.post)
|
||||||
|
@ -486,16 +516,76 @@ class LibrarySection(PlexObject):
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def edit(self, agent=None, **kwargs):
|
def edit(self, agent=None, **kwargs):
|
||||||
""" Edit a library (Note: agent is required). See :class:`~plexapi.library.Library` for example usage.
|
""" Edit a library. See :class:`~plexapi.library.Library` for example usage.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
|
agent (str, optional): The library agent.
|
||||||
kwargs (dict): Dict of settings to edit.
|
kwargs (dict): Dict of settings to edit.
|
||||||
"""
|
"""
|
||||||
if not agent:
|
if not agent:
|
||||||
agent = self.agent
|
agent = self.agent
|
||||||
part = '/library/sections/%s?agent=%s&%s' % (self.key, agent, urlencode(kwargs))
|
|
||||||
|
locations = []
|
||||||
|
if kwargs.get('location'):
|
||||||
|
if isinstance(kwargs['location'], str):
|
||||||
|
kwargs['location'] = [kwargs['location']]
|
||||||
|
for path in kwargs.pop('location'):
|
||||||
|
if not self._server.isBrowsable(path):
|
||||||
|
raise BadRequest('Path: %s does not exist.' % path)
|
||||||
|
locations.append(('location', path))
|
||||||
|
|
||||||
|
params = list(kwargs.items()) + locations
|
||||||
|
|
||||||
|
part = '/library/sections/%s?agent=%s&%s' % (self.key, agent, urlencode(params, doseq=True))
|
||||||
self._server.query(part, method=self._server._session.put)
|
self._server.query(part, method=self._server._session.put)
|
||||||
|
|
||||||
|
def addLocations(self, location):
|
||||||
|
""" Add a location to a library.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
location (str or list): A single folder path, list of paths.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
LibrarySection.addLocations('/path/1')
|
||||||
|
LibrarySection.addLocations(['/path/1', 'path/2', '/path/3'])
|
||||||
|
"""
|
||||||
|
locations = self.locations
|
||||||
|
if isinstance(location, str):
|
||||||
|
location = [location]
|
||||||
|
for path in location:
|
||||||
|
if not self._server.isBrowsable(path):
|
||||||
|
raise BadRequest('Path: %s does not exist.' % path)
|
||||||
|
locations.append(path)
|
||||||
|
self.edit(location=locations)
|
||||||
|
|
||||||
|
def removeLocations(self, location):
|
||||||
|
""" Remove a location from a library.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
location (str or list): A single folder path, list of paths.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
LibrarySection.removeLocations('/path/1')
|
||||||
|
LibrarySection.removeLocations(['/path/1', 'path/2', '/path/3'])
|
||||||
|
"""
|
||||||
|
locations = self.locations
|
||||||
|
if isinstance(location, str):
|
||||||
|
location = [location]
|
||||||
|
for path in location:
|
||||||
|
if path in locations:
|
||||||
|
locations.remove(path)
|
||||||
|
else:
|
||||||
|
raise BadRequest('Path: %s does not exist in the library.' % location)
|
||||||
|
if len(locations) == 0:
|
||||||
|
raise BadRequest('You are unable to remove all locations from a library.')
|
||||||
|
self.edit(location=locations)
|
||||||
|
|
||||||
def get(self, title):
|
def get(self, title):
|
||||||
""" Returns the media item with the specified title.
|
""" Returns the media item with the specified title.
|
||||||
|
|
||||||
|
@ -510,9 +600,7 @@ class LibrarySection(PlexObject):
|
||||||
|
|
||||||
def getGuid(self, guid):
|
def getGuid(self, guid):
|
||||||
""" Returns the media item with the specified external IMDB, TMDB, or TVDB ID.
|
""" 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
|
Note: Only available for the Plex Movie and Plex TV Series agents.
|
||||||
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:
|
Parameters:
|
||||||
guid (str): The external guid of the item to return.
|
guid (str): The external guid of the item to return.
|
||||||
|
@ -525,20 +613,23 @@ class LibrarySection(PlexObject):
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
# This will retrieve all items in the entire library 3 times
|
|
||||||
result1 = library.getGuid('imdb://tt0944947')
|
result1 = library.getGuid('imdb://tt0944947')
|
||||||
result2 = library.getGuid('tmdb://1399')
|
result2 = library.getGuid('tmdb://1399')
|
||||||
result3 = library.getGuid('tvdb://121361')
|
result3 = library.getGuid('tvdb://121361')
|
||||||
|
|
||||||
# This will only retrieve all items in the library once to create a lookup dictionary
|
# Alternatively, create your own guid lookup dictionary for faster performance
|
||||||
guidLookup = {guid.id: item for item in library.all() for guid in item.guids}
|
guidLookup = {guid.id: item for item in library.all() for guid in item.guids}
|
||||||
result1 = guidLookup['imdb://tt0944947']
|
result1 = guidLookup['imdb://tt0944947']
|
||||||
result2 = guidLookup['tmdb://1399']
|
result2 = guidLookup['tmdb://1399']
|
||||||
result3 = guidLookup['tvdb://121361']
|
result3 = guidLookup['tvdb://121361']
|
||||||
|
|
||||||
"""
|
"""
|
||||||
key = '/library/sections/%s/all?includeGuids=1' % self.key
|
try:
|
||||||
return self.fetchItem(key, Guid__id__iexact=guid)
|
dummy = self.search(maxresults=1)[0]
|
||||||
|
match = dummy.matches(agent=self.agent, title=guid.replace('://', '-'))
|
||||||
|
return self.search(guid=match[0].guid)[0]
|
||||||
|
except IndexError:
|
||||||
|
raise NotFound("Guid '%s' is not found in the library" % guid) from None
|
||||||
|
|
||||||
def all(self, libtype=None, **kwargs):
|
def all(self, libtype=None, **kwargs):
|
||||||
""" Returns a list of all items from this library section.
|
""" Returns a list of all items from this library section.
|
||||||
|
@ -556,13 +647,13 @@ class LibrarySection(PlexObject):
|
||||||
def hubs(self):
|
def hubs(self):
|
||||||
""" Returns a list of available :class:`~plexapi.library.Hub` for this library section.
|
""" Returns a list of available :class:`~plexapi.library.Hub` for this library section.
|
||||||
"""
|
"""
|
||||||
key = '/hubs/sections/%s' % self.key
|
key = '/hubs/sections/%s?includeStations=1' % self.key
|
||||||
return self.fetchItems(key)
|
return self.fetchItems(key)
|
||||||
|
|
||||||
def agents(self):
|
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))
|
return self._server.agents(self.type)
|
||||||
|
|
||||||
def settings(self):
|
def settings(self):
|
||||||
""" Returns a list of all library settings. """
|
""" Returns a list of all library settings. """
|
||||||
|
@ -606,6 +697,36 @@ class LibrarySection(PlexObject):
|
||||||
|
|
||||||
self.edit(**data)
|
self.edit(**data)
|
||||||
|
|
||||||
|
def _lockUnlockAllField(self, field, libtype=None, locked=True):
|
||||||
|
""" Lock or unlock a field for all items in the library. """
|
||||||
|
libtype = libtype or self.TYPE
|
||||||
|
args = {
|
||||||
|
'type': utils.searchType(libtype),
|
||||||
|
'%s.locked' % field: int(locked)
|
||||||
|
}
|
||||||
|
key = '/library/sections/%s/all%s' % (self.key, utils.joinArgs(args))
|
||||||
|
self._server.query(key, method=self._server._session.put)
|
||||||
|
|
||||||
|
def lockAllField(self, field, libtype=None):
|
||||||
|
""" Lock a field for all items in the library.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
field (str): The field to lock (e.g. thumb, rating, collection).
|
||||||
|
libtype (str, optional): The library type to lock (movie, show, season, episode,
|
||||||
|
artist, album, track, photoalbum, photo). Default is the main library type.
|
||||||
|
"""
|
||||||
|
self._lockUnlockAllField(field, libtype=libtype, locked=True)
|
||||||
|
|
||||||
|
def unlockAllField(self, field, libtype=None):
|
||||||
|
""" Unlock a field for all items in the library.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
field (str): The field to unlock (e.g. thumb, rating, collection).
|
||||||
|
libtype (str, optional): The library type to lock (movie, show, season, episode,
|
||||||
|
artist, album, track, photoalbum, photo). Default is the main library type.
|
||||||
|
"""
|
||||||
|
self._lockUnlockAllField(field, libtype=libtype, locked=False)
|
||||||
|
|
||||||
def timeline(self):
|
def timeline(self):
|
||||||
""" Returns a timeline query for this library section. """
|
""" Returns a timeline query for this library section. """
|
||||||
key = '/library/sections/%s/timeline' % self.key
|
key = '/library/sections/%s/timeline' % self.key
|
||||||
|
@ -1718,9 +1839,8 @@ class MusicSection(LibrarySection):
|
||||||
return self.fetchItems(key)
|
return self.fetchItems(key)
|
||||||
|
|
||||||
def stations(self):
|
def stations(self):
|
||||||
""" Returns a list of :class:`~plexapi.audio.Album` objects in this section. """
|
""" Returns a list of :class:`~plexapi.playlist.Playlist` stations in this section. """
|
||||||
key = '/hubs/sections/%s?includeStations=1' % self.key
|
return next((hub.items for hub in self.hubs() if hub.context == 'hub.music.stations'), None)
|
||||||
return self.fetchItems(key, cls=Station)
|
|
||||||
|
|
||||||
def searchArtists(self, **kwargs):
|
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. """
|
||||||
|
@ -1934,6 +2054,7 @@ class Hub(PlexObject):
|
||||||
context (str): The context of the hub.
|
context (str): The context of the hub.
|
||||||
hubKey (str): API URL for these specific hub items.
|
hubKey (str): API URL for these specific hub items.
|
||||||
hubIdentifier (str): The identifier of the hub.
|
hubIdentifier (str): The identifier of the hub.
|
||||||
|
items (list): List of items in the hub.
|
||||||
key (str): API URL for the hub.
|
key (str): API URL for the hub.
|
||||||
more (bool): True if there are more items to load (call reload() to fetch all items).
|
more (bool): True if there are more items to load (call reload() to fetch all items).
|
||||||
size (int): The number of items in the hub.
|
size (int): The number of items in the hub.
|
||||||
|
@ -2086,39 +2207,6 @@ class Place(HubMediaTag):
|
||||||
TAGTYPE = 400
|
TAGTYPE = 400
|
||||||
|
|
||||||
|
|
||||||
@utils.registerPlexObject
|
|
||||||
class Station(PlexObject):
|
|
||||||
""" Represents the Station area in the MusicSection.
|
|
||||||
|
|
||||||
Attributes:
|
|
||||||
TITLE (str): 'Stations'
|
|
||||||
TYPE (str): 'station'
|
|
||||||
hubIdentifier (str): Unknown.
|
|
||||||
size (int): Number of items found.
|
|
||||||
title (str): Title of this Hub.
|
|
||||||
type (str): Type of items in the Hub.
|
|
||||||
more (str): Unknown.
|
|
||||||
style (str): Unknown
|
|
||||||
items (str): List of items in the Hub.
|
|
||||||
"""
|
|
||||||
TITLE = 'Stations'
|
|
||||||
TYPE = 'station'
|
|
||||||
|
|
||||||
def _loadData(self, data):
|
|
||||||
""" Load attribute values from Plex XML response. """
|
|
||||||
self._data = data
|
|
||||||
self.hubIdentifier = data.attrib.get('hubIdentifier')
|
|
||||||
self.size = utils.cast(int, data.attrib.get('size'))
|
|
||||||
self.title = data.attrib.get('title')
|
|
||||||
self.type = data.attrib.get('type')
|
|
||||||
self.more = data.attrib.get('more')
|
|
||||||
self.style = data.attrib.get('style')
|
|
||||||
self.items = self.findItems(data)
|
|
||||||
|
|
||||||
def __len__(self):
|
|
||||||
return self.size
|
|
||||||
|
|
||||||
|
|
||||||
class FilteringType(PlexObject):
|
class FilteringType(PlexObject):
|
||||||
""" Represents a single filtering Type object for a library.
|
""" Represents a single filtering Type object for a library.
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ from urllib.parse import quote_plus
|
||||||
from plexapi import log, settings, utils
|
from plexapi import log, settings, utils
|
||||||
from plexapi.base import PlexObject
|
from plexapi.base import PlexObject
|
||||||
from plexapi.exceptions import BadRequest
|
from plexapi.exceptions import BadRequest
|
||||||
|
from plexapi.utils import deprecated
|
||||||
|
|
||||||
|
|
||||||
@utils.registerPlexObject
|
@utils.registerPlexObject
|
||||||
|
@ -1058,31 +1059,50 @@ class Agent(PlexObject):
|
||||||
self.hasAttribution = data.attrib.get('hasAttribution')
|
self.hasAttribution = data.attrib.get('hasAttribution')
|
||||||
self.hasPrefs = data.attrib.get('hasPrefs')
|
self.hasPrefs = data.attrib.get('hasPrefs')
|
||||||
self.identifier = data.attrib.get('identifier')
|
self.identifier = data.attrib.get('identifier')
|
||||||
|
self.name = data.attrib.get('name')
|
||||||
self.primary = data.attrib.get('primary')
|
self.primary = data.attrib.get('primary')
|
||||||
self.shortIdentifier = self.identifier.rsplit('.', 1)[1]
|
self.shortIdentifier = self.identifier.rsplit('.', 1)[1]
|
||||||
if 'mediaType' in self._initpath:
|
|
||||||
self.name = data.attrib.get('name')
|
|
||||||
self.languageCode = []
|
|
||||||
for code in data:
|
|
||||||
self.languageCode += [code.attrib.get('code')]
|
|
||||||
else:
|
|
||||||
self.mediaTypes = [AgentMediaType(server=self._server, data=d) for d in data]
|
|
||||||
|
|
||||||
def _settings(self):
|
if 'mediaType' in self._initpath:
|
||||||
|
self.languageCodes = self.listAttrs(data, 'code', etag='Language')
|
||||||
|
self.mediaTypes = []
|
||||||
|
else:
|
||||||
|
self.languageCodes = []
|
||||||
|
self.mediaTypes = self.findItems(data, cls=AgentMediaType)
|
||||||
|
|
||||||
|
@property
|
||||||
|
@deprecated('use "languageCodes" instead')
|
||||||
|
def languageCode(self):
|
||||||
|
return self.languageCodes
|
||||||
|
|
||||||
|
def settings(self):
|
||||||
key = '/:/plugins/%s/prefs' % self.identifier
|
key = '/:/plugins/%s/prefs' % self.identifier
|
||||||
data = self._server.query(key)
|
data = self._server.query(key)
|
||||||
return self.findItems(data, cls=settings.Setting)
|
return self.findItems(data, cls=settings.Setting)
|
||||||
|
|
||||||
|
@deprecated('use "settings" instead')
|
||||||
|
def _settings(self):
|
||||||
|
return self.settings()
|
||||||
|
|
||||||
|
|
||||||
class AgentMediaType(Agent):
|
class AgentMediaType(Agent):
|
||||||
|
""" Represents a single Agent MediaType.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
TAG (str): 'MediaType'
|
||||||
|
"""
|
||||||
|
TAG = 'MediaType'
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
uid = self._clean(self.firstAttr('name'))
|
uid = self._clean(self.firstAttr('name'))
|
||||||
return '<%s>' % ':'.join([p for p in [self.__class__.__name__, uid] if p])
|
return '<%s>' % ':'.join([p for p in [self.__class__.__name__, uid] if p])
|
||||||
|
|
||||||
def _loadData(self, data):
|
def _loadData(self, data):
|
||||||
|
self.languageCodes = self.listAttrs(data, 'code', etag='Language')
|
||||||
self.mediaType = utils.cast(int, data.attrib.get('mediaType'))
|
self.mediaType = utils.cast(int, data.attrib.get('mediaType'))
|
||||||
self.name = data.attrib.get('name')
|
self.name = data.attrib.get('name')
|
||||||
self.languageCode = []
|
|
||||||
for code in data:
|
@property
|
||||||
self.languageCode += [code.attrib.get('code')]
|
@deprecated('use "languageCodes" instead')
|
||||||
|
def languageCode(self):
|
||||||
|
return self.languageCodes
|
||||||
|
|
|
@ -29,7 +29,11 @@ class Playlist(PlexPartialObject, Playable, ArtMixin, PosterMixin, SmartFilterMi
|
||||||
icon (str): Icon URI string for smart playlists.
|
icon (str): Icon URI string for smart playlists.
|
||||||
key (str): API URL (/playlist/<ratingkey>).
|
key (str): API URL (/playlist/<ratingkey>).
|
||||||
leafCount (int): Number of items in the playlist view.
|
leafCount (int): Number of items in the playlist view.
|
||||||
|
librarySectionID (int): Library section identifier (radio only)
|
||||||
|
librarySectionKey (str): Library section key (radio only)
|
||||||
|
librarySectionTitle (str): Library section title (radio only)
|
||||||
playlistType (str): 'audio', 'video', or 'photo'
|
playlistType (str): 'audio', 'video', or 'photo'
|
||||||
|
radio (bool): If this playlist represents a radio station
|
||||||
ratingKey (int): Unique key identifying the playlist.
|
ratingKey (int): Unique key identifying the playlist.
|
||||||
smart (bool): True if the playlist is a smart playlist.
|
smart (bool): True if the playlist is a smart playlist.
|
||||||
summary (str): Summary of the playlist.
|
summary (str): Summary of the playlist.
|
||||||
|
@ -54,7 +58,11 @@ class Playlist(PlexPartialObject, Playable, ArtMixin, PosterMixin, SmartFilterMi
|
||||||
self.icon = data.attrib.get('icon')
|
self.icon = data.attrib.get('icon')
|
||||||
self.key = data.attrib.get('key', '').replace('/items', '') # FIX_BUG_50
|
self.key = data.attrib.get('key', '').replace('/items', '') # FIX_BUG_50
|
||||||
self.leafCount = utils.cast(int, data.attrib.get('leafCount'))
|
self.leafCount = utils.cast(int, data.attrib.get('leafCount'))
|
||||||
|
self.librarySectionID = utils.cast(int, data.attrib.get('librarySectionID'))
|
||||||
|
self.librarySectionKey = data.attrib.get('librarySectionKey')
|
||||||
|
self.librarySectionTitle = data.attrib.get('librarySectionTitle')
|
||||||
self.playlistType = data.attrib.get('playlistType')
|
self.playlistType = data.attrib.get('playlistType')
|
||||||
|
self.radio = utils.cast(bool, data.attrib.get('radio', 0))
|
||||||
self.ratingKey = utils.cast(int, data.attrib.get('ratingKey'))
|
self.ratingKey = utils.cast(int, data.attrib.get('ratingKey'))
|
||||||
self.smart = utils.cast(bool, data.attrib.get('smart'))
|
self.smart = utils.cast(bool, data.attrib.get('smart'))
|
||||||
self.summary = data.attrib.get('summary')
|
self.summary = data.attrib.get('summary')
|
||||||
|
@ -169,6 +177,8 @@ class Playlist(PlexPartialObject, Playable, ArtMixin, PosterMixin, SmartFilterMi
|
||||||
|
|
||||||
def items(self):
|
def items(self):
|
||||||
""" Returns a list of all items in the playlist. """
|
""" Returns a list of all items in the playlist. """
|
||||||
|
if self.radio:
|
||||||
|
return []
|
||||||
if self._items is None:
|
if self._items is None:
|
||||||
key = '%s/items' % self.key
|
key = '%s/items' % self.key
|
||||||
items = self.fetchItems(key)
|
items = self.fetchItems(key)
|
||||||
|
|
|
@ -175,8 +175,11 @@ class PlayQueue(PlexObject):
|
||||||
args["uri"] = "library:///directory/{uri_args}".format(uri_args=uri_args)
|
args["uri"] = "library:///directory/{uri_args}".format(uri_args=uri_args)
|
||||||
args["type"] = items[0].listType
|
args["type"] = items[0].listType
|
||||||
elif items.type == "playlist":
|
elif items.type == "playlist":
|
||||||
args["playlistID"] = items.ratingKey
|
|
||||||
args["type"] = items.playlistType
|
args["type"] = items.playlistType
|
||||||
|
if items.radio:
|
||||||
|
args["uri"] = f"server://{server.machineIdentifier}/{server.library.identifier}{items.key}"
|
||||||
|
else:
|
||||||
|
args["playlistID"] = items.ratingKey
|
||||||
else:
|
else:
|
||||||
uuid = items.section().uuid
|
uuid = items.section().uuid
|
||||||
args["type"] = items.listType
|
args["type"] = items.listType
|
||||||
|
@ -192,6 +195,42 @@ class PlayQueue(PlexObject):
|
||||||
c._server = server
|
c._server = server
|
||||||
return c
|
return c
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def fromStationKey(cls, server, key):
|
||||||
|
"""Create and return a new :class:`~plexapi.playqueue.PlayQueue`.
|
||||||
|
|
||||||
|
This is a convenience method to create a `PlayQueue` for
|
||||||
|
radio stations when only the `key` string is available.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
server (:class:`~plexapi.server.PlexServer`): Server you are connected to.
|
||||||
|
key (str): A station key as provided by :func:`~plexapi.library.LibrarySection.hubs()`
|
||||||
|
or :func:`~plexapi.audio.Artist.station()`
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from plexapi.playqueue import PlayQueue
|
||||||
|
music = server.library.section("Music")
|
||||||
|
artist = music.get("Artist Name")
|
||||||
|
station = artist.station()
|
||||||
|
key = station.key # "/library/metadata/12855/station/8bd39616-dbdb-459e-b8da-f46d0b170af4?type=10"
|
||||||
|
pq = PlayQueue.fromStationKey(server, key)
|
||||||
|
client = server.clients()[0]
|
||||||
|
client.playMedia(pq)
|
||||||
|
"""
|
||||||
|
args = {
|
||||||
|
"type": "audio",
|
||||||
|
"uri": f"server://{server.machineIdentifier}/{server.library.identifier}{key}"
|
||||||
|
}
|
||||||
|
path = f"/playQueues{utils.joinArgs(args)}"
|
||||||
|
data = server.query(path, method=server._session.post)
|
||||||
|
c = cls(server, data, initpath=path)
|
||||||
|
c.playQueueType = args["type"]
|
||||||
|
c._server = server
|
||||||
|
return c
|
||||||
|
|
||||||
def addItem(self, item, playNext=False, refresh=True):
|
def addItem(self, item, playNext=False, refresh=True):
|
||||||
"""
|
"""
|
||||||
Append the provided item to the "Up Next" section of the PlayQueue.
|
Append the provided item to the "Up Next" section of the PlayQueue.
|
||||||
|
|
|
@ -3,6 +3,7 @@ from urllib.parse import urlencode
|
||||||
from xml.etree import ElementTree
|
from xml.etree import ElementTree
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
|
import os
|
||||||
from plexapi import (BASE_HEADERS, CONFIG, TIMEOUT, X_PLEX_CONTAINER_SIZE, log,
|
from plexapi import (BASE_HEADERS, CONFIG, TIMEOUT, X_PLEX_CONTAINER_SIZE, log,
|
||||||
logfilter)
|
logfilter)
|
||||||
from plexapi import utils
|
from plexapi import utils
|
||||||
|
@ -228,10 +229,10 @@ class PlexServer(PlexObject):
|
||||||
return activities
|
return activities
|
||||||
|
|
||||||
def agents(self, mediaType=None):
|
def agents(self, mediaType=None):
|
||||||
""" Returns the :class:`~plexapi.media.Agent` objects this server has available. """
|
""" Returns a list of :class:`~plexapi.media.Agent` objects this server has available. """
|
||||||
key = '/system/agents'
|
key = '/system/agents'
|
||||||
if mediaType:
|
if mediaType:
|
||||||
key += '?mediaType=%s' % mediaType
|
key += '?mediaType=%s' % utils.searchType(mediaType)
|
||||||
return self.fetchItems(key)
|
return self.fetchItems(key)
|
||||||
|
|
||||||
def createToken(self, type='delegation', scope='all'):
|
def createToken(self, type='delegation', scope='all'):
|
||||||
|
@ -384,6 +385,18 @@ class PlexServer(PlexObject):
|
||||||
for path, paths, files in self.walk(_path):
|
for path, paths, files in self.walk(_path):
|
||||||
yield path, paths, files
|
yield path, paths, files
|
||||||
|
|
||||||
|
def isBrowsable(self, path):
|
||||||
|
""" Returns True if the Plex server can browse the given path.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
path (:class:`~plexapi.library.Path` or str): Full path to browse.
|
||||||
|
"""
|
||||||
|
if isinstance(path, Path):
|
||||||
|
path = path.path
|
||||||
|
path = os.path.normpath(path)
|
||||||
|
paths = [p.path for p in self.browse(os.path.dirname(path), includeFiles=False)]
|
||||||
|
return path in paths
|
||||||
|
|
||||||
def clients(self):
|
def clients(self):
|
||||||
""" Returns list of all :class:`~plexapi.client.PlexClient` objects connected to server. """
|
""" Returns list of all :class:`~plexapi.client.PlexClient` objects connected to server. """
|
||||||
items = []
|
items = []
|
||||||
|
|
|
@ -113,17 +113,19 @@ class Setting(PlexObject):
|
||||||
|
|
||||||
def _loadData(self, data):
|
def _loadData(self, data):
|
||||||
""" Load attribute values from Plex XML response. """
|
""" Load attribute values from Plex XML response. """
|
||||||
self._setValue = None
|
self.type = data.attrib.get('type')
|
||||||
|
self.advanced = utils.cast(bool, data.attrib.get('advanced'))
|
||||||
|
self.default = self._cast(data.attrib.get('default'))
|
||||||
|
self.enumValues = self._getEnumValues(data)
|
||||||
|
self.group = data.attrib.get('group')
|
||||||
|
self.hidden = utils.cast(bool, data.attrib.get('hidden'))
|
||||||
self.id = data.attrib.get('id')
|
self.id = data.attrib.get('id')
|
||||||
self.label = data.attrib.get('label')
|
self.label = data.attrib.get('label')
|
||||||
|
self.option = data.attrib.get('option')
|
||||||
|
self.secure = utils.cast(bool, data.attrib.get('secure'))
|
||||||
self.summary = data.attrib.get('summary')
|
self.summary = data.attrib.get('summary')
|
||||||
self.type = data.attrib.get('type')
|
|
||||||
self.default = self._cast(data.attrib.get('default'))
|
|
||||||
self.value = self._cast(data.attrib.get('value'))
|
self.value = self._cast(data.attrib.get('value'))
|
||||||
self.hidden = utils.cast(bool, data.attrib.get('hidden'))
|
self._setValue = None
|
||||||
self.advanced = utils.cast(bool, data.attrib.get('advanced'))
|
|
||||||
self.group = data.attrib.get('group')
|
|
||||||
self.enumValues = self._getEnumValues(data)
|
|
||||||
|
|
||||||
def _cast(self, value):
|
def _cast(self, value):
|
||||||
""" Cast the specific value to the type of this setting. """
|
""" Cast the specific value to the type of this setting. """
|
||||||
|
@ -132,8 +134,8 @@ class Setting(PlexObject):
|
||||||
return value
|
return value
|
||||||
|
|
||||||
def _getEnumValues(self, data):
|
def _getEnumValues(self, data):
|
||||||
""" Returns a list of dictionary of valis value for this setting. """
|
""" Returns a list or dictionary of values for this setting. """
|
||||||
enumstr = data.attrib.get('enumValues')
|
enumstr = data.attrib.get('enumValues') or data.attrib.get('values')
|
||||||
if not enumstr:
|
if not enumstr:
|
||||||
return None
|
return None
|
||||||
if ':' in enumstr:
|
if ':' in enumstr:
|
||||||
|
|
|
@ -27,7 +27,7 @@ musicbrainzngs==0.7.1
|
||||||
oauthlib==3.1.1
|
oauthlib==3.1.1
|
||||||
packaging==21.3
|
packaging==21.3
|
||||||
paho-mqtt==1.6.1
|
paho-mqtt==1.6.1
|
||||||
plexapi==4.8.0
|
plexapi==4.9.1
|
||||||
portend==3.1.0
|
portend==3.1.0
|
||||||
profilehooks==1.12.0
|
profilehooks==1.12.0
|
||||||
PyJWT==2.3.0
|
PyJWT==2.3.0
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue