# -*- coding: utf-8 -*-
import xml
from pathlib import Path
from urllib.parse import quote_plus
from plexapi import log, settings, utils
from plexapi.base import PlexObject
from plexapi.exceptions import BadRequest
from plexapi.utils import deprecated
[docs]
@utils.registerPlexObject
class VideoStream(MediaPartStream):
""" Represents a video stream within a :class:`~plexapi.media.MediaPart`.
Attributes:
TAG (str): 'Stream'
STREAMTYPE (int): 1
anamorphic (str): If the video is anamorphic.
bitDepth (int): The bit depth of the video stream (ex: 8).
cabac (int): The context-adaptive binary arithmetic coding.
chromaLocation (str): The chroma location of the video stream.
chromaSubsampling (str): The chroma subsampling of the video stream (ex: 4:2:0).
codecID (str): The codec ID (ex: XVID).
codedHeight (int): The coded height of the video stream in pixels.
codedWidth (int): The coded width of the video stream in pixels.
colorPrimaries (str): The color primaries of the video stream.
colorRange (str): The color range of the video stream.
colorSpace (str): The color space of the video stream (ex: bt2020).
colorTrc (str): The color trc of the video stream.
DOVIBLCompatID (int): Dolby Vision base layer compatibility ID.
DOVIBLPresent (bool): True if Dolby Vision base layer is present.
DOVIELPresent (bool): True if Dolby Vision enhancement layer is present.
DOVILevel (int): Dolby Vision level.
DOVIPresent (bool): True if Dolby Vision is present.
DOVIProfile (int): Dolby Vision profile.
DOVIRPUPresent (bool): True if Dolby Vision reference processing unit is present.
DOVIVersion (float): The Dolby Vision version.
duration (int): The duration of video stream in milliseconds.
frameRate (float): The frame rate of the video stream (ex: 23.976).
frameRateMode (str): The frame rate mode of the video stream.
hasScalingMatrix (bool): True if video stream has a scaling matrix.
height (int): The height of the video stream in pixels (ex: 1080).
level (int): The codec encoding level of the video stream (ex: 41).
profile (str): The profile of the video stream (ex: asp).
pixelAspectRatio (str): The pixel aspect ratio of the video stream.
pixelFormat (str): The pixel format of the video stream.
refFrames (int): The number of reference frames of the video stream.
scanType (str): The scan type of the video stream (ex: progressive).
streamIdentifier(int): The stream identifier of the video stream.
width (int): The width of the video stream in pixels (ex: 1920).
"""
TAG = 'Stream'
STREAMTYPE = 1
def _loadData(self, data):
""" Load attribute values from Plex XML response. """
super(VideoStream, self)._loadData(data)
self.anamorphic = data.attrib.get('anamorphic')
self.bitDepth = utils.cast(int, data.attrib.get('bitDepth'))
self.cabac = utils.cast(int, data.attrib.get('cabac'))
self.chromaLocation = data.attrib.get('chromaLocation')
self.chromaSubsampling = data.attrib.get('chromaSubsampling')
self.codecID = data.attrib.get('codecID')
self.codedHeight = utils.cast(int, data.attrib.get('codedHeight'))
self.codedWidth = utils.cast(int, data.attrib.get('codedWidth'))
self.colorPrimaries = data.attrib.get('colorPrimaries')
self.colorRange = data.attrib.get('colorRange')
self.colorSpace = data.attrib.get('colorSpace')
self.colorTrc = data.attrib.get('colorTrc')
self.DOVIBLCompatID = utils.cast(int, data.attrib.get('DOVIBLCompatID'))
self.DOVIBLPresent = utils.cast(bool, data.attrib.get('DOVIBLPresent'))
self.DOVIELPresent = utils.cast(bool, data.attrib.get('DOVIELPresent'))
self.DOVILevel = utils.cast(int, data.attrib.get('DOVILevel'))
self.DOVIPresent = utils.cast(bool, data.attrib.get('DOVIPresent'))
self.DOVIProfile = utils.cast(int, data.attrib.get('DOVIProfile'))
self.DOVIRPUPresent = utils.cast(bool, data.attrib.get('DOVIRPUPresent'))
self.DOVIVersion = utils.cast(float, data.attrib.get('DOVIVersion'))
self.duration = utils.cast(int, data.attrib.get('duration'))
self.frameRate = utils.cast(float, data.attrib.get('frameRate'))
self.frameRateMode = data.attrib.get('frameRateMode')
self.hasScalingMatrix = utils.cast(bool, data.attrib.get('hasScalingMatrix'))
self.height = utils.cast(int, data.attrib.get('height'))
self.level = utils.cast(int, data.attrib.get('level'))
self.profile = data.attrib.get('profile')
self.pixelAspectRatio = data.attrib.get('pixelAspectRatio')
self.pixelFormat = data.attrib.get('pixelFormat')
self.refFrames = utils.cast(int, data.attrib.get('refFrames'))
self.scanType = data.attrib.get('scanType')
self.streamIdentifier = utils.cast(int, data.attrib.get('streamIdentifier'))
self.width = utils.cast(int, data.attrib.get('width'))
[docs]
@utils.registerPlexObject
class AudioStream(MediaPartStream):
""" Represents a audio stream within a :class:`~plexapi.media.MediaPart`.
Attributes:
TAG (str): 'Stream'
STREAMTYPE (int): 2
audioChannelLayout (str): The audio channel layout of the audio stream (ex: 5.1(side)).
bitDepth (int): The bit depth of the audio stream (ex: 16).
bitrateMode (str): The bitrate mode of the audio stream (ex: cbr).
channels (int): The number of audio channels of the audio stream (ex: 6).
duration (int): The duration of audio stream in milliseconds.
profile (str): The profile of the audio stream.
samplingRate (int): The sampling rate of the audio stream (ex: xxx)
streamIdentifier (int): The stream identifier of the audio stream.
Track_only_attributes: The following attributes are only available for tracks.
* albumGain (float): The gain for the album.
* albumPeak (float): The peak for the album.
* albumRange (float): The range for the album.
* endRamp (str): The end ramp for the track.
* gain (float): The gain for the track.
* loudness (float): The loudness for the track.
* lra (float): The lra for the track.
* peak (float): The peak for the track.
* startRamp (str): The start ramp for the track.
"""
TAG = 'Stream'
STREAMTYPE = 2
def _loadData(self, data):
""" Load attribute values from Plex XML response. """
super(AudioStream, self)._loadData(data)
self.audioChannelLayout = data.attrib.get('audioChannelLayout')
self.bitDepth = utils.cast(int, data.attrib.get('bitDepth'))
self.bitrateMode = data.attrib.get('bitrateMode')
self.channels = utils.cast(int, data.attrib.get('channels'))
self.duration = utils.cast(int, data.attrib.get('duration'))
self.profile = data.attrib.get('profile')
self.samplingRate = utils.cast(int, data.attrib.get('samplingRate'))
self.streamIdentifier = utils.cast(int, data.attrib.get('streamIdentifier'))
# Track only attributes
self.albumGain = utils.cast(float, data.attrib.get('albumGain'))
self.albumPeak = utils.cast(float, data.attrib.get('albumPeak'))
self.albumRange = utils.cast(float, data.attrib.get('albumRange'))
self.endRamp = data.attrib.get('endRamp')
self.gain = utils.cast(float, data.attrib.get('gain'))
self.loudness = utils.cast(float, data.attrib.get('loudness'))
self.lra = utils.cast(float, data.attrib.get('lra'))
self.peak = utils.cast(float, data.attrib.get('peak'))
self.startRamp = data.attrib.get('startRamp')
[docs]
def setSelected(self):
""" Sets this audio stream as the selected audio stream.
Alias for :func:`~plexapi.media.MediaPart.setSelectedAudioStream`.
"""
return self._parent().setSelectedAudioStream(self)
@deprecated('Use "setSelected" instead.')
def setDefault(self):
return self.setSelected()
[docs]
@utils.registerPlexObject
class SubtitleStream(MediaPartStream):
""" Represents a audio stream within a :class:`~plexapi.media.MediaPart`.
Attributes:
TAG (str): 'Stream'
STREAMTYPE (int): 3
canAutoSync (bool): True if the subtitle stream can be auto synced.
container (str): The container of the subtitle stream.
forced (bool): True if this is a forced subtitle.
format (str): The format of the subtitle stream (ex: srt).
headerCompression (str): The header compression of the subtitle stream.
hearingImpaired (bool): True if this is a hearing impaired (SDH) subtitle.
perfectMatch (bool): True if the on-demand subtitle is a perfect match.
providerTitle (str): The provider title where the on-demand subtitle is downloaded from.
score (int): The match score (download count) of the on-demand subtitle.
sourceKey (str): The source key of the on-demand subtitle.
transient (str): Unknown.
userID (int): The user id of the user that downloaded the on-demand subtitle.
"""
TAG = 'Stream'
STREAMTYPE = 3
def _loadData(self, data):
""" Load attribute values from Plex XML response. """
super(SubtitleStream, self)._loadData(data)
self.canAutoSync = utils.cast(bool, data.attrib.get('canAutoSync'))
self.container = data.attrib.get('container')
self.forced = utils.cast(bool, data.attrib.get('forced', '0'))
self.format = data.attrib.get('format')
self.headerCompression = data.attrib.get('headerCompression')
self.hearingImpaired = utils.cast(bool, data.attrib.get('hearingImpaired', '0'))
self.perfectMatch = utils.cast(bool, data.attrib.get('perfectMatch'))
self.providerTitle = data.attrib.get('providerTitle')
self.score = utils.cast(int, data.attrib.get('score'))
self.sourceKey = data.attrib.get('sourceKey')
self.transient = data.attrib.get('transient')
self.userID = utils.cast(int, data.attrib.get('userID'))
[docs]
def setSelected(self):
""" Sets this subtitle stream as the selected subtitle stream.
Alias for :func:`~plexapi.media.MediaPart.setSelectedSubtitleStream`.
"""
return self._parent().setSelectedSubtitleStream(self)
@deprecated('Use "setSelected" instead.')
def setDefault(self):
return self.setSelected()
[docs]
@utils.registerPlexObject
class LyricStream(MediaPartStream):
""" Represents a lyric stream within a :class:`~plexapi.media.MediaPart`.
Attributes:
TAG (str): 'Stream'
STREAMTYPE (int): 4
format (str): The format of the lyric stream (ex: lrc).
minLines (int): The minimum number of lines in the (timed) lyric stream.
provider (str): The provider of the lyric stream (ex: com.plexapp.agents.lyricfind).
timed (bool): True if the lyrics are timed to the track.
"""
TAG = 'Stream'
STREAMTYPE = 4
def _loadData(self, data):
""" Load attribute values from Plex XML response. """
super(LyricStream, self)._loadData(data)
self.format = data.attrib.get('format')
self.minLines = utils.cast(int, data.attrib.get('minLines'))
self.provider = data.attrib.get('provider')
self.timed = utils.cast(bool, data.attrib.get('timed', '0'))
[docs]
@utils.registerPlexObject
class Session(PlexObject):
""" Represents a current session.
Attributes:
TAG (str): 'Session'
id (str): The unique identifier for the session.
bandwidth (int): The Plex streaming brain reserved bandwidth for the session.
location (str): The location of the session (lan, wan, or cellular)
"""
TAG = 'Session'
def _loadData(self, data):
self.id = data.attrib.get('id')
self.bandwidth = utils.cast(int, data.attrib.get('bandwidth'))
self.location = data.attrib.get('location')
[docs]
@utils.registerPlexObject
class TranscodeSession(PlexObject):
""" Represents a current transcode session.
Attributes:
TAG (str): 'TranscodeSession'
audioChannels (int): The number of audio channels of the transcoded media.
audioCodec (str): The audio codec of the transcoded media.
audioDecision (str): The transcode decision for the audio stream.
complete (bool): True if the transcode is complete.
container (str): The container of the transcoded media.
context (str): The context for the transcode session.
duration (int): The duration of the transcoded media in milliseconds.
height (int): The height of the transcoded media in pixels.
key (str): API URL (ex: /transcode/sessions/<id>).
maxOffsetAvailable (float): Unknown.
minOffsetAvailable (float): Unknown.
progress (float): The progress percentage of the transcode.
protocol (str): The protocol of the transcode.
remaining (int): Unknown.
size (int): The size of the transcoded media in bytes.
sourceAudioCodec (str): The audio codec of the source media.
sourceVideoCodec (str): The video codec of the source media.
speed (float): The speed of the transcode.
subtitleDecision (str): The transcode decision for the subtitle stream
throttled (bool): True if the transcode is throttled.
timestamp (int): The epoch timestamp when the transcode started.
transcodeHwDecoding (str): The hardware transcoding decoder engine.
transcodeHwDecodingTitle (str): The title of the hardware transcoding decoder engine.
transcodeHwEncoding (str): The hardware transcoding encoder engine.
transcodeHwEncodingTitle (str): The title of the hardware transcoding encoder engine.
transcodeHwFullPipeline (str): True if hardware decoding and encoding is being used for the transcode.
transcodeHwRequested (str): True if hardware transcoding was requested for the transcode.
videoCodec (str): The video codec of the transcoded media.
videoDecision (str): The transcode decision for the video stream.
width (str): The width of the transcoded media in pixels.
"""
TAG = 'TranscodeSession'
def _loadData(self, data):
""" Load attribute values from Plex XML response. """
self._data = data
self.audioChannels = utils.cast(int, data.attrib.get('audioChannels'))
self.audioCodec = data.attrib.get('audioCodec')
self.audioDecision = data.attrib.get('audioDecision')
self.complete = utils.cast(bool, data.attrib.get('complete', '0'))
self.container = data.attrib.get('container')
self.context = data.attrib.get('context')
self.duration = utils.cast(int, data.attrib.get('duration'))
self.height = utils.cast(int, data.attrib.get('height'))
self.key = data.attrib.get('key')
self.maxOffsetAvailable = utils.cast(float, data.attrib.get('maxOffsetAvailable'))
self.minOffsetAvailable = utils.cast(float, data.attrib.get('minOffsetAvailable'))
self.progress = utils.cast(float, data.attrib.get('progress'))
self.protocol = data.attrib.get('protocol')
self.remaining = utils.cast(int, data.attrib.get('remaining'))
self.size = utils.cast(int, data.attrib.get('size'))
self.sourceAudioCodec = data.attrib.get('sourceAudioCodec')
self.sourceVideoCodec = data.attrib.get('sourceVideoCodec')
self.speed = utils.cast(float, data.attrib.get('speed'))
self.subtitleDecision = data.attrib.get('subtitleDecision')
self.throttled = utils.cast(bool, data.attrib.get('throttled', '0'))
self.timestamp = utils.cast(float, data.attrib.get('timeStamp'))
self.transcodeHwDecoding = data.attrib.get('transcodeHwDecoding')
self.transcodeHwDecodingTitle = data.attrib.get('transcodeHwDecodingTitle')
self.transcodeHwEncoding = data.attrib.get('transcodeHwEncoding')
self.transcodeHwEncodingTitle = data.attrib.get('transcodeHwEncodingTitle')
self.transcodeHwFullPipeline = utils.cast(bool, data.attrib.get('transcodeHwFullPipeline', '0'))
self.transcodeHwRequested = utils.cast(bool, data.attrib.get('transcodeHwRequested', '0'))
self.videoCodec = data.attrib.get('videoCodec')
self.videoDecision = data.attrib.get('videoDecision')
self.width = utils.cast(int, data.attrib.get('width'))
[docs]
@utils.registerPlexObject
class TranscodeJob(PlexObject):
""" Represents an Optimizing job.
TrancodeJobs are the process for optimizing conversions.
Active or paused optimization items. Usually one item as a time."""
TAG = 'TranscodeJob'
def _loadData(self, data):
self._data = data
self.generatorID = data.attrib.get('generatorID')
self.key = data.attrib.get('key')
self.progress = data.attrib.get('progress')
self.ratingKey = data.attrib.get('ratingKey')
self.size = data.attrib.get('size')
self.targetTagID = data.attrib.get('targetTagID')
self.thumb = data.attrib.get('thumb')
self.title = data.attrib.get('title')
self.type = data.attrib.get('type')
[docs]
@utils.registerPlexObject
class Optimized(PlexObject):
""" Represents a Optimized item.
Optimized items are optimized and queued conversions items."""
TAG = 'Item'
def _loadData(self, data):
self._data = data
self.id = data.attrib.get('id')
self.composite = data.attrib.get('composite')
self.title = data.attrib.get('title')
self.type = data.attrib.get('type')
self.target = data.attrib.get('target')
self.targetTagID = data.attrib.get('targetTagID')
[docs]
def items(self):
""" Returns a list of all :class:`~plexapi.media.Video` objects
in this optimized item.
"""
key = f'{self._initpath}/{self.id}/items'
return self.fetchItems(key)
[docs]
def remove(self):
""" Remove an Optimized item"""
key = f'{self._initpath}/{self.id}'
self._server.query(key, method=self._server._session.delete)
[docs]
def rename(self, title):
""" Rename an Optimized item"""
key = f'{self._initpath}/{self.id}?Item[title]={title}'
self._server.query(key, method=self._server._session.put)
[docs]
def reprocess(self, ratingKey):
""" Reprocess a removed Conversion item that is still a listed Optimize item"""
key = f'{self._initpath}/{self.id}/{ratingKey}/enable'
self._server.query(key, method=self._server._session.put)
[docs]
@utils.registerPlexObject
class Conversion(PlexObject):
""" Represents a Conversion item.
Conversions are items queued for optimization or being actively optimized."""
TAG = 'Video'
def _loadData(self, data):
self._data = data
self.addedAt = data.attrib.get('addedAt')
self.art = data.attrib.get('art')
self.chapterSource = data.attrib.get('chapterSource')
self.contentRating = data.attrib.get('contentRating')
self.duration = data.attrib.get('duration')
self.generatorID = data.attrib.get('generatorID')
self.generatorType = data.attrib.get('generatorType')
self.guid = data.attrib.get('guid')
self.key = data.attrib.get('key')
self.lastViewedAt = data.attrib.get('lastViewedAt')
self.librarySectionID = data.attrib.get('librarySectionID')
self.librarySectionKey = data.attrib.get('librarySectionKey')
self.librarySectionTitle = data.attrib.get('librarySectionTitle')
self.originallyAvailableAt = data.attrib.get('originallyAvailableAt')
self.playQueueItemID = data.attrib.get('playQueueItemID')
self.playlistID = data.attrib.get('playlistID')
self.primaryExtraKey = data.attrib.get('primaryExtraKey')
self.rating = data.attrib.get('rating')
self.ratingKey = data.attrib.get('ratingKey')
self.studio = data.attrib.get('studio')
self.summary = data.attrib.get('summary')
self.tagline = data.attrib.get('tagline')
self.target = data.attrib.get('target')
self.thumb = data.attrib.get('thumb')
self.title = data.attrib.get('title')
self.type = data.attrib.get('type')
self.updatedAt = data.attrib.get('updatedAt')
self.userID = data.attrib.get('userID')
self.username = data.attrib.get('username')
self.viewOffset = data.attrib.get('viewOffset')
self.year = data.attrib.get('year')
[docs]
def remove(self):
""" Remove Conversion from queue """
key = f'/playlists/{self.playlistID}/items/{self.generatorID}/{self.ratingKey}/disable'
self._server.query(key, method=self._server._session.put)
[docs]
def move(self, after):
""" Move Conversion items position in queue
after (int): Place item after specified playQueueItemID. '-1' is the active conversion.
Example:
Move 5th conversion Item to active conversion
conversions[4].move('-1')
Move 4th conversion Item to 3rd in conversion queue
conversions[3].move(conversions[1].playQueueItemID)
"""
key = f'{self._initpath}/items/{self.playQueueItemID}/move?after={after}'
self._server.query(key, method=self._server._session.put)
[docs]
@utils.registerPlexObject
class Collection(MediaTag):
""" Represents a single Collection media tag.
Attributes:
TAG (str): 'Collection'
FILTER (str): 'collection'
"""
TAG = 'Collection'
FILTER = 'collection'
[docs]
def collection(self):
""" Return the :class:`~plexapi.collection.Collection` object for this collection tag.
"""
key = f'{self._librarySectionKey}/collections'
return self.fetchItem(key, etag='Directory', index=self.id)
[docs]
@utils.registerPlexObject
class Country(MediaTag):
""" Represents a single Country media tag.
Attributes:
TAG (str): 'Country'
FILTER (str): 'country'
"""
TAG = 'Country'
FILTER = 'country'
[docs]
@utils.registerPlexObject
class Director(MediaTag):
""" Represents a single Director media tag.
Attributes:
TAG (str): 'Director'
FILTER (str): 'director'
"""
TAG = 'Director'
FILTER = 'director'
[docs]
@utils.registerPlexObject
class Genre(MediaTag):
""" Represents a single Genre media tag.
Attributes:
TAG (str): 'Genre'
FILTER (str): 'genre'
"""
TAG = 'Genre'
FILTER = 'genre'
[docs]
@utils.registerPlexObject
class Label(MediaTag):
""" Represents a single Label media tag.
Attributes:
TAG (str): 'Label'
FILTER (str): 'label'
"""
TAG = 'Label'
FILTER = 'label'
[docs]
@utils.registerPlexObject
class Mood(MediaTag):
""" Represents a single Mood media tag.
Attributes:
TAG (str): 'Mood'
FILTER (str): 'mood'
"""
TAG = 'Mood'
FILTER = 'mood'
[docs]
@utils.registerPlexObject
class Producer(MediaTag):
""" Represents a single Producer media tag.
Attributes:
TAG (str): 'Producer'
FILTER (str): 'producer'
"""
TAG = 'Producer'
FILTER = 'producer'
[docs]
@utils.registerPlexObject
class Role(MediaTag):
""" Represents a single Role (actor/actress) media tag.
Attributes:
TAG (str): 'Role'
FILTER (str): 'role'
"""
TAG = 'Role'
FILTER = 'role'
[docs]
@utils.registerPlexObject
class Similar(MediaTag):
""" Represents a single Similar media tag.
Attributes:
TAG (str): 'Similar'
FILTER (str): 'similar'
"""
TAG = 'Similar'
FILTER = 'similar'
[docs]
@utils.registerPlexObject
class Style(MediaTag):
""" Represents a single Style media tag.
Attributes:
TAG (str): 'Style'
FILTER (str): 'style'
"""
TAG = 'Style'
FILTER = 'style'
[docs]
@utils.registerPlexObject
class Tag(MediaTag):
""" Represents a single Tag media tag.
Attributes:
TAG (str): 'Tag'
FILTER (str): 'tag'
"""
TAG = 'Tag'
FILTER = 'tag'
[docs]
@utils.registerPlexObject
class Writer(MediaTag):
""" Represents a single Writer media tag.
Attributes:
TAG (str): 'Writer'
FILTER (str): 'writer'
"""
TAG = 'Writer'
FILTER = 'writer'
[docs]
@utils.registerPlexObject
class Guid(PlexObject):
""" Represents a single Guid media tag.
Attributes:
TAG (str): 'Guid'
id (id): The guid for external metadata sources (e.g. IMDB, TMDB, TVDB, MBID).
"""
TAG = 'Guid'
def _loadData(self, data):
""" Load attribute values from Plex XML response. """
self._data = data
self.id = data.attrib.get('id')
[docs]
@utils.registerPlexObject
class Image(PlexObject):
""" Represents a single Image media tag.
Attributes:
TAG (str): 'Image'
alt (str): The alt text for the image.
type (str): The type of image (e.g. coverPoster, background, snapshot).
url (str): The API URL (/library/metadata/<ratingKey>/thumb/<thumbid>).
"""
TAG = 'Image'
def _loadData(self, data):
""" Load attribute values from Plex XML response. """
self._data = data
self.alt = data.attrib.get('alt')
self.type = data.attrib.get('type')
self.url = data.attrib.get('url')
[docs]
@utils.registerPlexObject
class Rating(PlexObject):
""" Represents a single Rating media tag.
Attributes:
TAG (str): 'Rating'
image (str): The uri for the rating image
(e.g. ``imdb://image.rating``, ``rottentomatoes://image.rating.ripe``,
``rottentomatoes://image.rating.upright``, ``themoviedb://image.rating``).
type (str): The type of rating (e.g. audience or critic).
value (float): The rating value.
"""
TAG = 'Rating'
def _loadData(self, data):
""" Load attribute values from Plex XML response. """
self._data = data
self.image = data.attrib.get('image')
self.type = data.attrib.get('type')
self.value = utils.cast(float, data.attrib.get('value'))
[docs]
@utils.registerPlexObject
class Review(PlexObject):
""" Represents a single Review for a Movie.
Attributes:
TAG (str): 'Review'
filter (str): The library filter for the review.
id (int): The ID of the review.
image (str): The image uri for the review.
link (str): The url to the online review.
source (str): The source of the review.
tag (str): The name of the reviewer.
text (str): The text of the review.
"""
TAG = 'Review'
def _loadData(self, data):
self._data = data
self.filter = data.attrib.get('filter')
self.id = utils.cast(int, data.attrib.get('id', 0))
self.image = data.attrib.get('image')
self.link = data.attrib.get('link')
self.source = data.attrib.get('source')
self.tag = data.attrib.get('tag')
self.text = data.attrib.get('text')
[docs]
@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')
[docs]
class BaseResource(PlexObject):
""" Base class for all Art, Poster, and Theme objects.
Attributes:
TAG (str): 'Photo' or 'Track'
key (str): API URL (/library/metadata/<ratingkey>).
provider (str): The source of the resource. 'local' for local files (e.g. theme.mp3),
None if uploaded or agent-/plugin-supplied.
ratingKey (str): Unique key identifying the resource.
selected (bool): True if the resource is currently selected.
thumb (str): The URL to retrieve the resource thumbnail.
"""
def _loadData(self, data):
self._data = data
self.key = data.attrib.get('key')
self.provider = data.attrib.get('provider')
self.ratingKey = data.attrib.get('ratingKey')
self.selected = utils.cast(bool, data.attrib.get('selected'))
self.thumb = data.attrib.get('thumb')
def select(self):
key = self._initpath[:-1]
data = f'{key}?url={quote_plus(self.ratingKey)}'
try:
self._server.query(data, method=self._server._session.put)
except xml.etree.ElementTree.ParseError:
pass
@property
def resourceFilepath(self):
""" Returns the file path to the resource in the Plex Media Server data directory.
Note: Returns the URL if the resource is not stored locally.
"""
if self.ratingKey.startswith('media://'):
return str(Path('Media') / 'localhost' / self.ratingKey.split('://')[-1])
elif self.ratingKey.startswith('metadata://'):
return str(Path(self._parent().metadataDirectory) / 'Contents' / '_combined' / self.ratingKey.split('://')[-1])
elif self.ratingKey.startswith('upload://'):
return str(Path(self._parent().metadataDirectory) / 'Uploads' / self.ratingKey.split('://')[-1])
else:
return self.ratingKey
[docs]
class Art(BaseResource):
""" Represents a single Art object. """
TAG = 'Photo'
[docs]
class Logo(BaseResource):
""" Represents a single Logo object. """
TAG = 'Photo'
[docs]
class Poster(BaseResource):
""" Represents a single Poster object. """
TAG = 'Photo'
[docs]
class Theme(BaseResource):
""" Represents a single Theme object. """
TAG = 'Track'
[docs]
@utils.registerPlexObject
class Chapter(PlexObject):
""" Represents a single Chapter media tag.
Attributes:
TAG (str): 'Chapter'
end (int): The end time of the chapter in milliseconds.
filter (str): The library filter for the chapter.
id (int): The ID of the chapter.
index (int): The index of the chapter.
tag (str): The name of the chapter.
title (str): The title of the chapter.
thumb (str): The URL to retrieve the chapter thumbnail.
start (int): The start time of the chapter in milliseconds.
"""
TAG = 'Chapter'
def __repr__(self):
name = self._clean(self.firstAttr('tag'))
start = utils.millisecondToHumanstr(self._clean(self.firstAttr('start')))
end = utils.millisecondToHumanstr(self._clean(self.firstAttr('end')))
offsets = f'{start}-{end}'
return f"<{':'.join([self.__class__.__name__, name, offsets])}>"
def _loadData(self, data):
self._data = data
self.end = utils.cast(int, data.attrib.get('endTimeOffset'))
self.filter = data.attrib.get('filter')
self.id = utils.cast(int, data.attrib.get('id', 0))
self.index = utils.cast(int, data.attrib.get('index'))
self.tag = data.attrib.get('tag')
self.title = self.tag
self.thumb = data.attrib.get('thumb')
self.start = utils.cast(int, data.attrib.get('startTimeOffset'))
[docs]
@utils.registerPlexObject
class Marker(PlexObject):
""" Represents a single Marker media tag.
Attributes:
TAG (str): 'Marker'
end (int): The end time of the marker in milliseconds.
final (bool): True if the marker is the final credits marker.
id (int): The ID of the marker.
type (str): The type of marker.
start (int): The start time of the marker in milliseconds.
version (int): The Plex marker version.
"""
TAG = 'Marker'
def __repr__(self):
name = self._clean(self.firstAttr('type'))
start = utils.millisecondToHumanstr(self._clean(self.firstAttr('start')))
end = utils.millisecondToHumanstr(self._clean(self.firstAttr('end')))
offsets = f'{start}-{end}'
return f"<{':'.join([self.__class__.__name__, name, offsets])}>"
def _loadData(self, data):
self._data = data
self.end = utils.cast(int, data.attrib.get('endTimeOffset'))
self.final = utils.cast(bool, data.attrib.get('final'))
self.id = utils.cast(int, data.attrib.get('id'))
self.type = data.attrib.get('type')
self.start = utils.cast(int, data.attrib.get('startTimeOffset'))
attributes = data.find('Attributes')
self.version = attributes.attrib.get('version')
@property
def first(self):
""" Returns True if the marker in the first credits marker. """
if self.type != 'credits':
return None
first = min(
(marker for marker in self._parent().markers if marker.type == 'credits'),
key=lambda m: m.start
)
return first == self
[docs]
@utils.registerPlexObject
class Field(PlexObject):
""" Represents a single Field.
Attributes:
TAG (str): 'Field'
locked (bool): True if the field is locked.
name (str): The name of the field.
"""
TAG = 'Field'
def _loadData(self, data):
self._data = data
self.locked = utils.cast(bool, data.attrib.get('locked'))
self.name = data.attrib.get('name')
[docs]
@utils.registerPlexObject
class SearchResult(PlexObject):
""" Represents a single SearchResult.
Attributes:
TAG (str): 'SearchResult'
"""
TAG = 'SearchResult'
def __repr__(self):
name = self._clean(self.firstAttr('name'))
score = self._clean(self.firstAttr('score'))
return f"<{':'.join([p for p in [self.__class__.__name__, name, score] if p])}>"
def _loadData(self, data):
self._data = data
self.guid = data.attrib.get('guid')
self.lifespanEnded = data.attrib.get('lifespanEnded')
self.name = data.attrib.get('name')
self.score = utils.cast(int, data.attrib.get('score'))
self.year = data.attrib.get('year')
[docs]
@utils.registerPlexObject
class Agent(PlexObject):
""" Represents a single Agent.
Attributes:
TAG (str): 'Agent'
"""
TAG = 'Agent'
def __repr__(self):
uid = self._clean(self.firstAttr('shortIdentifier'))
return f"<{':'.join([p for p in [self.__class__.__name__, uid] if p])}>"
def _loadData(self, data):
self._data = data
self.hasAttribution = data.attrib.get('hasAttribution')
self.hasPrefs = data.attrib.get('hasPrefs')
self.identifier = data.attrib.get('identifier')
self.name = data.attrib.get('name')
self.primary = data.attrib.get('primary')
self.shortIdentifier = self.identifier.rsplit('.', 1)[1]
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 = f'/:/plugins/{self.identifier}/prefs'
data = self._server.query(key)
return self.findItems(data, cls=settings.Setting)
@deprecated('use "settings" instead')
def _settings(self):
return self.settings()
[docs]
@utils.registerPlexObject
class Availability(PlexObject):
""" Represents a single online streaming service Availability.
Attributes:
TAG (str): 'Availability'
country (str): The streaming service country.
offerType (str): Subscription, buy, or rent from the streaming service.
platform (str): The platform slug for the streaming service.
platformColorThumb (str): Thumbnail icon for the streaming service.
platformInfo (str): The streaming service platform info.
platformUrl (str): The URL to the media on the streaming service.
price (float): The price to buy or rent from the streaming service.
priceDescription (str): The display price to buy or rent from the streaming service.
quality (str): The video quality on the streaming service.
title (str): The title of the streaming service.
url (str): The Plex availability URL.
"""
TAG = 'Availability'
def __repr__(self):
return f'<{self.__class__.__name__}:{self.platform}:{self.offerType}>'
def _loadData(self, data):
self._data = data
self.country = data.attrib.get('country')
self.offerType = data.attrib.get('offerType')
self.platform = data.attrib.get('platform')
self.platformColorThumb = data.attrib.get('platformColorThumb')
self.platformInfo = data.attrib.get('platformInfo')
self.platformUrl = data.attrib.get('platformUrl')
self.price = utils.cast(float, data.attrib.get('price'))
self.priceDescription = data.attrib.get('priceDescription')
self.quality = data.attrib.get('quality')
self.title = data.attrib.get('title')
self.url = data.attrib.get('url')