2016-03-21 04:26:02 +00:00
|
|
|
# -*- coding: utf-8 -*-
|
2017-02-03 07:15:41 +00:00
|
|
|
from plexapi.exceptions import BadRequest
|
|
|
|
from plexapi.utils import cast, listItems
|
2014-12-29 03:21:58 +00:00
|
|
|
|
|
|
|
|
|
|
|
class Media(object):
|
|
|
|
TYPE = 'Media'
|
2015-06-08 16:41:47 +00:00
|
|
|
|
2014-12-29 03:21:58 +00:00
|
|
|
def __init__(self, server, data, initpath, video):
|
|
|
|
self.server = server
|
|
|
|
self.initpath = initpath
|
|
|
|
self.video = video
|
|
|
|
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'))
|
2014-12-29 03:21:58 +00:00
|
|
|
self.optimizedForStreaming = cast(bool, data.attrib.get('has64bitOffsets'))
|
2016-04-10 03:59:47 +00:00
|
|
|
self.optimizedForStreaming = cast(bool, data.attrib.get('optimizedForStreaming'))
|
|
|
|
self.videoCodec = data.attrib.get('videoCodec')
|
|
|
|
self.videoFrameRate = data.attrib.get('videoFrameRate')
|
|
|
|
self.videoResolution = data.attrib.get('videoResolution')
|
|
|
|
self.width = cast(int, data.attrib.get('width'))
|
2016-04-02 06:19:32 +00:00
|
|
|
self.parts = [MediaPart(server, e, initpath, self) for e in data]
|
2014-12-29 03:21:58 +00:00
|
|
|
|
|
|
|
def __repr__(self):
|
|
|
|
title = self.video.title.replace(' ','.')[0:20]
|
|
|
|
return '<%s:%s>' % (self.__class__.__name__, title.encode('utf8'))
|
|
|
|
|
|
|
|
|
|
|
|
class MediaPart(object):
|
|
|
|
TYPE = 'Part'
|
|
|
|
|
|
|
|
def __init__(self, server, data, initpath, media):
|
|
|
|
self.server = server
|
|
|
|
self.initpath = initpath
|
|
|
|
self.media = media
|
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.key = data.attrib.get('key')
|
2014-12-29 03:21:58 +00:00
|
|
|
self.size = cast(int, data.attrib.get('size'))
|
2016-03-24 06:20:08 +00:00
|
|
|
self.streams = [MediaPartStream.parse(self.server, e, self.initpath, self) for e in data if e.tag == 'Stream']
|
2014-12-29 03:21:58 +00:00
|
|
|
|
|
|
|
def __repr__(self):
|
|
|
|
return '<%s:%s>' % (self.__class__.__name__, self.id)
|
|
|
|
|
2016-03-21 04:26:02 +00:00
|
|
|
def selectedStream(self, stream_type):
|
2014-12-29 03:21:58 +00:00
|
|
|
streams = filter(lambda x: stream_type == x.type, self.streams)
|
2015-09-05 14:09:15 +00:00
|
|
|
selected = list(filter(lambda x: x.selected is True, streams))
|
2014-12-29 03:21:58 +00:00
|
|
|
if len(selected) == 0:
|
|
|
|
return None
|
|
|
|
return selected[0]
|
|
|
|
|
|
|
|
|
|
|
|
class MediaPartStream(object):
|
2016-03-24 06:20:08 +00:00
|
|
|
TYPE = None
|
|
|
|
STREAMTYPE = None
|
2014-12-29 03:21:58 +00:00
|
|
|
|
|
|
|
def __init__(self, server, data, initpath, part):
|
|
|
|
self.server = server
|
|
|
|
self.initpath = initpath
|
|
|
|
self.part = part
|
|
|
|
self.codec = data.attrib.get('codec')
|
2016-03-24 06:20:08 +00:00
|
|
|
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'))
|
2016-03-24 06:20:08 +00:00
|
|
|
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
|
|
|
|
def parse(server, data, initpath, part):
|
2016-03-24 06:20:08 +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, part)
|
|
|
|
|
|
|
|
def __repr__(self):
|
|
|
|
return '<%s:%s>' % (self.__class__.__name__, self.id)
|
|
|
|
|
|
|
|
|
2016-03-24 06:20:08 +00:00
|
|
|
class VideoStream(MediaPartStream):
|
|
|
|
TYPE = 'videostream'
|
|
|
|
STREAMTYPE = 1
|
2014-12-29 03:21:58 +00:00
|
|
|
|
|
|
|
def __init__(self, server, data, initpath, part):
|
2016-03-24 06:20:08 +00:00
|
|
|
super(VideoStream, self).__init__(server, data, initpath, part)
|
|
|
|
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'))
|
2016-03-24 06:20:08 +00:00
|
|
|
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'))
|
2016-03-24 06:20:08 +00:00
|
|
|
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')
|
2016-03-24 06:20:08 +00:00
|
|
|
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'))
|
|
|
|
|
|
|
|
|
2016-03-24 06:20:08 +00:00
|
|
|
class AudioStream(MediaPartStream):
|
|
|
|
TYPE = 'audiostream'
|
|
|
|
STREAMTYPE = 2
|
2014-12-29 03:21:58 +00:00
|
|
|
|
|
|
|
def __init__(self, server, data, initpath, part):
|
2016-03-24 06:20:08 +00:00
|
|
|
super(AudioStream, self).__init__(server, data, initpath, part)
|
|
|
|
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'))
|
2016-03-24 06:20:08 +00:00
|
|
|
self.bitrateMode = data.attrib.get('bitrateMode')
|
2016-04-10 03:59:47 +00:00
|
|
|
self.channels = cast(int, data.attrib.get('channels'))
|
2016-03-24 06:20:08 +00:00
|
|
|
self.dialogNorm = cast(int, data.attrib.get('dialogNorm'))
|
2014-12-29 03:21:58 +00:00
|
|
|
self.duration = cast(int, data.attrib.get('duration'))
|
2016-03-24 06:20:08 +00:00
|
|
|
self.samplingRate = cast(int, data.attrib.get('samplingRate'))
|
2014-12-29 03:21:58 +00:00
|
|
|
self.title = data.attrib.get('title')
|
|
|
|
|
|
|
|
|
2016-03-24 06:20:08 +00:00
|
|
|
class SubtitleStream(MediaPartStream):
|
|
|
|
TYPE = 'subtitlestream'
|
|
|
|
STREAMTYPE = 3
|
2014-12-29 03:21:58 +00:00
|
|
|
|
|
|
|
def __init__(self, server, data, initpath, part):
|
2016-03-24 06:20:08 +00:00
|
|
|
super(SubtitleStream, self).__init__(server, data, initpath, part)
|
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')
|
2016-03-24 06:20:08 +00:00
|
|
|
self.title = data.attrib.get('title')
|
2014-12-29 03:21:58 +00:00
|
|
|
|
|
|
|
|
2015-11-05 02:10:10 +00:00
|
|
|
class TranscodeSession(object):
|
|
|
|
TYPE = 'TranscodeSession'
|
|
|
|
|
|
|
|
def __init__(self, server, data):
|
|
|
|
self.server = server
|
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'))
|
2015-11-05 02:10:10 +00:00
|
|
|
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'))
|
2015-11-05 02:10:10 +00:00
|
|
|
self.videoCodec = data.attrib.get('videoCodec')
|
2016-04-10 03:59:47 +00:00
|
|
|
self.videoDecision = data.attrib.get('videoDecision')
|
2015-11-05 02:10:10 +00:00
|
|
|
self.width = cast(int, data.attrib.get('width'))
|
|
|
|
|
|
|
|
|
2016-03-21 04:26:02 +00:00
|
|
|
class MediaTag(object):
|
2017-02-03 07:15:41 +00:00
|
|
|
""" 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.
|
|
|
|
|
|
|
|
Parameters:
|
|
|
|
server (:class:`~plexapi.server.PlexServer`): PlexServer this client is connected to (optional)
|
|
|
|
data (ElementTree): Response from PlexServer used to build this object (optional).
|
|
|
|
|
|
|
|
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
|
|
|
|
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.
|
|
|
|
"""
|
2014-12-29 03:21:58 +00:00
|
|
|
TYPE = None
|
|
|
|
|
|
|
|
def __init__(self, server, data):
|
|
|
|
self.server = server
|
|
|
|
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')
|
2017-02-03 07:15:41 +00:00
|
|
|
# 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 __repr__(self):
|
2017-01-03 22:58:35 +00:00
|
|
|
tag = self.tag.replace(' ', '.')[0:20].encode('utf-8')
|
2017-02-03 07:15:41 +00:00
|
|
|
if self.librarySectionTitle:
|
|
|
|
return u'<%s:%s:%s:%s>' % (self.__class__.__name__, self.id, tag, self.librarySectionTitle)
|
|
|
|
return u'<%s:%s:%s>' % (self.__class__.__name__, self.id, tag)
|
|
|
|
|
|
|
|
def items(self, *args, **kwargs):
|
|
|
|
""" Return the list of items within this tag. This function is only applicable
|
|
|
|
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 listItems(self.server, self.key)
|
2014-12-29 03:21:58 +00:00
|
|
|
|
|
|
|
|
2017-02-02 03:53:05 +00:00
|
|
|
class Collection(MediaTag):
|
|
|
|
TYPE = 'Collection'
|
|
|
|
FILTER = 'collection'
|
|
|
|
|
|
|
|
|
|
|
|
class Country(MediaTag):
|
|
|
|
TYPE = 'Country'
|
|
|
|
FILTER = 'country'
|
|
|
|
|
|
|
|
|
|
|
|
class Director(MediaTag):
|
|
|
|
TYPE = 'Director'
|
|
|
|
FILTER = 'director'
|
|
|
|
|
|
|
|
|
|
|
|
class Genre(MediaTag):
|
|
|
|
TYPE = 'Genre'
|
|
|
|
FILTER = 'genre'
|
|
|
|
|
|
|
|
|
|
|
|
class Mood(MediaTag):
|
|
|
|
TYPE = 'Mood'
|
|
|
|
FILTER = 'mood'
|
|
|
|
|
|
|
|
|
|
|
|
class Producer(MediaTag):
|
|
|
|
TYPE = 'Producer'
|
|
|
|
FILTER = 'producer'
|
|
|
|
|
|
|
|
|
|
|
|
class Role(MediaTag):
|
|
|
|
TYPE = 'Role'
|
|
|
|
FILTER = 'role'
|
|
|
|
|
|
|
|
|
|
|
|
class Similar(MediaTag):
|
|
|
|
TYPE = 'Similar'
|
|
|
|
FILTER = 'similar'
|
|
|
|
|
|
|
|
|
|
|
|
class Writer(MediaTag):
|
|
|
|
TYPE = 'Writer'
|
|
|
|
FILTER = 'writer'
|
2016-10-02 20:05:37 +00:00
|
|
|
|
|
|
|
|
|
|
|
class Field(object):
|
|
|
|
TYPE = 'Field'
|
|
|
|
|
|
|
|
def __init__(self, data):
|
|
|
|
self.name = data.attrib.get('name')
|
|
|
|
self.locked = cast(bool, data.attrib.get('locked'))
|
|
|
|
|
|
|
|
def __repr__(self):
|
|
|
|
name = self.name.replace(' ', '.')[0:20]
|
|
|
|
return '<%s:%s:%s>' % (self.__class__.__name__, name, self.locked)
|