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:
dependabot[bot] 2024-09-20 20:24:00 -07:00 committed by GitHub
parent de697cb2ca
commit cc1a325eac
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 153 additions and 45 deletions

View file

@ -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'))

View file

@ -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. """

View file

@ -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):

View file

@ -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

View file

@ -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}"

View file

@ -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([

View file

@ -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.

View file

@ -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:

View file

@ -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):

View file

@ -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):

View file

@ -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)

View file

@ -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'))

View file

@ -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