2016-03-21 04:26:02 +00:00
|
|
|
|
# -*- coding: utf-8 -*-
|
2016-03-17 04:51:20 +00:00
|
|
|
|
from plexapi import media, utils
|
2017-01-05 21:58:43 +00:00
|
|
|
|
from plexapi.exceptions import NotFound
|
2016-04-04 03:55:29 +00:00
|
|
|
|
from plexapi.utils import Playable, PlexPartialObject
|
2017-01-02 21:06:40 +00:00
|
|
|
|
|
2014-12-29 03:21:58 +00:00
|
|
|
|
|
2016-04-04 03:55:29 +00:00
|
|
|
|
class Video(PlexPartialObject):
|
2014-12-29 03:21:58 +00:00
|
|
|
|
TYPE = None
|
|
|
|
|
|
2016-04-04 03:55:29 +00:00
|
|
|
|
def __init__(self, server, data, initpath):
|
2017-01-02 21:06:40 +00:00
|
|
|
|
"""Default class for all video types.
|
|
|
|
|
|
2016-12-16 23:38:08 +00:00
|
|
|
|
Args:
|
|
|
|
|
server (Plexserver): The PMS server your connected to
|
|
|
|
|
data (Element): Element built from server.query
|
2017-01-02 21:06:40 +00:00
|
|
|
|
initpath (str): Relativ path fx /library/sections/1/all
|
|
|
|
|
|
2016-12-16 23:38:08 +00:00
|
|
|
|
"""
|
2016-04-04 03:55:29 +00:00
|
|
|
|
super(Video, self).__init__(data, initpath, server)
|
|
|
|
|
|
2014-12-29 03:21:58 +00:00
|
|
|
|
def _loadData(self, data):
|
2016-12-16 23:38:08 +00:00
|
|
|
|
"""Used to set the attributes
|
2017-01-02 21:06:40 +00:00
|
|
|
|
|
2016-12-16 23:38:08 +00:00
|
|
|
|
Args:
|
|
|
|
|
data (Element): Usually built from server.query
|
|
|
|
|
"""
|
2016-04-12 02:43:21 +00:00
|
|
|
|
self.listType = 'video'
|
2017-02-04 17:43:50 +00:00
|
|
|
|
self.addedAt = utils.toDatetime(data.attrib.get('addedAt'))
|
|
|
|
|
self.key = data.attrib.get('key')
|
|
|
|
|
self.lastViewedAt = utils.toDatetime(data.attrib.get('lastViewedAt'))
|
|
|
|
|
self.librarySectionID = data.attrib.get('librarySectionID')
|
|
|
|
|
self.ratingKey = utils.cast(int, data.attrib.get('ratingKey'))
|
|
|
|
|
self.summary = data.attrib.get('summary')
|
|
|
|
|
self.thumb = data.attrib.get('thumb')
|
|
|
|
|
self.title = data.attrib.get('title')
|
2016-03-21 04:26:02 +00:00
|
|
|
|
self.titleSort = data.attrib.get('titleSort', self.title)
|
2017-02-04 17:43:50 +00:00
|
|
|
|
self.type = data.attrib.get('type')
|
|
|
|
|
self.updatedAt = utils.toDatetime(data.attrib.get('updatedAt'))
|
2016-03-21 04:26:02 +00:00
|
|
|
|
self.viewCount = utils.cast(int, data.attrib.get('viewCount', 0))
|
2014-12-29 03:21:58 +00:00
|
|
|
|
|
2016-04-04 03:55:29 +00:00
|
|
|
|
@property
|
|
|
|
|
def thumbUrl(self):
|
2017-01-02 21:06:40 +00:00
|
|
|
|
"""Return url to thumb image."""
|
2017-01-02 21:19:07 +00:00
|
|
|
|
if self.thumb:
|
|
|
|
|
return self.server.url(self.thumb)
|
2016-04-04 03:55:29 +00:00
|
|
|
|
|
2014-12-29 03:21:58 +00:00
|
|
|
|
def analyze(self):
|
2016-12-16 23:38:08 +00:00
|
|
|
|
"""The primary purpose of media analysis is to gather information about
|
|
|
|
|
that mediaitem. All of the media you add to a Library has properties
|
|
|
|
|
that are useful to know–whether it's a video file,
|
|
|
|
|
a music track, or one of your photos.
|
2016-03-21 04:26:02 +00:00
|
|
|
|
"""
|
2017-01-09 14:21:54 +00:00
|
|
|
|
self.server.query('/%s/analyze' % self.key.lstrip('/'), method=self.server.session.put)
|
2014-12-29 03:21:58 +00:00
|
|
|
|
|
|
|
|
|
def markWatched(self):
|
2017-01-02 21:06:40 +00:00
|
|
|
|
"""Mark a items as watched."""
|
2014-12-29 03:21:58 +00:00
|
|
|
|
path = '/:/scrobble?key=%s&identifier=com.plexapp.plugins.library' % self.ratingKey
|
|
|
|
|
self.server.query(path)
|
|
|
|
|
self.reload()
|
|
|
|
|
|
|
|
|
|
def markUnwatched(self):
|
2017-01-02 21:06:40 +00:00
|
|
|
|
"""Mark a item as unwatched."""
|
2014-12-29 03:21:58 +00:00
|
|
|
|
path = '/:/unscrobble?key=%s&identifier=com.plexapp.plugins.library' % self.ratingKey
|
|
|
|
|
self.server.query(path)
|
|
|
|
|
self.reload()
|
|
|
|
|
|
|
|
|
|
def refresh(self):
|
2017-01-02 21:06:40 +00:00
|
|
|
|
"""Refresh a item."""
|
2016-12-16 23:38:08 +00:00
|
|
|
|
self.server.query('%s/refresh' %
|
|
|
|
|
self.key, method=self.server.session.put)
|
|
|
|
|
|
2016-04-12 02:43:21 +00:00
|
|
|
|
def section(self):
|
2017-01-02 21:06:40 +00:00
|
|
|
|
"""Library section."""
|
2016-04-12 02:43:21 +00:00
|
|
|
|
return self.server.library.sectionByID(self.librarySectionID)
|
2014-12-29 03:21:58 +00:00
|
|
|
|
|
|
|
|
|
|
2016-03-17 04:51:20 +00:00
|
|
|
|
@utils.register_libtype
|
2016-04-04 03:55:29 +00:00
|
|
|
|
class Movie(Video, Playable):
|
2014-12-29 03:21:58 +00:00
|
|
|
|
TYPE = 'movie'
|
|
|
|
|
|
|
|
|
|
def _loadData(self, data):
|
2016-12-16 23:38:08 +00:00
|
|
|
|
"""Used to set the attributes
|
2017-01-02 21:06:40 +00:00
|
|
|
|
|
2016-12-16 23:38:08 +00:00
|
|
|
|
Args:
|
2017-01-02 21:06:40 +00:00
|
|
|
|
data (Element): XML reponse from PMS as Element
|
|
|
|
|
normally built from server.query
|
2016-12-16 23:38:08 +00:00
|
|
|
|
"""
|
2016-04-07 05:39:04 +00:00
|
|
|
|
Video._loadData(self, data)
|
|
|
|
|
Playable._loadData(self, data)
|
2017-02-04 17:43:50 +00:00
|
|
|
|
self.art = data.attrib.get('art')
|
2016-12-16 23:38:08 +00:00
|
|
|
|
self.audienceRating = utils.cast(
|
2017-02-04 17:43:50 +00:00
|
|
|
|
float, data.attrib.get('audienceRating'))
|
|
|
|
|
self.audienceRatingImage = data.attrib.get('audienceRatingImage')
|
|
|
|
|
self.chapterSource = data.attrib.get('chapterSource')
|
|
|
|
|
self.contentRating = data.attrib.get('contentRating')
|
|
|
|
|
self.duration = utils.cast(int, data.attrib.get('duration'))
|
|
|
|
|
self.guid = data.attrib.get('guid')
|
|
|
|
|
self.originalTitle = data.attrib.get('originalTitle')
|
2016-12-16 23:38:08 +00:00
|
|
|
|
self.originallyAvailableAt = utils.toDatetime(
|
2017-02-04 17:43:50 +00:00
|
|
|
|
data.attrib.get('originallyAvailableAt'), '%Y-%m-%d')
|
|
|
|
|
self.primaryExtraKey = data.attrib.get('primaryExtraKey')
|
|
|
|
|
self.rating = data.attrib.get('rating')
|
|
|
|
|
self.ratingImage = data.attrib.get('ratingImage')
|
|
|
|
|
self.studio = data.attrib.get('studio')
|
|
|
|
|
self.tagline = data.attrib.get('tagline')
|
|
|
|
|
self.userRating = utils.cast(float, data.attrib.get('userRating'))
|
2016-03-21 04:26:02 +00:00
|
|
|
|
self.viewOffset = utils.cast(int, data.attrib.get('viewOffset', 0))
|
2017-02-04 17:43:50 +00:00
|
|
|
|
self.year = utils.cast(int, data.attrib.get('year'))
|
2016-12-16 23:38:08 +00:00
|
|
|
|
if self.isFullObject(): # check this
|
2017-02-02 03:53:05 +00:00
|
|
|
|
self.collections = [media.Collection(self.server, e) for e in data if e.tag == media.Collection.TYPE]
|
|
|
|
|
self.countries = [media.Country(self.server, e) for e in data if e.tag == media.Country.TYPE]
|
|
|
|
|
self.directors = [media.Director(self.server, e) for e in data if e.tag == media.Director.TYPE]
|
|
|
|
|
self.genres = [media.Genre(self.server, e) for e in data if e.tag == media.Genre.TYPE]
|
|
|
|
|
self.media = [media.Media(self.server, e, self.initpath, self) for e in data if e.tag == media.Media.TYPE]
|
|
|
|
|
self.producers = [media.Producer(self.server, e) for e in data if e.tag == media.Producer.TYPE]
|
|
|
|
|
self.roles = [media.Role(self.server, e) for e in data if e.tag == media.Role.TYPE]
|
|
|
|
|
self.writers = [media.Writer(self.server, e) for e in data if e.tag == media.Writer.TYPE]
|
|
|
|
|
self.fields = [media.Field(e) for e in data if e.tag == media.Field.TYPE]
|
2016-04-04 03:17:29 +00:00
|
|
|
|
self.videoStreams = utils.findStreams(self.media, 'videostream')
|
|
|
|
|
self.audioStreams = utils.findStreams(self.media, 'audiostream')
|
2016-12-16 23:38:08 +00:00
|
|
|
|
self.subtitleStreams = utils.findStreams(
|
|
|
|
|
self.media, 'subtitlestream')
|
|
|
|
|
|
2016-03-21 04:26:02 +00:00
|
|
|
|
@property
|
|
|
|
|
def actors(self):
|
|
|
|
|
return self.roles
|
2016-12-16 23:38:08 +00:00
|
|
|
|
|
2016-03-21 04:26:02 +00:00
|
|
|
|
@property
|
2016-03-22 03:12:12 +00:00
|
|
|
|
def isWatched(self):
|
2016-03-21 04:26:02 +00:00
|
|
|
|
return bool(self.viewCount > 0)
|
2014-12-29 03:21:58 +00:00
|
|
|
|
|
2017-01-09 14:21:54 +00:00
|
|
|
|
@property
|
|
|
|
|
def location(self):
|
|
|
|
|
""" This does not exist in plex xml response but is added to have a common
|
|
|
|
|
interface to get the location of the Movie/Show/Episode
|
|
|
|
|
"""
|
|
|
|
|
files = [i.file for i in self.iterParts() if i]
|
|
|
|
|
if len(files) == 1:
|
|
|
|
|
files = files[0]
|
|
|
|
|
|
|
|
|
|
return files
|
|
|
|
|
|
|
|
|
|
def download(self, savepath=None, keep_orginal_name=False, **kwargs):
|
|
|
|
|
downloaded = []
|
|
|
|
|
locs = [i for i in self.iterParts() if i]
|
|
|
|
|
for loc in locs:
|
|
|
|
|
if keep_orginal_name is False:
|
|
|
|
|
name = '%s.%s' % (self.title.replace(' ', '.'), loc.container)
|
|
|
|
|
else:
|
|
|
|
|
name = loc.file
|
|
|
|
|
# So this seems to be a alot slower but allows transcode.
|
|
|
|
|
if kwargs:
|
|
|
|
|
download_url = self.getStreamURL(**kwargs)
|
|
|
|
|
else:
|
|
|
|
|
download_url = self.server.url('%s?download=1' % loc.key)
|
|
|
|
|
dl = utils.download(download_url, filename=name, savepath=savepath, session=self.server.session)
|
|
|
|
|
if dl:
|
|
|
|
|
downloaded.append(dl)
|
|
|
|
|
return downloaded
|
|
|
|
|
|
2014-12-29 03:21:58 +00:00
|
|
|
|
|
2016-03-17 04:51:20 +00:00
|
|
|
|
@utils.register_libtype
|
2014-12-29 03:21:58 +00:00
|
|
|
|
class Show(Video):
|
|
|
|
|
TYPE = 'show'
|
2015-02-24 03:42:29 +00:00
|
|
|
|
|
2014-12-29 03:21:58 +00:00
|
|
|
|
def _loadData(self, data):
|
2016-12-16 23:38:08 +00:00
|
|
|
|
"""Used to set the attributes
|
2017-01-02 21:06:40 +00:00
|
|
|
|
|
2016-12-16 23:38:08 +00:00
|
|
|
|
Args:
|
|
|
|
|
data (Element): Usually built from server.query
|
|
|
|
|
"""
|
2016-04-07 05:39:04 +00:00
|
|
|
|
Video._loadData(self, data)
|
2017-01-09 14:21:54 +00:00
|
|
|
|
# Incase this was loaded from search etc
|
|
|
|
|
self.key = self.key.replace('/children', '')
|
2017-02-04 17:43:50 +00:00
|
|
|
|
self.art = data.attrib.get('art')
|
|
|
|
|
self.banner = data.attrib.get('banner')
|
|
|
|
|
self.childCount = utils.cast(int, data.attrib.get('childCount'))
|
|
|
|
|
self.contentRating = data.attrib.get('contentRating')
|
|
|
|
|
self.duration = utils.cast(int, data.attrib.get('duration'))
|
|
|
|
|
self.guid = data.attrib.get('guid')
|
|
|
|
|
self.index = data.attrib.get('index')
|
|
|
|
|
self.leafCount = utils.cast(int, data.attrib.get('leafCount'))
|
|
|
|
|
self.location = utils.findLocations(data, single=True) or None
|
2016-12-16 23:38:08 +00:00
|
|
|
|
self.originallyAvailableAt = utils.toDatetime(
|
2017-02-04 17:43:50 +00:00
|
|
|
|
data.attrib.get('originallyAvailableAt'), '%Y-%m-%d')
|
|
|
|
|
self.rating = utils.cast(float, data.attrib.get('rating'))
|
|
|
|
|
self.studio = data.attrib.get('studio')
|
|
|
|
|
self.theme = data.attrib.get('theme')
|
2016-12-16 23:38:08 +00:00
|
|
|
|
self.viewedLeafCount = utils.cast(
|
2017-02-04 17:43:50 +00:00
|
|
|
|
int, data.attrib.get('viewedLeafCount'))
|
|
|
|
|
self.year = utils.cast(int, data.attrib.get('year'))
|
2017-01-09 14:21:54 +00:00
|
|
|
|
if self.isFullObject(): # will be fixed with docs.
|
2017-02-02 06:32:38 +00:00
|
|
|
|
self.genres = [media.Genre(self.server, e) for e in data if e.tag == media.Genre.TYPE]
|
|
|
|
|
self.roles = [media.Role(self.server, e) for e in data if e.tag == media.Role.TYPE]
|
2016-03-21 04:26:02 +00:00
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def actors(self):
|
|
|
|
|
return self.roles
|
2016-12-16 23:38:08 +00:00
|
|
|
|
|
2016-03-21 04:26:02 +00:00
|
|
|
|
@property
|
2016-03-22 03:12:12 +00:00
|
|
|
|
def isWatched(self):
|
2016-03-21 04:26:02 +00:00
|
|
|
|
return bool(self.viewedLeafCount == self.leafCount)
|
2014-12-29 03:21:58 +00:00
|
|
|
|
|
|
|
|
|
def seasons(self):
|
2017-01-02 21:06:40 +00:00
|
|
|
|
"""Returns a list of Season."""
|
2014-12-29 03:21:58 +00:00
|
|
|
|
path = '/library/metadata/%s/children' % self.ratingKey
|
2016-03-21 04:26:02 +00:00
|
|
|
|
return utils.listItems(self.server, path, Season.TYPE)
|
2014-12-29 03:21:58 +00:00
|
|
|
|
|
2017-01-04 20:38:04 +00:00
|
|
|
|
def season(self, title=None):
|
2017-01-02 21:06:40 +00:00
|
|
|
|
"""Returns a Season
|
|
|
|
|
|
|
|
|
|
Args:
|
2017-01-04 20:38:04 +00:00
|
|
|
|
title (str, int): fx Season 1
|
2017-01-02 21:06:40 +00:00
|
|
|
|
"""
|
2017-01-05 21:58:43 +00:00
|
|
|
|
if isinstance(title, int):
|
|
|
|
|
title = 'Season %s' % title
|
2017-01-04 20:38:04 +00:00
|
|
|
|
|
2014-12-29 03:21:58 +00:00
|
|
|
|
path = '/library/metadata/%s/children' % self.ratingKey
|
2016-03-21 04:26:02 +00:00
|
|
|
|
return utils.findItem(self.server, path, title)
|
2014-12-29 03:21:58 +00:00
|
|
|
|
|
2015-06-02 02:55:20 +00:00
|
|
|
|
def episodes(self, watched=None):
|
2017-01-02 21:06:40 +00:00
|
|
|
|
"""Returs a list of Episode
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
watched (bool): Defaults to None. Exclude watched episodes
|
|
|
|
|
"""
|
2014-12-29 03:21:58 +00:00
|
|
|
|
leavesKey = '/library/metadata/%s/allLeaves' % self.ratingKey
|
2016-03-21 04:26:02 +00:00
|
|
|
|
return utils.listItems(self.server, leavesKey, watched=watched)
|
2014-12-29 03:21:58 +00:00
|
|
|
|
|
2017-01-04 20:38:04 +00:00
|
|
|
|
def episode(self, title=None, season=None, episode=None):
|
2017-01-05 21:58:43 +00:00
|
|
|
|
"""Find a episode using a title or season and episode.
|
|
|
|
|
|
|
|
|
|
Note:
|
|
|
|
|
Both season and episode is required if title is missing.
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
title (str): Default None
|
|
|
|
|
season (int): Season number, default None
|
|
|
|
|
episode (int): Episode number, default None
|
|
|
|
|
|
|
|
|
|
Raises:
|
|
|
|
|
ValueError: If season and episode is missing.
|
|
|
|
|
NotFound: If the episode is missing.
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
Episode
|
|
|
|
|
|
|
|
|
|
Examples:
|
|
|
|
|
>>> plex.search('The blacklist')[0].episode(season=1, episode=1)
|
|
|
|
|
<Episode:116263:The.Freelancer>
|
|
|
|
|
>>> plex.search('The blacklist')[0].episode('The Freelancer')
|
|
|
|
|
<Episode:116263:The.Freelancer>
|
|
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
if not title and (not season or not episode):
|
|
|
|
|
raise TypeError('Missing argument: title or season and episode are required')
|
|
|
|
|
|
2017-01-04 20:38:04 +00:00
|
|
|
|
if title:
|
|
|
|
|
path = '/library/metadata/%s/allLeaves' % self.ratingKey
|
|
|
|
|
return utils.findItem(self.server, path, title)
|
|
|
|
|
|
|
|
|
|
elif season and episode:
|
|
|
|
|
results = [i for i in self.episodes()
|
|
|
|
|
if i.seasonNumber == season and i.index == episode]
|
|
|
|
|
if results:
|
|
|
|
|
return results[0]
|
2017-01-05 21:58:43 +00:00
|
|
|
|
else:
|
|
|
|
|
raise NotFound('Couldnt find %s S%s E%s' % (self.title, season, episode))
|
2014-12-29 03:21:58 +00:00
|
|
|
|
|
2015-06-02 02:55:20 +00:00
|
|
|
|
def watched(self):
|
2017-01-02 21:06:40 +00:00
|
|
|
|
"""Return a list of watched episodes"""
|
2015-06-02 02:55:20 +00:00
|
|
|
|
return self.episodes(watched=True)
|
|
|
|
|
|
|
|
|
|
def unwatched(self):
|
2017-01-02 21:06:40 +00:00
|
|
|
|
"""Return a list of unwatched episodes"""
|
2015-06-02 02:55:20 +00:00
|
|
|
|
return self.episodes(watched=False)
|
|
|
|
|
|
2014-12-29 03:21:58 +00:00
|
|
|
|
def get(self, title):
|
2017-01-02 21:06:40 +00:00
|
|
|
|
"""Get a Episode with a title.
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
title (str): fx Secret santa
|
|
|
|
|
"""
|
2014-12-29 03:21:58 +00:00
|
|
|
|
return self.episode(title)
|
|
|
|
|
|
2017-01-09 14:21:54 +00:00
|
|
|
|
def analyze(self):
|
|
|
|
|
""" """
|
|
|
|
|
raise 'Cant analyse a show' # fix me
|
|
|
|
|
|
2015-06-02 02:27:43 +00:00
|
|
|
|
def refresh(self):
|
2017-01-02 21:06:40 +00:00
|
|
|
|
"""Refresh the metadata."""
|
2017-01-09 14:21:54 +00:00
|
|
|
|
self.server.query('/library/metadata/%s/refresh' % self.ratingKey, method=self.server.session.put)
|
|
|
|
|
|
|
|
|
|
def download(self, savepath=None, keep_orginal_name=False, **kwargs):
|
|
|
|
|
downloaded = []
|
|
|
|
|
for ep in self.episodes():
|
|
|
|
|
dl = ep.download(savepath=savepath, keep_orginal_name=keep_orginal_name, **kwargs)
|
|
|
|
|
if dl:
|
|
|
|
|
downloaded.extend(dl)
|
|
|
|
|
return downloaded
|
2015-06-02 02:27:43 +00:00
|
|
|
|
|
2014-12-29 03:21:58 +00:00
|
|
|
|
|
2016-03-17 04:51:20 +00:00
|
|
|
|
@utils.register_libtype
|
2014-12-29 03:21:58 +00:00
|
|
|
|
class Season(Video):
|
|
|
|
|
TYPE = 'season'
|
|
|
|
|
|
|
|
|
|
def _loadData(self, data):
|
2016-12-16 23:38:08 +00:00
|
|
|
|
"""Used to set the attributes
|
2017-01-02 21:06:40 +00:00
|
|
|
|
|
2016-12-16 23:38:08 +00:00
|
|
|
|
Args:
|
|
|
|
|
data (Element): Usually built from server.query
|
|
|
|
|
"""
|
2016-04-07 05:39:04 +00:00
|
|
|
|
Video._loadData(self, data)
|
2017-01-09 14:21:54 +00:00
|
|
|
|
self.key = self.key.replace('/children', '')
|
2017-02-04 17:43:50 +00:00
|
|
|
|
self.leafCount = utils.cast(int, data.attrib.get('leafCount'))
|
|
|
|
|
self.index = utils.cast(int, data.attrib.get('index'))
|
|
|
|
|
self.parentKey = data.attrib.get('parentKey')
|
|
|
|
|
self.parentRatingKey = utils.cast(int, data.attrib.get('parentRatingKey'))
|
|
|
|
|
self.parentTitle = data.attrib.get('parentTitle')
|
2016-12-16 23:38:08 +00:00
|
|
|
|
self.viewedLeafCount = utils.cast(
|
2017-02-04 17:43:50 +00:00
|
|
|
|
int, data.attrib.get('viewedLeafCount'))
|
2016-12-16 23:38:08 +00:00
|
|
|
|
|
2016-03-21 04:26:02 +00:00
|
|
|
|
@property
|
2016-03-22 03:12:12 +00:00
|
|
|
|
def isWatched(self):
|
2016-03-21 04:26:02 +00:00
|
|
|
|
return bool(self.viewedLeafCount == self.leafCount)
|
2014-12-29 03:21:58 +00:00
|
|
|
|
|
2016-05-20 03:42:06 +00:00
|
|
|
|
@property
|
|
|
|
|
def seasonNumber(self):
|
2017-01-09 14:21:54 +00:00
|
|
|
|
"""Returns season number."""
|
2016-05-20 03:42:06 +00:00
|
|
|
|
return self.index
|
|
|
|
|
|
2015-06-02 02:55:20 +00:00
|
|
|
|
def episodes(self, watched=None):
|
2017-01-02 21:06:40 +00:00
|
|
|
|
"""Returs a list of Episode
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
watched (bool): Defaults to None. Exclude watched episodes
|
2017-01-02 21:19:07 +00:00
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
list: of Episode
|
|
|
|
|
|
2017-01-04 20:38:04 +00:00
|
|
|
|
|
2016-12-16 23:38:08 +00:00
|
|
|
|
"""
|
2014-12-29 03:21:58 +00:00
|
|
|
|
childrenKey = '/library/metadata/%s/children' % self.ratingKey
|
2016-03-21 04:26:02 +00:00
|
|
|
|
return utils.listItems(self.server, childrenKey, watched=watched)
|
2014-12-29 03:21:58 +00:00
|
|
|
|
|
2017-01-05 21:58:43 +00:00
|
|
|
|
def episode(self, title=None, episode=None):
|
|
|
|
|
"""Find a episode using a title or season and episode.
|
2017-01-02 21:06:40 +00:00
|
|
|
|
|
2017-01-05 21:58:43 +00:00
|
|
|
|
Note:
|
|
|
|
|
episode is required if title is missing.
|
2017-01-02 21:06:40 +00:00
|
|
|
|
|
2017-01-05 21:58:43 +00:00
|
|
|
|
Args:
|
|
|
|
|
title (str): Default None
|
|
|
|
|
episode (int): Episode number, default None
|
|
|
|
|
|
|
|
|
|
Raises:
|
|
|
|
|
TypeError: If title and episode is missing.
|
|
|
|
|
NotFound: If that episode cant be found.
|
|
|
|
|
|
|
|
|
|
Returns:
|
2017-01-02 21:06:40 +00:00
|
|
|
|
Episode
|
2017-01-05 21:58:43 +00:00
|
|
|
|
|
|
|
|
|
Examples:
|
|
|
|
|
>>> plex.search('The blacklist').season(1).episode(episode=1)
|
|
|
|
|
<Episode:116263:The.Freelancer>
|
|
|
|
|
>>> plex.search('The blacklist').season(1).episode('The Freelancer')
|
|
|
|
|
<Episode:116263:The.Freelancer>
|
|
|
|
|
|
2016-12-16 23:38:08 +00:00
|
|
|
|
"""
|
2017-01-05 21:58:43 +00:00
|
|
|
|
if not title and not episode:
|
|
|
|
|
raise TypeError('Missing argument, you need to use title or episode.')
|
2017-01-04 20:38:04 +00:00
|
|
|
|
if title:
|
|
|
|
|
path = '/library/metadata/%s/children' % self.ratingKey
|
|
|
|
|
return utils.findItem(self.server, path, title)
|
2017-01-05 21:58:43 +00:00
|
|
|
|
elif episode:
|
2017-02-02 03:53:05 +00:00
|
|
|
|
results = [i for i in self.episodes() if i.seasonNumber == self.index and i.index == episode]
|
2017-01-04 20:38:04 +00:00
|
|
|
|
if results:
|
|
|
|
|
return results[0]
|
2017-02-02 03:53:05 +00:00
|
|
|
|
raise NotFound('Couldnt find %s.Season %s Episode %s.' % (self.grandparentTitle, self.index. episode))
|
2017-01-04 20:38:04 +00:00
|
|
|
|
|
2014-12-29 03:21:58 +00:00
|
|
|
|
def get(self, title):
|
2017-01-05 21:58:43 +00:00
|
|
|
|
"""Get a episode with a matching title.
|
2017-01-02 21:06:40 +00:00
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
title (str): fx Secret santa
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
Episode
|
|
|
|
|
"""
|
2014-12-29 03:21:58 +00:00
|
|
|
|
return self.episode(title)
|
|
|
|
|
|
|
|
|
|
def show(self):
|
2017-01-02 21:06:40 +00:00
|
|
|
|
"""Return this seasons show."""
|
2016-03-21 04:26:02 +00:00
|
|
|
|
return utils.listItems(self.server, self.parentKey)[0]
|
2014-12-29 03:21:58 +00:00
|
|
|
|
|
2015-06-02 02:55:20 +00:00
|
|
|
|
def watched(self):
|
2017-01-02 21:06:40 +00:00
|
|
|
|
"""Returns a list of watched Episode"""
|
2015-06-02 02:55:20 +00:00
|
|
|
|
return self.episodes(watched=True)
|
|
|
|
|
|
|
|
|
|
def unwatched(self):
|
2017-01-02 21:06:40 +00:00
|
|
|
|
"""Returns a list of unwatched Episode"""
|
2015-06-02 02:55:20 +00:00
|
|
|
|
return self.episodes(watched=False)
|
|
|
|
|
|
2017-01-05 21:58:43 +00:00
|
|
|
|
def __repr__(self):
|
|
|
|
|
clsname = self.__class__.__name__
|
|
|
|
|
key = self.key.replace('/library/metadata/', '').replace('/children', '') if self.key else 'NA'
|
|
|
|
|
title = self.title.replace(' ', '.')[0:20].encode('utf8')
|
2017-01-09 14:21:54 +00:00
|
|
|
|
return '<%s:%s:%s:%s>' % (clsname, key, self.parentTitle, title)
|
|
|
|
|
|
|
|
|
|
def download(self, savepath=None, keep_orginal_name=False, **kwargs):
|
|
|
|
|
downloaded = []
|
|
|
|
|
for ep in self.episodes():
|
|
|
|
|
dl = ep.download(savepath=savepath, keep_orginal_name=keep_orginal_name, **kwargs)
|
|
|
|
|
if dl:
|
|
|
|
|
downloaded.extend(dl)
|
|
|
|
|
return downloaded
|
2017-01-05 21:58:43 +00:00
|
|
|
|
|
2014-12-29 03:21:58 +00:00
|
|
|
|
|
2016-03-17 04:51:20 +00:00
|
|
|
|
@utils.register_libtype
|
2016-04-04 03:55:29 +00:00
|
|
|
|
class Episode(Video, Playable):
|
2014-12-29 03:21:58 +00:00
|
|
|
|
TYPE = 'episode'
|
|
|
|
|
|
|
|
|
|
def _loadData(self, data):
|
2017-01-02 21:06:40 +00:00
|
|
|
|
"""Used to set the attributes
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
data (Element): Usually built from server.query
|
|
|
|
|
"""
|
2016-04-07 05:39:04 +00:00
|
|
|
|
Video._loadData(self, data)
|
|
|
|
|
Playable._loadData(self, data)
|
2017-02-04 17:43:50 +00:00
|
|
|
|
self.art = data.attrib.get('art')
|
|
|
|
|
self.chapterSource = data.attrib.get('chapterSource')
|
|
|
|
|
self.contentRating = data.attrib.get('contentRating')
|
|
|
|
|
self.duration = utils.cast(int, data.attrib.get('duration'))
|
|
|
|
|
self.grandparentArt = data.attrib.get('grandparentArt')
|
|
|
|
|
self.grandparentKey = data.attrib.get('grandparentKey')
|
|
|
|
|
self.grandparentRatingKey = utils.cast(int, data.attrib.get('grandparentRatingKey'))
|
|
|
|
|
self.grandparentTheme = data.attrib.get('grandparentTheme')
|
|
|
|
|
self.grandparentThumb = data.attrib.get('grandparentThumb')
|
|
|
|
|
self.grandparentTitle = data.attrib.get('grandparentTitle')
|
|
|
|
|
self.guid = data.attrib.get('guid')
|
|
|
|
|
self.index = utils.cast(int, data.attrib.get('index'))
|
|
|
|
|
self.originallyAvailableAt = utils.toDatetime(data.attrib.get('originallyAvailableAt'), '%Y-%m-%d')
|
|
|
|
|
self.parentIndex = data.attrib.get('parentIndex')
|
|
|
|
|
self.parentKey = data.attrib.get('parentKey')
|
|
|
|
|
self.parentRatingKey = utils.cast(int, data.attrib.get('parentRatingKey'))
|
|
|
|
|
self.parentThumb = data.attrib.get('parentThumb')
|
|
|
|
|
self.rating = utils.cast(float, data.attrib.get('rating'))
|
2016-03-17 04:51:20 +00:00
|
|
|
|
self.viewOffset = utils.cast(int, data.attrib.get('viewOffset', 0))
|
2017-02-04 17:43:50 +00:00
|
|
|
|
self.year = utils.cast(int, data.attrib.get('year'))
|
2017-02-02 03:53:05 +00:00
|
|
|
|
self.directors = [media.Director(self.server, e) for e in data if e.tag == media.Director.TYPE]
|
|
|
|
|
self.media = [media.Media(self.server, e, self.initpath, self) for e in data if e.tag == media.Media.TYPE]
|
|
|
|
|
self.writers = [media.Writer(self.server, e) for e in data if e.tag == media.Writer.TYPE]
|
2016-12-16 23:38:08 +00:00
|
|
|
|
self.videoStreams = utils.findStreams(self.media, 'videostream')
|
|
|
|
|
self.audioStreams = utils.findStreams(self.media, 'audiostream')
|
|
|
|
|
self.subtitleStreams = utils.findStreams(self.media, 'subtitlestream')
|
2016-04-07 05:39:04 +00:00
|
|
|
|
# data for active sessions and history
|
2017-02-04 17:43:50 +00:00
|
|
|
|
self.sessionKey = utils.cast(int, data.attrib.get('sessionKey'))
|
2016-04-07 05:39:04 +00:00
|
|
|
|
self.username = utils.findUsername(data)
|
2016-04-04 03:17:29 +00:00
|
|
|
|
self.player = utils.findPlayer(self.server, data)
|
|
|
|
|
self.transcodeSession = utils.findTranscodeSession(self.server, data)
|
2016-05-20 03:42:06 +00:00
|
|
|
|
# Cached season number
|
|
|
|
|
self._seasonNumber = None
|
2016-03-21 04:26:02 +00:00
|
|
|
|
|
2017-01-05 21:58:43 +00:00
|
|
|
|
def __repr__(self):
|
|
|
|
|
clsname = self.__class__.__name__
|
|
|
|
|
key = self.key.replace('/library/metadata/', '').replace('/children', '') if self.key else 'NA'
|
|
|
|
|
title = self.title.replace(' ', '.')[0:20].encode('utf8')
|
|
|
|
|
return '<%s:%s:%s:S%s:E%s:%s>' % (clsname, key, self.grandparentTitle, self.seasonNumber, self.index, title)
|
|
|
|
|
|
2016-03-21 04:26:02 +00:00
|
|
|
|
@property
|
2016-03-22 03:12:12 +00:00
|
|
|
|
def isWatched(self):
|
2017-01-02 21:06:40 +00:00
|
|
|
|
"""Returns True if watched, False if not."""
|
2016-03-21 04:26:02 +00:00
|
|
|
|
return bool(self.viewCount > 0)
|
2014-12-29 03:21:58 +00:00
|
|
|
|
|
2016-05-20 03:42:06 +00:00
|
|
|
|
@property
|
|
|
|
|
def seasonNumber(self):
|
2017-01-02 21:06:40 +00:00
|
|
|
|
"""Return this episode seasonnumber."""
|
2016-05-20 03:42:06 +00:00
|
|
|
|
if self._seasonNumber is None:
|
|
|
|
|
self._seasonNumber = self.parentIndex if self.parentIndex else self.season().seasonNumber
|
2017-01-04 20:38:04 +00:00
|
|
|
|
return utils.cast(int, self._seasonNumber)
|
2016-05-20 03:42:06 +00:00
|
|
|
|
|
2015-02-24 03:42:29 +00:00
|
|
|
|
@property
|
|
|
|
|
def thumbUrl(self):
|
2017-01-02 21:06:40 +00:00
|
|
|
|
"""Return url to thumb image."""
|
2017-01-02 21:19:07 +00:00
|
|
|
|
if self.grandparentThumb:
|
|
|
|
|
return self.server.url(self.grandparentThumb)
|
2015-02-24 03:42:29 +00:00
|
|
|
|
|
2014-12-29 03:21:58 +00:00
|
|
|
|
def season(self):
|
2017-01-02 21:06:40 +00:00
|
|
|
|
"""Return this episode Season"""
|
2016-03-21 04:26:02 +00:00
|
|
|
|
return utils.listItems(self.server, self.parentKey)[0]
|
2014-12-29 03:21:58 +00:00
|
|
|
|
|
|
|
|
|
def show(self):
|
2017-01-02 21:06:40 +00:00
|
|
|
|
"""Return this episodes Show"""
|
2016-03-21 04:26:02 +00:00
|
|
|
|
return utils.listItems(self.server, self.grandparentKey)[0]
|
2017-01-09 14:21:54 +00:00
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def location(self):
|
|
|
|
|
""" This does not exist in plex xml response but is added to have a common
|
|
|
|
|
interface to get the location of the Movie/Show
|
|
|
|
|
"""
|
|
|
|
|
# Note this should probably belong to some parent.
|
|
|
|
|
files = [i.file for i in self.iterParts() if i]
|
|
|
|
|
if len(files) == 1:
|
|
|
|
|
files = files[0]
|
|
|
|
|
return files
|
|
|
|
|
|
|
|
|
|
def _prettyfilename(self):
|
2017-02-02 03:53:05 +00:00
|
|
|
|
return '%s.S%sE%s' % (self.grandparentTitle.replace(' ', '.'),
|
|
|
|
|
str(self.seasonNumber).zfill(2), str(self.index).zfill(2))
|