python-plexapi/plexapi/media.py

816 lines
31 KiB
Python
Raw Normal View History

# -*- coding: utf-8 -*-
2020-03-14 14:35:56 +00:00
import xml
2020-05-12 21:15:16 +00:00
from urllib.parse import quote_plus
2020-03-14 14:35:56 +00:00
2020-05-12 21:15:16 +00:00
from plexapi import log, settings, utils
from plexapi.base import PlexObject
from plexapi.exceptions import BadRequest
from plexapi.utils import cast
2014-12-29 03:21:58 +00:00
@utils.registerPlexObject
class Media(PlexObject):
""" Container object for all MediaPart objects. Provides useful data about the
video this media belong to such as video framerate, resolution, etc.
Attributes:
TAG (str): 'Media'
server (:class:`~plexapi.server.PlexServer`): PlexServer object this is from.
initpath (str): Relative path requested when retrieving specified data.
video (str): Video this media belongs to.
aspectRatio (float): Aspect ratio of the video (ex: 2.35).
audioChannels (int): Number of audio channels for this video (ex: 6).
audioCodec (str): Audio codec used within the video (ex: ac3).
bitrate (int): Bitrate of the video (ex: 1624)
container (str): Container this video is in (ex: avi).
duration (int): Length of the video in milliseconds (ex: 6990483).
height (int): Height of the video in pixels (ex: 256).
id (int): Plex ID of this media item (ex: 46184).
has64bitOffsets (bool): True if video has 64 bit offsets (?).
optimizedForStreaming (bool): True if video is optimized for streaming.
target (str): Media version target name.
title (str): Media version title.
videoCodec (str): Video codec used within the video (ex: ac3).
videoFrameRate (str): Video frame rate (ex: 24p).
videoResolution (str): Video resolution (ex: sd).
2018-10-02 14:52:32 +00:00
videoProfile (str): Video profile (ex: high).
width (int): Width of the video in pixels (ex: 608).
parts (list<:class:`~plexapi.media.MediaPart`>): List of MediaParts in this video.
"""
TAG = 'Media'
2015-06-08 16:41:47 +00:00
def _loadData(self, data):
""" Load attribute values from Plex XML response. """
self._data = data
2014-12-29 03:21:58 +00:00
self.aspectRatio = cast(float, data.attrib.get('aspectRatio'))
self.audioChannels = cast(int, data.attrib.get('audioChannels'))
self.audioCodec = data.attrib.get('audioCodec')
2016-04-10 03:59:47 +00:00
self.bitrate = cast(int, data.attrib.get('bitrate'))
2014-12-29 03:21:58 +00:00
self.container = data.attrib.get('container')
2016-04-10 03:59:47 +00:00
self.duration = cast(int, data.attrib.get('duration'))
self.height = cast(int, data.attrib.get('height'))
self.id = cast(int, data.attrib.get('id'))
self.has64bitOffsets = cast(bool, data.attrib.get('has64bitOffsets'))
2016-04-10 03:59:47 +00:00
self.optimizedForStreaming = cast(bool, data.attrib.get('optimizedForStreaming'))
self.target = data.attrib.get('target')
self.title = data.attrib.get('title')
2016-04-10 03:59:47 +00:00
self.videoCodec = data.attrib.get('videoCodec')
self.videoFrameRate = data.attrib.get('videoFrameRate')
self.videoProfile = data.attrib.get('videoProfile')
2016-04-10 03:59:47 +00:00
self.videoResolution = data.attrib.get('videoResolution')
self.width = cast(int, data.attrib.get('width'))
self.parts = self.findItems(data, MediaPart)
2014-12-29 03:21:58 +00:00
def delete(self):
part = self._initpath + '/media/%s' % self.id
try:
return self._server.query(part, method=self._server._session.delete)
except BadRequest:
log.error("Failed to delete %s. This could be because you havn't allowed "
"items to be deleted" % part)
raise
2014-12-29 03:21:58 +00:00
@utils.registerPlexObject
class MediaPart(PlexObject):
""" Represents a single media part (often a single file) for the media this belongs to.
Attributes:
TAG (str): 'Part'
server (:class:`~plexapi.server.PlexServer`): PlexServer object this is from.
initpath (str): Relative path requested when retrieving specified data.
media (:class:`~plexapi.media.Media`): Media object this part belongs to.
container (str): Container type of this media part (ex: avi).
duration (int): Length of this media part in milliseconds.
file (str): Path to this file on disk (ex: /media/Movies/Cars.(2006)/Cars.cd2.avi)
id (int): Unique ID of this media part.
indexes (str, None): None or SD.
key (str): Key used to access this media part (ex: /library/parts/46618/1389985872/file.avi).
size (int): Size of this file in bytes (ex: 733884416).
streams (list<:class:`~plexapi.media.MediaPartStream`>): List of streams in this media part.
exists (bool): Determine if file exists
accessible (bool): Determine if file is accessible
"""
TAG = 'Part'
2014-12-29 03:21:58 +00:00
def _loadData(self, data):
""" Load attribute values from Plex XML response. """
self._data = data
2016-04-10 03:59:47 +00:00
self.container = data.attrib.get('container')
2014-12-29 03:21:58 +00:00
self.duration = cast(int, data.attrib.get('duration'))
self.file = data.attrib.get('file')
2016-04-10 03:59:47 +00:00
self.id = cast(int, data.attrib.get('id'))
self.indexes = data.attrib.get('indexes')
2016-04-10 03:59:47 +00:00
self.key = data.attrib.get('key')
2014-12-29 03:21:58 +00:00
self.size = cast(int, data.attrib.get('size'))
2018-09-08 15:25:16 +00:00
self.decision = data.attrib.get('decision')
self.optimizedForStreaming = cast(bool, data.attrib.get('optimizedForStreaming'))
self.syncItemId = cast(int, data.attrib.get('syncItemId'))
self.syncState = data.attrib.get('syncState')
self.videoProfile = data.attrib.get('videoProfile')
self.streams = self._buildStreams(data)
self.exists = cast(bool, data.attrib.get('exists'))
self.accessible = cast(bool, data.attrib.get('accessible'))
2014-12-29 03:21:58 +00:00
def _buildStreams(self, data):
streams = []
for elem in data:
for cls in (VideoStream, AudioStream, SubtitleStream):
if elem.attrib.get('streamType') == str(cls.STREAMTYPE):
streams.append(cls(self._server, elem, self._initpath))
return streams
def videoStreams(self):
""" Returns a list of :class:`~plexapi.media.VideoStream` objects in this MediaPart. """
return [stream for stream in self.streams if stream.streamType == VideoStream.STREAMTYPE]
def audioStreams(self):
""" Returns a list of :class:`~plexapi.media.AudioStream` objects in this MediaPart. """
return [stream for stream in self.streams if stream.streamType == AudioStream.STREAMTYPE]
def subtitleStreams(self):
""" Returns a list of :class:`~plexapi.media.SubtitleStream` objects in this MediaPart. """
return [stream for stream in self.streams if stream.streamType == SubtitleStream.STREAMTYPE]
2019-02-05 02:07:22 +00:00
def setDefaultAudioStream(self, stream):
""" Set the default :class:`~plexapi.media.AudioStream` for this MediaPart.
Parameters:
stream (:class:`~plexapi.media.AudioStream`): AudioStream to set as default
"""
2019-02-06 23:22:28 +00:00
if isinstance(stream, AudioStream):
2019-02-04 22:28:30 +00:00
key = "/library/parts/%d?audioStreamID=%d&allParts=1" % (self.id, stream.id)
else:
2019-02-06 23:22:28 +00:00
key = "/library/parts/%d?audioStreamID=%d&allParts=1" % (self.id, stream)
self._server.query(key, method=self._server._session.put)
2019-02-05 02:07:22 +00:00
def setDefaultSubtitleStream(self, stream):
""" Set the default :class:`~plexapi.media.SubtitleStream` for this MediaPart.
2020-03-14 14:35:56 +00:00
Parameters:
2019-02-05 02:07:22 +00:00
stream (:class:`~plexapi.media.SubtitleStream`): SubtitleStream to set as default.
"""
2019-02-06 23:22:28 +00:00
if isinstance(stream, SubtitleStream):
2019-02-04 22:28:30 +00:00
key = "/library/parts/%d?subtitleStreamID=%d&allParts=1" % (self.id, stream.id)
else:
2019-02-06 23:22:28 +00:00
key = "/library/parts/%d?subtitleStreamID=%d&allParts=1" % (self.id, stream)
self._server.query(key, method=self._server._session.put)
2019-02-06 23:22:28 +00:00
def resetDefaultSubtitleStream(self):
2019-02-05 02:07:22 +00:00
""" Set default subtitle of this MediaPart to 'none'. """
key = "/library/parts/%d?subtitleStreamID=0&allParts=1" % (self.id)
self._server.query(key, method=self._server._session.put)
2014-12-29 03:21:58 +00:00
class MediaPartStream(PlexObject):
""" Base class for media streams. These consist of video, audio and subtitles.
Attributes:
server (:class:`~plexapi.server.PlexServer`): PlexServer object this is from.
initpath (str): Relative path requested when retrieving specified data.
part (:class:`~plexapi.media.MediaPart`): Media part this stream belongs to.
codec (str): Codec of this stream (ex: srt, ac3, mpeg4).
codecID (str): Codec ID (ex: XVID).
id (int): Unique stream ID on this server.
index (int): Unknown
language (str): Stream language (ex: English, ไทย).
languageCode (str): Ascii code for language (ex: eng, tha).
selected (bool): True if this stream is selected.
streamType (int): Stream type (1=:class:`~plexapi.media.VideoStream`,
2=:class:`~plexapi.media.AudioStream`, 3=:class:`~plexapi.media.SubtitleStream`).
type (int): Alias for streamType.
"""
def _loadData(self, data):
""" Load attribute values from Plex XML response. """
self._data = data
2014-12-29 03:21:58 +00:00
self.codec = data.attrib.get('codec')
self.codecID = data.attrib.get('codecID')
self.id = cast(int, data.attrib.get('id'))
2014-12-29 03:21:58 +00:00
self.index = cast(int, data.attrib.get('index', '-1'))
self.language = data.attrib.get('language')
self.languageCode = data.attrib.get('languageCode')
self.selected = cast(bool, data.attrib.get('selected', '0'))
self.streamType = cast(int, data.attrib.get('streamType'))
self.type = cast(int, data.attrib.get('streamType'))
2014-12-29 03:21:58 +00:00
@staticmethod
2017-10-26 21:55:59 +00:00
def parse(server, data, initpath): # pragma: no cover seems to be dead code.
""" Factory method returns a new MediaPartStream from xml data. """
2017-02-20 05:37:00 +00:00
STREAMCLS = {1: VideoStream, 2: AudioStream, 3: SubtitleStream}
2014-12-29 03:21:58 +00:00
stype = cast(int, data.attrib.get('streamType'))
cls = STREAMCLS.get(stype, MediaPartStream)
return cls(server, data, initpath)
2014-12-29 03:21:58 +00:00
@utils.registerPlexObject
class VideoStream(MediaPartStream):
""" Respresents a video stream within a :class:`~plexapi.media.MediaPart`.
Attributes:
TAG (str): 'Stream'
STREAMTYPE (int): 1
bitDepth (int): Bit depth (ex: 8).
bitrate (int): Bitrate (ex: 1169)
cabac (int): Unknown
chromaSubsampling (str): Chroma Subsampling (ex: 4:2:0).
colorSpace (str): Unknown
duration (int): Duration of video stream in milliseconds.
frameRate (float): Frame rate (ex: 23.976)
frameRateMode (str): Unknown
hasScallingMatrix (bool): True if video stream has a scaling matrix.
height (int): Height of video stream.
level (int): Videl stream level (?).
profile (str): Video stream profile (ex: asp).
refFrames (int): Unknown
scanType (str): Video stream scan type (ex: progressive).
title (str): Title of this video stream.
width (int): Width of video stream.
"""
TAG = 'Stream'
STREAMTYPE = 1
2014-12-29 03:21:58 +00:00
def _loadData(self, data):
""" Load attribute values from Plex XML response. """
super(VideoStream, self)._loadData(data)
self.bitDepth = cast(int, data.attrib.get('bitDepth'))
2016-04-10 03:59:47 +00:00
self.bitrate = cast(int, data.attrib.get('bitrate'))
2014-12-29 03:21:58 +00:00
self.cabac = cast(int, data.attrib.get('cabac'))
self.chromaSubsampling = data.attrib.get('chromaSubsampling')
self.colorSpace = data.attrib.get('colorSpace')
2014-12-29 03:21:58 +00:00
self.duration = cast(int, data.attrib.get('duration'))
self.frameRate = cast(float, data.attrib.get('frameRate'))
self.frameRateMode = data.attrib.get('frameRateMode')
self.hasScallingMatrix = cast(bool, data.attrib.get('hasScallingMatrix'))
2014-12-29 03:21:58 +00:00
self.height = cast(int, data.attrib.get('height'))
self.level = cast(int, data.attrib.get('level'))
self.profile = data.attrib.get('profile')
self.refFrames = cast(int, data.attrib.get('refFrames'))
self.scanType = data.attrib.get('scanType')
2014-12-29 03:21:58 +00:00
self.title = data.attrib.get('title')
self.width = cast(int, data.attrib.get('width'))
@utils.registerPlexObject
class AudioStream(MediaPartStream):
""" Respresents a audio stream within a :class:`~plexapi.media.MediaPart`.
Attributes:
TAG (str): 'Stream'
STREAMTYPE (int): 2
audioChannelLayout (str): Audio channel layout (ex: 5.1(side)).
bitDepth (int): Bit depth (ex: 16).
bitrate (int): Audio bitrate (ex: 448).
bitrateMode (str): Bitrate mode (ex: cbr).
channels (int): number of channels in this stream (ex: 6).
dialogNorm (int): Unknown (ex: -27).
duration (int): Duration of audio stream in milliseconds.
samplingRate (int): Sampling rate (ex: xxx)
title (str): Title of this audio stream.
"""
TAG = 'Stream'
STREAMTYPE = 2
2014-12-29 03:21:58 +00:00
def _loadData(self, data):
""" Load attribute values from Plex XML response. """
super(AudioStream, self)._loadData(data)
self.audioChannelLayout = data.attrib.get('audioChannelLayout')
self.bitDepth = cast(int, data.attrib.get('bitDepth'))
2016-04-10 03:59:47 +00:00
self.bitrate = cast(int, data.attrib.get('bitrate'))
self.bitrateMode = data.attrib.get('bitrateMode')
2016-04-10 03:59:47 +00:00
self.channels = cast(int, data.attrib.get('channels'))
self.dialogNorm = cast(int, data.attrib.get('dialogNorm'))
2014-12-29 03:21:58 +00:00
self.duration = cast(int, data.attrib.get('duration'))
self.samplingRate = cast(int, data.attrib.get('samplingRate'))
2014-12-29 03:21:58 +00:00
self.title = data.attrib.get('title')
@utils.registerPlexObject
class SubtitleStream(MediaPartStream):
""" Respresents a audio stream within a :class:`~plexapi.media.MediaPart`.
Attributes:
TAG (str): 'Stream'
STREAMTYPE (int): 3
2019-02-04 19:15:05 +00:00
forced (bool): True if this is a forced subtitle
format (str): Subtitle format (ex: srt).
key (str): Key of this subtitle stream (ex: /library/streams/212284).
title (str): Title of this subtitle stream.
"""
TAG = 'Stream'
STREAMTYPE = 3
2014-12-29 03:21:58 +00:00
def _loadData(self, data):
""" Load attribute values from Plex XML response. """
super(SubtitleStream, self)._loadData(data)
2019-02-04 19:15:05 +00:00
self.forced = cast(bool, data.attrib.get('forced', '0'))
2014-12-29 03:21:58 +00:00
self.format = data.attrib.get('format')
2016-04-10 03:59:47 +00:00
self.key = data.attrib.get('key')
self.title = data.attrib.get('title')
2014-12-29 03:21:58 +00:00
2017-02-26 22:31:09 +00:00
@utils.registerPlexObject
class Session(PlexObject):
2017-02-27 02:12:56 +00:00
""" Represents a current session. """
2017-02-26 22:31:09 +00:00
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')
@utils.registerPlexObject
class TranscodeSession(PlexObject):
""" Represents a current transcode session.
Attributes:
TAG (str): 'TranscodeSession'
TODO: Document this.
"""
TAG = 'TranscodeSession'
def _loadData(self, data):
""" Load attribute values from Plex XML response. """
self._data = data
2016-04-10 03:59:47 +00:00
self.audioChannels = cast(int, data.attrib.get('audioChannels'))
self.audioCodec = data.attrib.get('audioCodec')
self.audioDecision = data.attrib.get('audioDecision')
self.container = data.attrib.get('container')
self.context = data.attrib.get('context')
self.duration = cast(int, data.attrib.get('duration'))
self.height = cast(int, data.attrib.get('height'))
self.key = data.attrib.get('key')
self.progress = cast(float, data.attrib.get('progress'))
self.protocol = data.attrib.get('protocol')
2016-04-10 03:59:47 +00:00
self.remaining = cast(int, data.attrib.get('remaining'))
self.speed = cast(int, data.attrib.get('speed'))
self.throttled = cast(int, data.attrib.get('throttled'))
self.sourceVideoCodec = data.attrib.get('sourceVideoCodec')
self.videoCodec = data.attrib.get('videoCodec')
2016-04-10 03:59:47 +00:00
self.videoDecision = data.attrib.get('videoDecision')
self.width = cast(int, data.attrib.get('width'))
@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')
2019-10-09 03:26:42 +00:00
@utils.registerPlexObject
2019-10-10 14:43:52 +00:00
class Optimized(PlexObject):
""" Represents a Optimized item.
Optimized items are optimized and queued conversions items."""
2020-01-27 19:17:49 +00:00
TAG = 'Item'
2019-10-09 03:26:42 +00:00
def _loadData(self, data):
self._data = data
2019-10-09 03:26:42 +00:00
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')
2019-10-10 02:28:15 +00:00
self.targetTagID = data.attrib.get('targetTagID')
2019-10-09 03:26:42 +00:00
def remove(self):
""" Remove an Optimized item"""
key = '%s/%s' % (self._initpath, self.id)
self._server.query(key, method=self._server._session.delete)
def rename(self, title):
""" Rename an Optimized item"""
key = '%s/%s?Item[title]=%s' % (self._initpath, self.id, title)
self._server.query(key, method=self._server._session.put)
2019-10-09 03:26:42 +00:00
def reprocess(self, ratingKey):
""" Reprocess a removed Conversion item that is still a listed Optimize item"""
key = '%s/%s/%s/enable' % (self._initpath, self.id, ratingKey)
self._server.query(key, method=self._server._session.put)
2019-10-09 03:26:42 +00:00
2019-10-10 15:11:43 +00:00
@utils.registerPlexObject
class Conversion(PlexObject):
2020-01-29 14:17:20 +00:00
""" Represents a Conversion item.
Conversions are items queued for optimization or being actively optimized."""
2020-01-27 19:16:41 +00:00
TAG = 'Video'
2019-10-10 15:11:43 +00:00
def _loadData(self, data):
2020-03-14 14:35:56 +00:00
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')
2019-10-10 15:11:43 +00:00
def remove(self):
""" Remove Conversion from queue """
key = '/playlists/%s/items/%s/%s/disable' % (self.playlistID, self.generatorID, self.ratingKey)
self._server.query(key, method=self._server._session.put)
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')
2020-04-16 21:18:38 +00:00
Move 4th conversion Item to 3rd in conversion queue
conversions[3].move(conversions[1].playQueueItemID)
"""
key = '%s/items/%s/move?after=%s' % (self._initpath, self.playQueueItemID, after)
self._server.query(key, method=self._server._session.put)
2019-10-10 15:11:43 +00:00
class MediaTag(PlexObject):
""" Base class for media tags used for filtering and searching your library
items or navigating the metadata of media items in your library. Tags are
the construct used for things such as Country, Director, Genre, etc.
Attributes:
server (:class:`~plexapi.server.PlexServer`): Server this client is connected to.
id (id): Tag ID (This seems meaningless except to use it as a unique id).
role (str): Unknown
tag (str): Name of the tag. This will be Animation, SciFi etc for Genres. The name of
person for Directors and Roles (ex: Animation, Stephen Graham, etc).
<Hub_Search_Attributes>: Attributes only applicable in search results from
2020-11-23 03:06:30 +00:00
PlexServer :func:`~plexapi.server.PlexServer.search`. They provide details of which
library section the tag was found as well as the url to dig deeper into the results.
* key (str): API URL to dig deeper into this tag (ex: /library/sections/1/all?actor=9081).
* librarySectionID (int): Section ID this tag was generated from.
* librarySectionTitle (str): Library section title this tag was found.
* librarySectionType (str): Media type of the library section this tag was found.
* tagType (int): Tag type ID.
* thumb (str): URL to thumbnail image.
"""
def _loadData(self, data):
""" Load attribute values from Plex XML response. """
self._data = data
2014-12-29 03:21:58 +00:00
self.id = cast(int, data.attrib.get('id'))
self.role = data.attrib.get('role')
2016-04-10 03:59:47 +00:00
self.tag = data.attrib.get('tag')
# additional attributes only from hub search
self.key = data.attrib.get('key')
self.librarySectionID = cast(int, data.attrib.get('librarySectionID'))
self.librarySectionTitle = data.attrib.get('librarySectionTitle')
self.librarySectionType = data.attrib.get('librarySectionType')
self.tagType = cast(int, data.attrib.get('tagType'))
self.thumb = data.attrib.get('thumb')
2014-12-29 03:21:58 +00:00
def items(self, *args, **kwargs):
""" Return the list of items within this tag. This function is only applicable
2020-11-23 03:06:30 +00:00
in search results from PlexServer :func:`~plexapi.server.PlexServer.search`.
"""
if not self.key:
raise BadRequest('Key is not defined for this tag: %s' % self.tag)
return self.fetchItems(self.key)
2014-12-29 03:21:58 +00:00
@utils.registerPlexObject
2017-02-02 03:53:05 +00:00
class Collection(MediaTag):
""" Represents a single Collection media tag.
Attributes:
TAG (str): 'Collection'
FILTER (str): 'collection'
"""
TAG = 'Collection'
2017-02-02 03:53:05 +00:00
FILTER = 'collection'
2017-02-20 05:37:00 +00:00
@utils.registerPlexObject
class Label(MediaTag):
""" Represents a single label media tag.
Attributes:
TAG (str): 'label'
FILTER (str): 'label'
"""
TAG = 'Label'
FILTER = 'label'
2019-11-11 14:32:55 +00:00
@utils.registerPlexObject
class Tag(MediaTag):
""" Represents a single tag media tag.
Attributes:
TAG (str): 'tag'
FILTER (str): 'tag'
"""
TAG = 'Tag'
FILTER = 'tag'
def _loadData(self, data):
self._data = data
self.id = cast(int, data.attrib.get('id', 0))
self.filter = data.attrib.get('filter')
self.tag = data.attrib.get('tag')
self.title = self.tag
@utils.registerPlexObject
2017-02-02 03:53:05 +00:00
class Country(MediaTag):
""" Represents a single Country media tag.
Attributes:
TAG (str): 'Country'
FILTER (str): 'country'
"""
TAG = 'Country'
2017-02-02 03:53:05 +00:00
FILTER = 'country'
2017-02-20 05:37:00 +00:00
@utils.registerPlexObject
2017-02-02 03:53:05 +00:00
class Director(MediaTag):
""" Represents a single Director media tag.
Attributes:
TAG (str): 'Director'
FILTER (str): 'director'
"""
TAG = 'Director'
2017-02-02 03:53:05 +00:00
FILTER = 'director'
2017-02-20 05:37:00 +00:00
@utils.registerPlexObject
2017-02-02 03:53:05 +00:00
class Genre(MediaTag):
""" Represents a single Genre media tag.
Attributes:
TAG (str): 'Genre'
FILTER (str): 'genre'
"""
TAG = 'Genre'
2017-02-02 03:53:05 +00:00
FILTER = 'genre'
2017-02-20 05:37:00 +00:00
@utils.registerPlexObject
2017-02-02 03:53:05 +00:00
class Mood(MediaTag):
""" Represents a single Mood media tag.
Attributes:
TAG (str): 'Mood'
FILTER (str): 'mood'
"""
TAG = 'Mood'
2017-02-02 03:53:05 +00:00
FILTER = 'mood'
2017-02-20 05:37:00 +00:00
2020-12-23 21:08:46 +00:00
@utils.registerPlexObject
class Style(MediaTag):
""" Represents a single Style media tag.
Attributes:
TAG (str): 'Style'
FILTER (str): 'style'
"""
TAG = 'Style'
FILTER = 'style'
2019-10-09 14:44:51 +00:00
@utils.registerPlexObject
class Poster(PlexObject):
""" Represents a Poster.
Attributes:
2019-11-06 14:28:19 +00:00
TAG (str): 'Photo'
2019-10-09 14:44:51 +00:00
"""
2019-11-06 14:28:19 +00:00
TAG = 'Photo'
2019-10-09 14:44:51 +00:00
def _loadData(self, data):
self._data = data
self.key = data.attrib.get('key')
self.ratingKey = data.attrib.get('ratingKey')
self.selected = data.attrib.get('selected')
self.thumb = data.attrib.get('thumb')
def select(self):
key = self._initpath[:-1]
2020-05-12 21:15:16 +00:00
data = '%s?url=%s' % (key, quote_plus(self.ratingKey))
2020-03-14 14:35:56 +00:00
try:
self._server.query(data, method=self._server._session.put)
except xml.etree.ElementTree.ParseError:
pass
2019-10-09 14:44:51 +00:00
@utils.registerPlexObject
2017-02-02 03:53:05 +00:00
class Producer(MediaTag):
""" Represents a single Producer media tag.
Attributes:
TAG (str): 'Producer'
FILTER (str): 'producer'
"""
TAG = 'Producer'
2017-02-02 03:53:05 +00:00
FILTER = 'producer'
2017-02-20 05:37:00 +00:00
@utils.registerPlexObject
2017-02-02 03:53:05 +00:00
class Role(MediaTag):
""" Represents a single Role (actor/actress) media tag.
Attributes:
TAG (str): 'Role'
FILTER (str): 'role'
"""
TAG = 'Role'
2017-02-02 03:53:05 +00:00
FILTER = 'role'
2017-02-20 05:37:00 +00:00
@utils.registerPlexObject
2017-02-02 03:53:05 +00:00
class Similar(MediaTag):
""" Represents a single Similar media tag.
Attributes:
TAG (str): 'Similar'
FILTER (str): 'similar'
"""
TAG = 'Similar'
2017-02-02 03:53:05 +00:00
FILTER = 'similar'
2017-02-20 05:37:00 +00:00
@utils.registerPlexObject
2017-02-02 03:53:05 +00:00
class Writer(MediaTag):
""" Represents a single Writer media tag.
Attributes:
TAG (str): 'Writer'
FILTER (str): 'writer'
"""
TAG = 'Writer'
2017-02-02 03:53:05 +00:00
FILTER = 'writer'
2016-10-02 20:05:37 +00:00
2017-02-20 05:37:00 +00:00
2018-03-02 12:15:15 +00:00
@utils.registerPlexObject
class Chapter(PlexObject):
""" Represents a single Writer media tag.
Attributes:
TAG (str): 'Chapter'
"""
TAG = 'Chapter'
def _loadData(self, data):
self._data = data
self.id = cast(int, data.attrib.get('id', 0))
self.filter = data.attrib.get('filter') # I couldn't filter on it anyways
self.tag = data.attrib.get('tag')
self.title = self.tag
self.index = cast(int, data.attrib.get('index'))
self.start = cast(int, data.attrib.get('startTimeOffset'))
self.end = cast(int, data.attrib.get('endTimeOffset'))
2020-05-23 06:09:22 +00:00
@utils.registerPlexObject
class Marker(PlexObject):
""" Represents a single Marker media tag.
Attributes:
TAG (str): 'Marker'
"""
TAG = 'Marker'
def __repr__(self):
name = self._clean(self.firstAttr('type'))
2020-06-14 18:21:46 +00:00
start = utils.millisecondToHumanstr(self._clean(self.firstAttr('start')))
end = utils.millisecondToHumanstr(self._clean(self.firstAttr('end')))
return '<%s:%s %s - %s>' % (self.__class__.__name__, name, start, end)
2020-05-23 06:09:22 +00:00
def _loadData(self, data):
self._data = data
self.type = data.attrib.get('type')
self.start = cast(int, data.attrib.get('startTimeOffset'))
self.end = cast(int, data.attrib.get('endTimeOffset'))
2020-10-21 12:58:56 +00:00
@utils.registerPlexObject
class Field(PlexObject):
""" Represents a single Field.
Attributes:
TAG (str): 'Field'
"""
TAG = 'Field'
2016-10-02 20:05:37 +00:00
def _loadData(self, data):
self._data = data
2016-10-02 20:05:37 +00:00
self.name = data.attrib.get('name')
self.locked = cast(bool, data.attrib.get('locked'))
2020-03-04 21:21:13 +00:00
@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 '<%s>' % ':'.join([p for p in [self.__class__.__name__, name, score] if p])
2020-03-04 21:21:13 +00:00
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 = cast(int, data.attrib.get('score'))
2020-03-04 21:21:13 +00:00
self.year = data.attrib.get('year')
2020-03-10 20:06:43 +00:00
@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 '<%s>' % ':'.join([p for p in [self.__class__.__name__, uid] if p])
2020-03-10 20:06:43 +00:00
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')
2020-03-10 20:17:24 +00:00
self.primary = data.attrib.get('primary')
self.shortIdentifier = self.identifier.rsplit('.', 1)[1]
if 'mediaType' in self._initpath:
self.name = data.attrib.get('name')
self.languageCode = []
for code in data:
self.languageCode += [code.attrib.get('code')]
else:
self.mediaTypes = [AgentMediaType(server=self._server, data=d) for d in data]
def _settings(self):
key = '/:/plugins/%s/prefs' % self.identifier
data = self._server.query(key)
return self.findItems(data, cls=settings.Setting)
class AgentMediaType(Agent):
def __repr__(self):
uid = self._clean(self.firstAttr('name'))
return '<%s>' % ':'.join([p for p in [self.__class__.__name__, uid] if p])
def _loadData(self, data):
2020-03-16 17:53:10 +00:00
self.mediaType = cast(int, data.attrib.get('mediaType'))
self.name = data.attrib.get('name')
2020-03-16 19:10:37 +00:00
self.languageCode = []
for code in data:
self.languageCode += [code.attrib.get('code')]