mirror of
https://github.com/Tautulli/Tautulli.git
synced 2025-07-06 21:21:15 -07:00
Bump plexapi from 4.15.15 to 4.15.16 (#2383)
* Bump plexapi from 4.15.15 to 4.15.16 Bumps [plexapi](https://github.com/pkkid/python-plexapi) from 4.15.15 to 4.15.16. - [Release notes](https://github.com/pkkid/python-plexapi/releases) - [Commits](https://github.com/pkkid/python-plexapi/compare/4.15.15...4.15.16) --- updated-dependencies: - dependency-name: plexapi dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> * Update plexapi==4.15.16 --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> [skip ci]
This commit is contained in:
parent
de697cb2ca
commit
cc1a325eac
13 changed files with 153 additions and 45 deletions
|
@ -193,6 +193,7 @@ class Artist(
|
||||||
similar (List<:class:`~plexapi.media.Similar`>): List of similar objects.
|
similar (List<:class:`~plexapi.media.Similar`>): List of similar objects.
|
||||||
styles (List<:class:`~plexapi.media.Style`>): List of style objects.
|
styles (List<:class:`~plexapi.media.Style`>): List of style objects.
|
||||||
theme (str): URL to theme resource (/library/metadata/<ratingkey>/theme/<themeid>).
|
theme (str): URL to theme resource (/library/metadata/<ratingkey>/theme/<themeid>).
|
||||||
|
ultraBlurColors (:class:`~plexapi.media.UltraBlurColors`): Ultra blur color object.
|
||||||
"""
|
"""
|
||||||
TAG = 'Directory'
|
TAG = 'Directory'
|
||||||
TYPE = 'artist'
|
TYPE = 'artist'
|
||||||
|
@ -213,6 +214,7 @@ class Artist(
|
||||||
self.similar = self.findItems(data, media.Similar)
|
self.similar = self.findItems(data, media.Similar)
|
||||||
self.styles = self.findItems(data, media.Style)
|
self.styles = self.findItems(data, media.Style)
|
||||||
self.theme = data.attrib.get('theme')
|
self.theme = data.attrib.get('theme')
|
||||||
|
self.ultraBlurColors = self.findItem(data, media.UltraBlurColors)
|
||||||
|
|
||||||
def __iter__(self):
|
def __iter__(self):
|
||||||
for album in self.albums():
|
for album in self.albums():
|
||||||
|
@ -281,6 +283,21 @@ class Artist(
|
||||||
filepaths += track.download(_savepath, keep_original_name, **kwargs)
|
filepaths += track.download(_savepath, keep_original_name, **kwargs)
|
||||||
return filepaths
|
return filepaths
|
||||||
|
|
||||||
|
def popularTracks(self):
|
||||||
|
""" Returns a list of :class:`~plexapi.audio.Track` popular tracks by the artist. """
|
||||||
|
filters = {
|
||||||
|
'album.subformat!': 'Compilation,Live',
|
||||||
|
'artist.id': self.ratingKey,
|
||||||
|
'group': 'title',
|
||||||
|
'ratingCount>>': 0,
|
||||||
|
}
|
||||||
|
return self.section().search(
|
||||||
|
libtype='track',
|
||||||
|
filters=filters,
|
||||||
|
sort='ratingCount:desc',
|
||||||
|
limit=100
|
||||||
|
)
|
||||||
|
|
||||||
def station(self):
|
def station(self):
|
||||||
""" Returns a :class:`~plexapi.playlist.Playlist` artist radio station or `None`. """
|
""" Returns a :class:`~plexapi.playlist.Playlist` artist radio station or `None`. """
|
||||||
key = f'{self.key}?includeStations=1'
|
key = f'{self.key}?includeStations=1'
|
||||||
|
@ -325,6 +342,7 @@ class Album(
|
||||||
studio (str): Studio that released the album.
|
studio (str): Studio that released the album.
|
||||||
styles (List<:class:`~plexapi.media.Style`>): List of style objects.
|
styles (List<:class:`~plexapi.media.Style`>): List of style objects.
|
||||||
subformats (List<:class:`~plexapi.media.Subformat`>): List of subformat objects.
|
subformats (List<:class:`~plexapi.media.Subformat`>): List of subformat objects.
|
||||||
|
ultraBlurColors (:class:`~plexapi.media.UltraBlurColors`): Ultra blur color object.
|
||||||
viewedLeafCount (int): Number of items marked as played in the album view.
|
viewedLeafCount (int): Number of items marked as played in the album view.
|
||||||
year (int): Year the album was released.
|
year (int): Year the album was released.
|
||||||
"""
|
"""
|
||||||
|
@ -354,6 +372,7 @@ class Album(
|
||||||
self.studio = data.attrib.get('studio')
|
self.studio = data.attrib.get('studio')
|
||||||
self.styles = self.findItems(data, media.Style)
|
self.styles = self.findItems(data, media.Style)
|
||||||
self.subformats = self.findItems(data, media.Subformat)
|
self.subformats = self.findItems(data, media.Subformat)
|
||||||
|
self.ultraBlurColors = self.findItem(data, media.UltraBlurColors)
|
||||||
self.viewedLeafCount = utils.cast(int, data.attrib.get('viewedLeafCount'))
|
self.viewedLeafCount = utils.cast(int, data.attrib.get('viewedLeafCount'))
|
||||||
self.year = utils.cast(int, data.attrib.get('year'))
|
self.year = utils.cast(int, data.attrib.get('year'))
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ import re
|
||||||
from typing import TYPE_CHECKING, Generic, Iterable, List, Optional, TypeVar, Union
|
from typing import TYPE_CHECKING, Generic, Iterable, List, Optional, TypeVar, Union
|
||||||
import weakref
|
import weakref
|
||||||
from functools import cached_property
|
from functools import cached_property
|
||||||
from urllib.parse import urlencode
|
from urllib.parse import parse_qsl, urlencode, urlparse
|
||||||
from xml.etree import ElementTree
|
from xml.etree import ElementTree
|
||||||
from xml.etree.ElementTree import Element
|
from xml.etree.ElementTree import Element
|
||||||
|
|
||||||
|
@ -391,10 +391,9 @@ class PlexObject:
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
key (string, optional): Override the key to reload.
|
key (string, optional): Override the key to reload.
|
||||||
**kwargs (dict): A dictionary of XML include parameters to exclude or override.
|
**kwargs (dict): A dictionary of XML include parameters to include/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.
|
See :class:`~plexapi.base.PlexPartialObject` for all the available include parameters.
|
||||||
|
Set parameter to True to include and False to exclude.
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
|
@ -402,20 +401,28 @@ class PlexObject:
|
||||||
|
|
||||||
from plexapi.server import PlexServer
|
from plexapi.server import PlexServer
|
||||||
plex = PlexServer('http://localhost:32400', token='xxxxxxxxxxxxxxxxxxxx')
|
plex = PlexServer('http://localhost:32400', token='xxxxxxxxxxxxxxxxxxxx')
|
||||||
movie = plex.library.section('Movies').get('Cars')
|
|
||||||
|
|
||||||
# Partial reload of the movie without the `checkFiles` parameter.
|
# Search results are partial objects.
|
||||||
# Excluding `checkFiles` will prevent the Plex server from reading the
|
movie = plex.library.section('Movies').get('Cars')
|
||||||
# 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
|
movie.isPartialObject() # Returns True
|
||||||
|
|
||||||
# Full reload of the movie with all include parameters.
|
# Partial reload of the movie without a default include parameter.
|
||||||
|
# The movie object will remain as a partial object.
|
||||||
|
movie.reload(includeMarkers=False)
|
||||||
|
movie.isPartialObject() # Returns True
|
||||||
|
|
||||||
|
# Full reload of the movie with all default include parameters.
|
||||||
# The movie object will be a full object.
|
# The movie object will be a full object.
|
||||||
movie.reload()
|
movie.reload()
|
||||||
movie.isFullObject() # Returns True
|
movie.isFullObject() # Returns True
|
||||||
|
|
||||||
|
# Full reload of the movie with all default and extra include parameter.
|
||||||
|
# Including `checkFiles` will tell the Plex server to check if the file
|
||||||
|
# still exists and is accessible.
|
||||||
|
# The movie object will be a full object.
|
||||||
|
movie.reload(checkFiles=True)
|
||||||
|
movie.isFullObject() # Returns True
|
||||||
|
|
||||||
"""
|
"""
|
||||||
return self._reload(key=key, **kwargs)
|
return self._reload(key=key, **kwargs)
|
||||||
|
|
||||||
|
@ -505,25 +512,25 @@ class PlexPartialObject(PlexObject):
|
||||||
automatically and update itself.
|
automatically and update itself.
|
||||||
"""
|
"""
|
||||||
_INCLUDES = {
|
_INCLUDES = {
|
||||||
'checkFiles': 1,
|
'checkFiles': 0,
|
||||||
'includeAllConcerts': 1,
|
'includeAllConcerts': 0,
|
||||||
'includeBandwidths': 1,
|
'includeBandwidths': 1,
|
||||||
'includeChapters': 1,
|
'includeChapters': 1,
|
||||||
'includeChildren': 1,
|
'includeChildren': 0,
|
||||||
'includeConcerts': 1,
|
'includeConcerts': 0,
|
||||||
'includeExternalMedia': 1,
|
'includeExternalMedia': 0,
|
||||||
'includeExtras': 1,
|
'includeExtras': 0,
|
||||||
'includeFields': 'thumbBlurHash,artBlurHash',
|
'includeFields': 'thumbBlurHash,artBlurHash',
|
||||||
'includeGeolocation': 1,
|
'includeGeolocation': 1,
|
||||||
'includeLoudnessRamps': 1,
|
'includeLoudnessRamps': 1,
|
||||||
'includeMarkers': 1,
|
'includeMarkers': 1,
|
||||||
'includeOnDeck': 1,
|
'includeOnDeck': 0,
|
||||||
'includePopularLeaves': 1,
|
'includePopularLeaves': 0,
|
||||||
'includePreferences': 1,
|
'includePreferences': 0,
|
||||||
'includeRelated': 1,
|
'includeRelated': 0,
|
||||||
'includeRelatedCount': 1,
|
'includeRelatedCount': 0,
|
||||||
'includeReviews': 1,
|
'includeReviews': 0,
|
||||||
'includeStations': 1,
|
'includeStations': 0,
|
||||||
}
|
}
|
||||||
_EXCLUDES = {
|
_EXCLUDES = {
|
||||||
'excludeElements': (
|
'excludeElements': (
|
||||||
|
@ -592,7 +599,11 @@ class PlexPartialObject(PlexObject):
|
||||||
search result for a movie often only contain a portion of the attributes a full
|
search result for a movie often only contain a portion of the attributes a full
|
||||||
object (main url) for that movie would contain.
|
object (main url) for that movie would contain.
|
||||||
"""
|
"""
|
||||||
return not self.key or (self._details_key or self.key) == self._initpath
|
parsed_key = urlparse(self._details_key or self.key)
|
||||||
|
parsed_initpath = urlparse(self._initpath)
|
||||||
|
query_key = set(parse_qsl(parsed_key.query))
|
||||||
|
query_init = set(parse_qsl(parsed_initpath.query))
|
||||||
|
return not self.key or (parsed_key.path == parsed_initpath.path and query_key <= query_init)
|
||||||
|
|
||||||
def isPartialObject(self):
|
def isPartialObject(self):
|
||||||
""" Returns True if this is not a full object. """
|
""" Returns True if this is not a full object. """
|
||||||
|
|
|
@ -197,7 +197,7 @@ class PlexClient(PlexObject):
|
||||||
raise NotFound(message)
|
raise NotFound(message)
|
||||||
else:
|
else:
|
||||||
raise BadRequest(message)
|
raise BadRequest(message)
|
||||||
data = response.text.encode('utf8')
|
data = utils.cleanXMLString(response.text).encode('utf8')
|
||||||
return ElementTree.fromstring(data) if data.strip() else None
|
return ElementTree.fromstring(data) if data.strip() else None
|
||||||
|
|
||||||
def sendCommand(self, command, proxy=None, **params):
|
def sendCommand(self, command, proxy=None, **params):
|
||||||
|
|
|
@ -60,6 +60,7 @@ class Collection(
|
||||||
title (str): Name of the collection.
|
title (str): Name of the collection.
|
||||||
titleSort (str): Title to use when sorting (defaults to title).
|
titleSort (str): Title to use when sorting (defaults to title).
|
||||||
type (str): 'collection'
|
type (str): 'collection'
|
||||||
|
ultraBlurColors (:class:`~plexapi.media.UltraBlurColors`): Ultra blur color object.
|
||||||
updatedAt (datetime): Datetime the collection was updated.
|
updatedAt (datetime): Datetime the collection was updated.
|
||||||
userRating (float): Rating of the collection (0.0 - 10.0) equaling (0 stars - 5 stars).
|
userRating (float): Rating of the collection (0.0 - 10.0) equaling (0 stars - 5 stars).
|
||||||
"""
|
"""
|
||||||
|
@ -102,6 +103,7 @@ class Collection(
|
||||||
self.title = data.attrib.get('title')
|
self.title = data.attrib.get('title')
|
||||||
self.titleSort = data.attrib.get('titleSort', self.title)
|
self.titleSort = data.attrib.get('titleSort', self.title)
|
||||||
self.type = data.attrib.get('type')
|
self.type = data.attrib.get('type')
|
||||||
|
self.ultraBlurColors = self.findItem(data, media.UltraBlurColors)
|
||||||
self.updatedAt = utils.toDatetime(data.attrib.get('updatedAt'))
|
self.updatedAt = utils.toDatetime(data.attrib.get('updatedAt'))
|
||||||
self.userRating = utils.cast(float, data.attrib.get('userRating'))
|
self.userRating = utils.cast(float, data.attrib.get('userRating'))
|
||||||
self._items = None # cache for self.items
|
self._items = None # cache for self.items
|
||||||
|
|
|
@ -4,6 +4,6 @@
|
||||||
# Library version
|
# Library version
|
||||||
MAJOR_VERSION = 4
|
MAJOR_VERSION = 4
|
||||||
MINOR_VERSION = 15
|
MINOR_VERSION = 15
|
||||||
PATCH_VERSION = 15
|
PATCH_VERSION = 16
|
||||||
__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}"
|
||||||
|
|
|
@ -2823,7 +2823,8 @@ class FilteringType(PlexObject):
|
||||||
additionalFields.extend([
|
additionalFields.extend([
|
||||||
('duration', 'integer', 'Duration'),
|
('duration', 'integer', 'Duration'),
|
||||||
('viewOffset', 'integer', 'View Offset'),
|
('viewOffset', 'integer', 'View Offset'),
|
||||||
('label', 'tag', 'Label')
|
('label', 'tag', 'Label'),
|
||||||
|
('ratingCount', 'integer', 'Rating Count'),
|
||||||
])
|
])
|
||||||
elif self.type == 'collection':
|
elif self.type == 'collection':
|
||||||
additionalFields.extend([
|
additionalFields.extend([
|
||||||
|
|
|
@ -106,12 +106,16 @@ class MediaPart(PlexObject):
|
||||||
Attributes:
|
Attributes:
|
||||||
TAG (str): 'Part'
|
TAG (str): 'Part'
|
||||||
accessible (bool): True if the file is accessible.
|
accessible (bool): True if the file is accessible.
|
||||||
|
Requires reloading the media with ``checkFiles=True``.
|
||||||
|
Refer to :func:`~plexapi.base.PlexObject.reload`.
|
||||||
audioProfile (str): The audio profile of the file.
|
audioProfile (str): The audio profile of the file.
|
||||||
container (str): The container type of the file (ex: avi).
|
container (str): The container type of the file (ex: avi).
|
||||||
decision (str): Unknown.
|
decision (str): Unknown.
|
||||||
deepAnalysisVersion (int): The Plex deep analysis version for the file.
|
deepAnalysisVersion (int): The Plex deep analysis version for the file.
|
||||||
duration (int): The duration of the file in milliseconds.
|
duration (int): The duration of the file in milliseconds.
|
||||||
exists (bool): True if the file exists.
|
exists (bool): True if the file exists.
|
||||||
|
Requires reloading the media with ``checkFiles=True``.
|
||||||
|
Refer to :func:`~plexapi.base.PlexObject.reload`.
|
||||||
file (str): The path to this file on disk (ex: /media/Movies/Cars (2006)/Cars (2006).mkv)
|
file (str): The path to this file on disk (ex: /media/Movies/Cars (2006)/Cars (2006).mkv)
|
||||||
has64bitOffsets (bool): True if the file has 64 bit offsets.
|
has64bitOffsets (bool): True if the file has 64 bit offsets.
|
||||||
hasThumbnail (bool): True if the file (track) has an embedded thumbnail.
|
hasThumbnail (bool): True if the file (track) has an embedded thumbnail.
|
||||||
|
@ -999,6 +1003,28 @@ class Review(PlexObject):
|
||||||
self.text = data.attrib.get('text')
|
self.text = data.attrib.get('text')
|
||||||
|
|
||||||
|
|
||||||
|
@utils.registerPlexObject
|
||||||
|
class UltraBlurColors(PlexObject):
|
||||||
|
""" Represents a single UltraBlurColors media tag.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
TAG (str): 'UltraBlurColors'
|
||||||
|
bottomLeft (str): The bottom left hex color.
|
||||||
|
bottomRight (str): The bottom right hex color.
|
||||||
|
topLeft (str): The top left hex color.
|
||||||
|
topRight (str): The top right hex color.
|
||||||
|
"""
|
||||||
|
TAG = 'UltraBlurColors'
|
||||||
|
|
||||||
|
def _loadData(self, data):
|
||||||
|
""" Load attribute values from Plex XML response. """
|
||||||
|
self._data = data
|
||||||
|
self.bottomLeft = data.attrib.get('bottomLeft')
|
||||||
|
self.bottomRight = data.attrib.get('bottomRight')
|
||||||
|
self.topLeft = data.attrib.get('topLeft')
|
||||||
|
self.topRight = data.attrib.get('topRight')
|
||||||
|
|
||||||
|
|
||||||
class BaseResource(PlexObject):
|
class BaseResource(PlexObject):
|
||||||
""" Base class for all Art, Poster, and Theme objects.
|
""" Base class for all Art, Poster, and Theme objects.
|
||||||
|
|
||||||
|
|
|
@ -14,8 +14,8 @@ class AdvancedSettingsMixin:
|
||||||
|
|
||||||
def preferences(self):
|
def preferences(self):
|
||||||
""" Returns a list of :class:`~plexapi.settings.Preferences` objects. """
|
""" Returns a list of :class:`~plexapi.settings.Preferences` objects. """
|
||||||
data = self._server.query(self._details_key)
|
key = f'{self.key}?includePreferences=1'
|
||||||
return self.findItems(data, settings.Preferences, rtag='Preferences')
|
return self.fetchItems(key, cls=settings.Preferences, rtag='Preferences')
|
||||||
|
|
||||||
def preference(self, pref):
|
def preference(self, pref):
|
||||||
""" Returns a :class:`~plexapi.settings.Preferences` object for the specified pref.
|
""" Returns a :class:`~plexapi.settings.Preferences` object for the specified pref.
|
||||||
|
@ -240,8 +240,7 @@ class UnmatchMatchMixin:
|
||||||
params['agent'] = utils.getAgentIdentifier(self.section(), agent)
|
params['agent'] = utils.getAgentIdentifier(self.section(), agent)
|
||||||
|
|
||||||
key = key + '?' + urlencode(params)
|
key = key + '?' + urlencode(params)
|
||||||
data = self._server.query(key, method=self._server._session.get)
|
return self.fetchItems(key, cls=media.SearchResult)
|
||||||
return self.findItems(data, initpath=key)
|
|
||||||
|
|
||||||
def fixMatch(self, searchResult=None, auto=False, agent=None):
|
def fixMatch(self, searchResult=None, auto=False, agent=None):
|
||||||
""" Use match result to update show metadata.
|
""" Use match result to update show metadata.
|
||||||
|
@ -278,8 +277,8 @@ class ExtrasMixin:
|
||||||
def extras(self):
|
def extras(self):
|
||||||
""" Returns a list of :class:`~plexapi.video.Extra` objects. """
|
""" Returns a list of :class:`~plexapi.video.Extra` objects. """
|
||||||
from plexapi.video import Extra
|
from plexapi.video import Extra
|
||||||
data = self._server.query(self._details_key)
|
key = f'{self.key}/extras'
|
||||||
return self.findItems(data, Extra, rtag='Extras')
|
return self.fetchItems(key, cls=Extra)
|
||||||
|
|
||||||
|
|
||||||
class HubsMixin:
|
class HubsMixin:
|
||||||
|
@ -289,8 +288,7 @@ class HubsMixin:
|
||||||
""" Returns a list of :class:`~plexapi.library.Hub` objects. """
|
""" Returns a list of :class:`~plexapi.library.Hub` objects. """
|
||||||
from plexapi.library import Hub
|
from plexapi.library import Hub
|
||||||
key = f'{self.key}/related'
|
key = f'{self.key}/related'
|
||||||
data = self._server.query(key)
|
return self.fetchItems(key, cls=Hub)
|
||||||
return self.findItems(data, Hub)
|
|
||||||
|
|
||||||
|
|
||||||
class PlayedUnplayedMixin:
|
class PlayedUnplayedMixin:
|
||||||
|
|
|
@ -250,7 +250,7 @@ class MyPlexAccount(PlexObject):
|
||||||
return response.json()
|
return response.json()
|
||||||
elif 'text/plain' in response.headers.get('Content-Type', ''):
|
elif 'text/plain' in response.headers.get('Content-Type', ''):
|
||||||
return response.text.strip()
|
return response.text.strip()
|
||||||
data = response.text.encode('utf8')
|
data = utils.cleanXMLString(response.text).encode('utf8')
|
||||||
return ElementTree.fromstring(data) if data.strip() else None
|
return ElementTree.fromstring(data) if data.strip() else None
|
||||||
|
|
||||||
def ping(self):
|
def ping(self):
|
||||||
|
|
|
@ -768,7 +768,7 @@ class PlexServer(PlexObject):
|
||||||
raise NotFound(message)
|
raise NotFound(message)
|
||||||
else:
|
else:
|
||||||
raise BadRequest(message)
|
raise BadRequest(message)
|
||||||
data = response.text.encode('utf8')
|
data = utils.cleanXMLString(response.text).encode('utf8')
|
||||||
return ElementTree.fromstring(data) if data.strip() else None
|
return ElementTree.fromstring(data) if data.strip() else None
|
||||||
|
|
||||||
def search(self, query, mediatype=None, limit=None, sectionId=None):
|
def search(self, query, mediatype=None, limit=None, sectionId=None):
|
||||||
|
|
|
@ -6,6 +6,7 @@ import logging
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import string
|
import string
|
||||||
|
import sys
|
||||||
import time
|
import time
|
||||||
import unicodedata
|
import unicodedata
|
||||||
import warnings
|
import warnings
|
||||||
|
@ -673,3 +674,45 @@ def openOrRead(file):
|
||||||
def sha1hash(guid):
|
def sha1hash(guid):
|
||||||
""" Return the SHA1 hash of a guid. """
|
""" Return the SHA1 hash of a guid. """
|
||||||
return sha1(guid.encode('utf-8')).hexdigest()
|
return sha1(guid.encode('utf-8')).hexdigest()
|
||||||
|
|
||||||
|
|
||||||
|
# https://stackoverflow.com/a/64570125
|
||||||
|
_illegal_XML_characters = [
|
||||||
|
(0x00, 0x08),
|
||||||
|
(0x0B, 0x0C),
|
||||||
|
(0x0E, 0x1F),
|
||||||
|
(0x7F, 0x84),
|
||||||
|
(0x86, 0x9F),
|
||||||
|
(0xFDD0, 0xFDDF),
|
||||||
|
(0xFFFE, 0xFFFF),
|
||||||
|
]
|
||||||
|
if sys.maxunicode >= 0x10000: # not narrow build
|
||||||
|
_illegal_XML_characters.extend(
|
||||||
|
[
|
||||||
|
(0x1FFFE, 0x1FFFF),
|
||||||
|
(0x2FFFE, 0x2FFFF),
|
||||||
|
(0x3FFFE, 0x3FFFF),
|
||||||
|
(0x4FFFE, 0x4FFFF),
|
||||||
|
(0x5FFFE, 0x5FFFF),
|
||||||
|
(0x6FFFE, 0x6FFFF),
|
||||||
|
(0x7FFFE, 0x7FFFF),
|
||||||
|
(0x8FFFE, 0x8FFFF),
|
||||||
|
(0x9FFFE, 0x9FFFF),
|
||||||
|
(0xAFFFE, 0xAFFFF),
|
||||||
|
(0xBFFFE, 0xBFFFF),
|
||||||
|
(0xCFFFE, 0xCFFFF),
|
||||||
|
(0xDFFFE, 0xDFFFF),
|
||||||
|
(0xEFFFE, 0xEFFFF),
|
||||||
|
(0xFFFFE, 0xFFFFF),
|
||||||
|
(0x10FFFE, 0x10FFFF),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
_illegal_XML_ranges = [
|
||||||
|
fr'{chr(low)}-{chr(high)}'
|
||||||
|
for (low, high) in _illegal_XML_characters
|
||||||
|
]
|
||||||
|
_illegal_XML_re = re.compile(fr'[{"".join(_illegal_XML_ranges)}]')
|
||||||
|
|
||||||
|
|
||||||
|
def cleanXMLString(s):
|
||||||
|
return _illegal_XML_re.sub('', s)
|
||||||
|
|
|
@ -375,6 +375,7 @@ class Movie(
|
||||||
studio (str): Studio that created movie (Di Bonaventura Pictures; 21 Laps Entertainment).
|
studio (str): Studio that created movie (Di Bonaventura Pictures; 21 Laps Entertainment).
|
||||||
tagline (str): Movie tag line (Back 2 Work; Who says men can't change?).
|
tagline (str): Movie tag line (Back 2 Work; Who says men can't change?).
|
||||||
theme (str): URL to theme resource (/library/metadata/<ratingkey>/theme/<themeid>).
|
theme (str): URL to theme resource (/library/metadata/<ratingkey>/theme/<themeid>).
|
||||||
|
ultraBlurColors (:class:`~plexapi.media.UltraBlurColors`): Ultra blur color object.
|
||||||
useOriginalTitle (int): Setting that indicates if the original title is used for the movie
|
useOriginalTitle (int): Setting that indicates if the original title is used for the movie
|
||||||
(-1 = Library default, 0 = No, 1 = Yes).
|
(-1 = Library default, 0 = No, 1 = Yes).
|
||||||
viewOffset (int): View offset in milliseconds.
|
viewOffset (int): View offset in milliseconds.
|
||||||
|
@ -420,6 +421,7 @@ class Movie(
|
||||||
self.studio = data.attrib.get('studio')
|
self.studio = data.attrib.get('studio')
|
||||||
self.tagline = data.attrib.get('tagline')
|
self.tagline = data.attrib.get('tagline')
|
||||||
self.theme = data.attrib.get('theme')
|
self.theme = data.attrib.get('theme')
|
||||||
|
self.ultraBlurColors = self.findItem(data, media.UltraBlurColors)
|
||||||
self.useOriginalTitle = utils.cast(int, data.attrib.get('useOriginalTitle', '-1'))
|
self.useOriginalTitle = utils.cast(int, data.attrib.get('useOriginalTitle', '-1'))
|
||||||
self.viewOffset = utils.cast(int, data.attrib.get('viewOffset', 0))
|
self.viewOffset = utils.cast(int, data.attrib.get('viewOffset', 0))
|
||||||
self.writers = self.findItems(data, media.Writer)
|
self.writers = self.findItems(data, media.Writer)
|
||||||
|
@ -456,8 +458,8 @@ class Movie(
|
||||||
|
|
||||||
def reviews(self):
|
def reviews(self):
|
||||||
""" Returns a list of :class:`~plexapi.media.Review` objects. """
|
""" Returns a list of :class:`~plexapi.media.Review` objects. """
|
||||||
data = self._server.query(self._details_key)
|
key = f'{self.key}?includeReviews=1'
|
||||||
return self.findItems(data, media.Review, rtag='Video')
|
return self.fetchItems(key, cls=media.Review, rtag='Video')
|
||||||
|
|
||||||
def editions(self):
|
def editions(self):
|
||||||
""" Returns a list of :class:`~plexapi.video.Movie` objects
|
""" Returns a list of :class:`~plexapi.video.Movie` objects
|
||||||
|
@ -543,6 +545,7 @@ class Show(
|
||||||
(-1 = Account default, 0 = Manually selected, 1 = Shown with foreign audio, 2 = Always enabled).
|
(-1 = Account default, 0 = Manually selected, 1 = Shown with foreign audio, 2 = Always enabled).
|
||||||
tagline (str): Show tag line.
|
tagline (str): Show tag line.
|
||||||
theme (str): URL to theme resource (/library/metadata/<ratingkey>/theme/<themeid>).
|
theme (str): URL to theme resource (/library/metadata/<ratingkey>/theme/<themeid>).
|
||||||
|
ultraBlurColors (:class:`~plexapi.media.UltraBlurColors`): Ultra blur color object.
|
||||||
useOriginalTitle (int): Setting that indicates if the original title is used for the show
|
useOriginalTitle (int): Setting that indicates if the original title is used for the show
|
||||||
(-1 = Library default, 0 = No, 1 = Yes).
|
(-1 = Library default, 0 = No, 1 = Yes).
|
||||||
viewedLeafCount (int): Number of items marked as played in the show view.
|
viewedLeafCount (int): Number of items marked as played in the show view.
|
||||||
|
@ -592,6 +595,7 @@ class Show(
|
||||||
self.subtitleMode = utils.cast(int, data.attrib.get('subtitleMode', '-1'))
|
self.subtitleMode = utils.cast(int, data.attrib.get('subtitleMode', '-1'))
|
||||||
self.tagline = data.attrib.get('tagline')
|
self.tagline = data.attrib.get('tagline')
|
||||||
self.theme = data.attrib.get('theme')
|
self.theme = data.attrib.get('theme')
|
||||||
|
self.ultraBlurColors = self.findItem(data, media.UltraBlurColors)
|
||||||
self.useOriginalTitle = utils.cast(int, data.attrib.get('useOriginalTitle', '-1'))
|
self.useOriginalTitle = utils.cast(int, data.attrib.get('useOriginalTitle', '-1'))
|
||||||
self.viewedLeafCount = utils.cast(int, data.attrib.get('viewedLeafCount'))
|
self.viewedLeafCount = utils.cast(int, data.attrib.get('viewedLeafCount'))
|
||||||
self.year = utils.cast(int, data.attrib.get('year'))
|
self.year = utils.cast(int, data.attrib.get('year'))
|
||||||
|
@ -614,8 +618,8 @@ class Show(
|
||||||
""" Returns show's On Deck :class:`~plexapi.video.Video` object or `None`.
|
""" Returns show's On Deck :class:`~plexapi.video.Video` object or `None`.
|
||||||
If show is unwatched, return will likely be the first episode.
|
If show is unwatched, return will likely be the first episode.
|
||||||
"""
|
"""
|
||||||
data = self._server.query(self._details_key)
|
key = f'{self.key}?includeOnDeck=1'
|
||||||
return next(iter(self.findItems(data, rtag='OnDeck')), None)
|
return next(iter(self.fetchItems(key, cls=Episode, rtag='OnDeck')), None)
|
||||||
|
|
||||||
def season(self, title=None, season=None):
|
def season(self, title=None, season=None):
|
||||||
""" Returns the season with the specified title or number.
|
""" Returns the season with the specified title or number.
|
||||||
|
@ -735,6 +739,7 @@ class Season(
|
||||||
subtitleLanguage (str): Setting that indicates the preferred subtitle language.
|
subtitleLanguage (str): Setting that indicates the preferred subtitle language.
|
||||||
subtitleMode (int): Setting that indicates the auto-select subtitle mode.
|
subtitleMode (int): Setting that indicates the auto-select subtitle mode.
|
||||||
(-1 = Series default, 0 = Manually selected, 1 = Shown with foreign audio, 2 = Always enabled).
|
(-1 = Series default, 0 = Manually selected, 1 = Shown with foreign audio, 2 = Always enabled).
|
||||||
|
ultraBlurColors (:class:`~plexapi.media.UltraBlurColors`): Ultra blur color object.
|
||||||
viewedLeafCount (int): Number of items marked as played in the season view.
|
viewedLeafCount (int): Number of items marked as played in the season view.
|
||||||
year (int): Year the season was released.
|
year (int): Year the season was released.
|
||||||
"""
|
"""
|
||||||
|
@ -766,6 +771,7 @@ class Season(
|
||||||
self.ratings = self.findItems(data, media.Rating)
|
self.ratings = self.findItems(data, media.Rating)
|
||||||
self.subtitleLanguage = data.attrib.get('subtitleLanguage', '')
|
self.subtitleLanguage = data.attrib.get('subtitleLanguage', '')
|
||||||
self.subtitleMode = utils.cast(int, data.attrib.get('subtitleMode', '-1'))
|
self.subtitleMode = utils.cast(int, data.attrib.get('subtitleMode', '-1'))
|
||||||
|
self.ultraBlurColors = self.findItem(data, media.UltraBlurColors)
|
||||||
self.viewedLeafCount = utils.cast(int, data.attrib.get('viewedLeafCount'))
|
self.viewedLeafCount = utils.cast(int, data.attrib.get('viewedLeafCount'))
|
||||||
self.year = utils.cast(int, data.attrib.get('year'))
|
self.year = utils.cast(int, data.attrib.get('year'))
|
||||||
|
|
||||||
|
@ -796,8 +802,8 @@ class Season(
|
||||||
""" Returns season's On Deck :class:`~plexapi.video.Video` object or `None`.
|
""" Returns season's On Deck :class:`~plexapi.video.Video` object or `None`.
|
||||||
Will only return a match if the show's On Deck episode is in this season.
|
Will only return a match if the show's On Deck episode is in this season.
|
||||||
"""
|
"""
|
||||||
data = self._server.query(self._details_key)
|
key = f'{self.key}?includeOnDeck=1'
|
||||||
return next(iter(self.findItems(data, rtag='OnDeck')), None)
|
return next(iter(self.fetchItems(key, cls=Episode, rtag='OnDeck')), None)
|
||||||
|
|
||||||
def episode(self, title=None, episode=None):
|
def episode(self, title=None, episode=None):
|
||||||
""" Returns the episode with the given title or number.
|
""" Returns the episode with the given title or number.
|
||||||
|
@ -914,6 +920,7 @@ class Episode(
|
||||||
skipParent (bool): True if the show's seasons are set to hidden.
|
skipParent (bool): True if the show's seasons are set to hidden.
|
||||||
sourceURI (str): Remote server URI (server://<machineIdentifier>/com.plexapp.plugins.library)
|
sourceURI (str): Remote server URI (server://<machineIdentifier>/com.plexapp.plugins.library)
|
||||||
(remote playlist item only).
|
(remote playlist item only).
|
||||||
|
ultraBlurColors (:class:`~plexapi.media.UltraBlurColors`): Ultra blur color object.
|
||||||
viewOffset (int): View offset in milliseconds.
|
viewOffset (int): View offset in milliseconds.
|
||||||
writers (List<:class:`~plexapi.media.Writer`>): List of writers objects.
|
writers (List<:class:`~plexapi.media.Writer`>): List of writers objects.
|
||||||
year (int): Year the episode was released.
|
year (int): Year the episode was released.
|
||||||
|
@ -958,6 +965,7 @@ class Episode(
|
||||||
self.roles = self.findItems(data, media.Role)
|
self.roles = self.findItems(data, media.Role)
|
||||||
self.skipParent = utils.cast(bool, data.attrib.get('skipParent', '0'))
|
self.skipParent = utils.cast(bool, data.attrib.get('skipParent', '0'))
|
||||||
self.sourceURI = data.attrib.get('source') # remote playlist item
|
self.sourceURI = data.attrib.get('source') # remote playlist item
|
||||||
|
self.ultraBlurColors = self.findItem(data, media.UltraBlurColors)
|
||||||
self.viewOffset = utils.cast(int, data.attrib.get('viewOffset', 0))
|
self.viewOffset = utils.cast(int, data.attrib.get('viewOffset', 0))
|
||||||
self.writers = self.findItems(data, media.Writer)
|
self.writers = self.findItems(data, media.Writer)
|
||||||
self.year = utils.cast(int, data.attrib.get('year'))
|
self.year = utils.cast(int, data.attrib.get('year'))
|
||||||
|
|
|
@ -26,7 +26,7 @@ musicbrainzngs==0.7.1
|
||||||
packaging==24.1
|
packaging==24.1
|
||||||
paho-mqtt==2.1.0
|
paho-mqtt==2.1.0
|
||||||
platformdirs==4.2.2
|
platformdirs==4.2.2
|
||||||
plexapi==4.15.15
|
plexapi==4.15.16
|
||||||
portend==3.2.0
|
portend==3.2.0
|
||||||
profilehooks==1.12.0
|
profilehooks==1.12.0
|
||||||
PyJWT==2.9.0
|
PyJWT==2.9.0
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue