Create registry of library items we may see in xml; Cleanup how we handle build_item and list_items

This commit is contained in:
Michael Shepanski 2016-03-17 00:51:20 -04:00
parent cc58b87c9b
commit 276ba26b77
9 changed files with 160 additions and 221 deletions

View file

@ -7,14 +7,14 @@ from platform import platform, uname
from plexapi.config import PlexConfig, reset_base_headers
from uuid import getnode
PROJECT = 'PlexAPI'
VERSION = '1.1.0'
# Load User Defined Config
CONFIG_PATH = os.path.expanduser('~/.config/plexapi/config.ini')
CONFIG = PlexConfig(CONFIG_PATH)
# Core Settings
PROJECT = 'PlexAPI'
VERSION = '1.1.0'
TIMEOUT = CONFIG.get('plexapi.timeout', 5, int)
# Plex Header Configuation

View file

@ -1,12 +1,12 @@
"""
PlexAudio
"""
from plexapi import utils
from plexapi.media import Media, Genre, Producer
from plexapi.exceptions import NotFound, UnknownType, Unsupported
from plexapi.exceptions import Unsupported
from plexapi.utils import NA
from plexapi.utils import cast, toDatetime
from plexapi.utils import cast, toDatetime, register_libtype
from plexapi.video import Video # TODO: remove this when Audio class can stand on its own legs
try:
from urllib import urlencode # Python2
except ImportError:
@ -67,6 +67,7 @@ class Audio(Video): # TODO: inherit from PlexPartialObject, like the Video clas
self._loadData(data[0])
@register_libtype
class Artist(Audio):
TYPE = 'artist'
@ -88,19 +89,19 @@ class Artist(Audio):
def albums(self):
path = '/library/metadata/%s/children' % self.ratingKey
return list_items(self.server, path, Album.TYPE)
return utils.list_items(self.server, path, Album.TYPE)
def album(self, title):
path = '/library/metadata/%s/children' % self.ratingKey
return find_item(self.server, path, title)
return utils.find_item(self.server, path, title)
def tracks(self, watched=None):
leavesKey = '/library/metadata/%s/allLeaves' % self.ratingKey
return list_items(self.server, leavesKey, watched=watched)
return utils.list_items(self.server, leavesKey, watched=watched)
def track(self, title):
path = '/library/metadata/%s/allLeaves' % self.ratingKey
return find_item(self.server, path, title)
return utils.find_item(self.server, path, title)
def watched(self):
return self.episodes(watched=True)
@ -115,6 +116,7 @@ class Artist(Audio):
self.server.query('/library/metadata/%s/refresh' % self.ratingKey)
@register_libtype
class Album(Audio):
TYPE = 'album'
@ -137,17 +139,17 @@ class Album(Audio):
def tracks(self, watched=None):
childrenKey = '/library/metadata/%s/children' % self.ratingKey
return list_items(self.server, childrenKey, watched=watched)
return utils.list_items(self.server, childrenKey, watched=watched)
def track(self, title):
path = '/library/metadata/%s/children' % self.ratingKey
return find_item(self.server, path, title)
return utils.find_item(self.server, path, title)
def get(self, title):
return self.track(title)
def artist(self):
return list_items(self.server, self.parentKey)[0]
return utils.list_items(self.server, self.parentKey)[0]
def watched(self):
return self.tracks(watched=True)
@ -156,6 +158,7 @@ class Album(Audio):
return self.tracks(watched=False)
@register_libtype
class Track(Audio):
TYPE = 'track'
@ -182,54 +185,8 @@ class Track(Audio):
return self.server.url(self.grandparentThumb)
def album(self):
return list_items(self.server, self.parentKey)[0]
return utils.list_items(self.server, self.parentKey)[0]
def artist(self):
raise NotImplemented
#return list_items(self.server, self.grandparentKey)[0]
def build_item(server, elem, initpath):
AUDIOCLS = {Artist.TYPE:Artist, Album.TYPE:Album, Track.TYPE:Track}
atype = elem.attrib.get('type')
if atype in AUDIOCLS:
cls = AUDIOCLS[atype]
return cls(server, elem, initpath)
raise UnknownType('Unknown audio type: %s' % atype)
def find_key(server, key):
path = '/library/metadata/{0}'.format(key)
try:
# Video seems to be the first sub element
elem = server.query(path)[0]
return build_item(server, elem, path)
except:
raise NotFound('Unable to find key: %s' % key)
def find_item(server, path, title):
for elem in server.query(path):
if elem.attrib.get('title').lower() == title.lower():
return build_item(server, elem, path)
raise NotFound('Unable to find title: %s' % title)
def list_items(server, path, audiotype=None, watched=None):
items = []
for elem in server.query(path):
if audiotype and elem.attrib.get('type') != audiotype: continue
if watched is True and elem.attrib.get('viewCount', 0) == 0: continue
if watched is False and elem.attrib.get('viewCount', 0) >= 1: continue
try:
items.append(build_item(server, elem, path))
except UnknownType:
pass
return items
def search_type(audiotype):
if audiotype == Artist.TYPE: return 8
elif audiotype == Album.TYPE: return 9
elif audiotype == Track.TYPE: return 10
raise NotFound('Unknown audiotype: %s' % audiotype)

View file

@ -1,10 +1,8 @@
"""
PlexLibrary
"""
from plexapi import video, audio, utils
from plexapi import utils
from plexapi.exceptions import NotFound
from threading import Thread
from Queue import Queue
class Library(object):
@ -41,44 +39,28 @@ class Library(object):
raise NotFound('Invalid library section: %s' % title)
def all(self):
return video.list_items(self.server, '/library/all')
return utils.list_items(self.server, '/library/all')
def onDeck(self):
return video.list_items(self.server, '/library/onDeck')
return utils.list_items(self.server, '/library/onDeck')
def recentlyAdded(self):
return video.list_items(self.server, '/library/recentlyAdded')
return utils.list_items(self.server, '/library/recentlyAdded')
def get(self, title):
return video.find_item(self.server, '/library/all', title)
return utils.find_item(self.server, '/library/all', title)
def getByKey(self, key):
return video.find_key(self.server, key)
return utils.find_key(self.server, key)
def search(self, title, prefilter='all', mtype=None, **tags):
# TODO: Handle tags much better.
_audio = lambda: self._search(audio, title, prefilter, mtype, **tags)
_video = lambda: self._search(video, title, prefilter, mtype, **tags)
results = []
qresults = utils.threaded([_audio, _video])
for item in iter(qresults.get_nowait, None):
results += item
return results
def _search(self, module, title, prefilter='all', mtype=None, **tags):
""" Search audio content.
title: Title to search (pass None to search all titles).
prefilter: One of {'all', 'onDeck', 'recentlyAdded'}.
mtype: One of {'artist', 'album', 'track', 'movie', 'show', 'season', 'episode'}.
tags: One of {country, director, genre, producer, actor, writer}.
"""
def search(self, title, prefilter='all', libtype=None, **tags):
args = {}
if title: args['title'] = title
if mtype: args['type'] = module.search_type(mtype)
if libtype: args['type'] = utils.search_type(libtype)
for tag, obj in tags.items():
args[tag] = obj.id
query = '/library/%s%s' % (prefilter, utils.joinArgs(args))
return module.list_items(self.server, query)
return utils.list_items(self.server, query)
def cleanBundles(self):
self.server.query('/library/clean/bundles')
@ -110,13 +92,13 @@ class LibrarySection(object):
return '<%s:%s>' % (self.__class__.__name__, title.encode('utf8'))
def _primary_list(self, key):
return video.list_items(self.server, '/library/sections/%s/%s' % (self.key, key))
return utils.list_items(self.server, '/library/sections/%s/%s' % (self.key, key))
def _secondary_list(self, key, input=None):
choices = list_choices(self.server, '/library/sections/%s/%s' % (self.key, key))
if not input:
return list(choices.keys())
return video.list_items(self.server, '/library/sections/%s/%s/%s' % (self.key, key, choices[input]))
return utils.list_items(self.server, '/library/sections/%s/%s/%s' % (self.key, key, choices[input]))
def all(self):
return self._primary_list('all')
@ -150,7 +132,7 @@ class LibrarySection(object):
def get(self, title):
path = '/library/sections/%s/all' % self.key
return video.find_item(self.server, path, title)
return utils.find_item(self.server, path, title)
def search(self, title, filter='all', vtype=None, **tags):
""" Search section content.
@ -161,11 +143,11 @@ class LibrarySection(object):
"""
args = {}
if title: args['title'] = title
if vtype: args['type'] = video.search_type(vtype)
if vtype: args['type'] = utils.search_type(vtype)
for tag, obj in tags.items():
args[tag] = obj.id
query = '/library/sections/%s/%s%s' % (self.key, filter, utils.joinArgs(args))
return video.list_items(self.server, query)
return utils.list_items(self.server, query)
def analyze(self):
self.server.query('/library/sections/%s/analyze' % self.key)
@ -199,7 +181,7 @@ class MovieSection(LibrarySection):
return self._secondary_list('resolution', input)
def search(self, title, filter='all', **tags):
return super(MovieSection, self).search(title, filter=filter, vtype=video.Movie.TYPE, **tags)
return super(MovieSection, self).search(title, filter=filter, vtype='movie', **tags)
class ShowSection(LibrarySection):
@ -209,10 +191,10 @@ class ShowSection(LibrarySection):
return self._primary_list('recentlyViewedShows')
def search(self, title, filter='all', **tags):
return super(ShowSection, self).search(title, filter=filter, vtype=video.Show.TYPE, **tags)
return super(ShowSection, self).search(title, filter=filter, vtype='show', **tags)
def searchEpisodes(self, title, filter='all', **tags):
return super(ShowSection, self).search(title, filter=filter, vtype=video.Episode.TYPE, **tags)
return super(ShowSection, self).search(title, filter=filter, vtype='episode', **tags)
class MusicSection(LibrarySection):
@ -227,23 +209,23 @@ class MusicSection(LibrarySection):
"""
args = {}
if title: args['title'] = title
if atype: args['type'] = audio.search_type(atype)
if atype: args['type'] = utils.search_type(atype)
for tag, obj in tags.items():
args[tag] = obj.id
query = '/library/sections/%s/%s%s' % (self.key, filter, utils.joinArgs(args))
return audio.list_items(self.server, query)
return utils.list_items(self.server, query)
def recentlyViewedShows(self):
return self._primary_list('recentlyViewedShows')
def searchArtists(self, title, filter='all', **tags):
return self.search(title, filter=filter, atype=audio.Artist.TYPE, **tags)
return self.search(title, filter=filter, atype='artist', **tags)
def searchAlbums(self, title, filter='all', **tags):
return self.search(title, filter=filter, atype=audio.Album.TYPE, **tags)
return self.search(title, filter=filter, atype='album', **tags)
def searchTracks(self, title, filter='all', **tags):
return self.search(title, filter=filter, atype=audio.Track.TYPE, **tags)
return self.search(title, filter=filter, atype='track', **tags)
def list_choices(server, path):

View file

@ -2,7 +2,6 @@
PlexAPI Play PlayQueues
"""
import plexapi, requests
from plexapi import video
from plexapi import utils
@ -19,7 +18,7 @@ class PlayQueue(object):
self.playQueueSelectedItemOffset = data.attrib.get('playQueueSelectedItemOffset')
self.playQueueTotalCount = data.attrib.get('playQueueTotalCount')
self.playQueueVersion = data.attrib.get('playQueueVersion')
self.items = [video.build_item(server, elem, initpath) for elem in data]
self.items = [utils.build_item(server, elem, initpath) for elem in data]
@classmethod
def create(cls, server, video, shuffle=0, continuous=0):

View file

@ -4,7 +4,7 @@ PlexServer
import requests
from requests.status_codes import _codes as codes
from plexapi import BASE_HEADERS, TIMEOUT
from plexapi import log, audio, video, utils
from plexapi import log, utils
from plexapi.client import Client
from plexapi.exceptions import BadRequest, NotFound
from plexapi.library import Library
@ -12,7 +12,6 @@ from plexapi.myplex import MyPlexAccount
from plexapi.playqueue import PlayQueue
from xml.etree import ElementTree
try:
from urllib import quote # Python2
except ImportError:
@ -95,24 +94,13 @@ class PlexServer(object):
return ElementTree.fromstring(data) if data else None
def search(self, query, mediatype=None):
# Searching at this level is meant to be quick and dirty, if you're looking
# for more in-depth search filters look at plex.library.search().
_audio = lambda: self._search(audio, query, mediatype)
_video = lambda: self._search(video, query, mediatype)
results = []
qresults = utils.threaded([_audio, _video])
for item in iter(qresults.get_nowait, None):
results += item
return results
def _search(self, module, query, mediatype=None):
items = module.list_items(self, '/search?query=%s' % quote(query))
items = utils.list_items(self, '/search?query=%s' % quote(query))
if mediatype:
return [item for item in items if item.type == mediatype]
return items
def sessions(self):
return video.list_items(self, '/status/sessions')
return utils.list_items(self, '/status/sessions')
def url(self, path):
if self.token:

View file

@ -4,11 +4,22 @@ PlexAPI Utils
from datetime import datetime
from threading import Thread
from Queue import Queue
from plexapi.exceptions import UnknownType
try:
from urllib import quote # Python2
except ImportError:
from urllib.parse import quote # Python3
# Registry of library types we may come across when parsing XML. This allows us to
# define a few helper functions to dynamically convery the XML into objects.
# see build_item() below for an example.
LIBRARY_TYPES = {}
def register_libtype(cls):
LIBRARY_TYPES[cls.TYPE] = cls
return cls
# This used to be a simple variable equal to '__NA__'. However, there has been need to
# compare NA against None in some use cases. This object allows the internals of PlexAPI
# to distinguish between unfetched values and fetched, but non-existent values.
@ -19,14 +30,12 @@ class __NA__(object):
def __nonzero__(self): return False # Python2; flake8: noqa
def __repr__(self): return '__NA__' # flake8: noqa
NA = __NA__()
# Not all objects in the Plex listings return the complete list of elements for the object.
# This object will allow you to assume each object is complete, and if the specified value
# you request is None it will fetch the full object automatically and update itself.
class PlexPartialObject(object):
""" Not all objects in the Plex listings return the complete list of
elements for the object. This object will allow you to assume each
object is complete, and if the specified value you request is None
it will fetch the full object automatically and update itself.
"""
def __init__(self, server, data, initpath):
self.server = server
@ -64,6 +73,55 @@ class PlexPartialObject(object):
self._loadData(data[0])
def build_item(server, elem, initpath):
libtype = elem.attrib.get('type')
if libtype in LIBRARY_TYPES:
cls = LIBRARY_TYPES[libtype]
return cls(server, elem, initpath)
raise UnknownType('Unknown library type: %s' % libtype)
def find_key(server, key):
path = '/library/metadata/{0}'.format(key)
try:
# Item seems to be the first sub element
elem = server.query(path)[0]
return build_item(server, elem, path)
except:
raise NotFound('Unable to find key: %s' % key)
def find_item(server, path, title):
for elem in server.query(path):
if elem.attrib.get('title').lower() == title.lower():
return build_item(server, elem, path)
raise NotFound('Unable to find item: %s' % title)
def list_items(server, path, libtype=None, watched=None):
items = []
for elem in server.query(path):
if libtype and elem.attrib.get('type') != libtype: continue
if watched is True and elem.attrib.get('viewCount', 0) == 0: continue
if watched is False and elem.attrib.get('viewCount', 0) >= 1: continue
try:
items.append(build_item(server, elem, path))
except UnknownType:
pass
return items
def search_type(libtype):
if libtype == LIBRARY_TYPES['movie'].TYPE: return 1
elif libtype == LIBRARY_TYPES['show'].TYPE: return 2
elif libtype == LIBRARY_TYPES['season'].TYPE: return 3
elif libtype == LIBRARY_TYPES['episode'].TYPE: return 4
elif libtype == LIBRARY_TYPES['artist'].TYPE: return 8
elif libtype == LIBRARY_TYPES['album'].TYPE: return 9
elif libtype == LIBRARY_TYPES['track'].TYPE: return 10
raise NotFound('Unknown libtype: %s' % libtype)
def cast(func, value):
if value not in [None, NA]:
if func == bool:

View file

@ -3,20 +3,19 @@ PlexVideo
"""
import re
from requests import put
from plexapi import media, utils
from plexapi.client import Client
from plexapi.media import Media, TranscodeSession, Country, Director, Genre, Producer, Actor, Writer
from plexapi.myplex import MyPlexUser
from plexapi.exceptions import NotFound, UnknownType, Unsupported
from plexapi.utils import PlexPartialObject, NA
from plexapi.utils import cast, toDatetime
from plexapi.exceptions import Unsupported
try:
from urllib import urlencode # Python2
except ImportError:
from urllib.parse import urlencode # Python3
NA = utils.NA
class Video(PlexPartialObject):
class Video(utils.PlexPartialObject):
TYPE = None
def _loadData(self, data):
@ -29,22 +28,22 @@ class Video(PlexPartialObject):
self.summary = data.attrib.get('summary', NA)
self.art = data.attrib.get('art', NA)
self.thumb = data.attrib.get('thumb', NA)
self.addedAt = toDatetime(data.attrib.get('addedAt', NA))
self.updatedAt = toDatetime(data.attrib.get('updatedAt', NA))
self.lastViewedAt = toDatetime(data.attrib.get('lastViewedAt', NA))
self.sessionKey = cast(int, data.attrib.get('sessionKey', NA))
self.addedAt = utils.toDatetime(data.attrib.get('addedAt', NA))
self.updatedAt = utils.toDatetime(data.attrib.get('updatedAt', NA))
self.lastViewedAt = utils.toDatetime(data.attrib.get('lastViewedAt', NA))
self.sessionKey = utils.cast(int, data.attrib.get('sessionKey', NA))
self.user = self._find_user(data) # for active sessions
self.player = self._find_player(data) # for active sessions
self.transcodeSession = self._find_transcodeSession(data)
if self.isFullObject():
# These are auto-populated when requested
self.media = [Media(self.server, elem, self.initpath, self) for elem in data if elem.tag == Media.TYPE]
self.countries = [Country(self.server, elem) for elem in data if elem.tag == Country.TYPE]
self.directors = [Director(self.server, elem) for elem in data if elem.tag == Director.TYPE]
self.genres = [Genre(self.server, elem) for elem in data if elem.tag == Genre.TYPE]
self.producers = [Producer(self.server, elem) for elem in data if elem.tag == Producer.TYPE]
self.actors = [Actor(self.server, elem) for elem in data if elem.tag == Actor.TYPE]
self.writers = [Writer(self.server, elem) for elem in data if elem.tag == Writer.TYPE]
self.media = [media.Media(self.server, elem, self.initpath, self) for elem in data if elem.tag == media.Media.TYPE]
self.countries = [media.Country(self.server, elem) for elem in data if elem.tag == media.Country.TYPE]
self.directors = [media.Director(self.server, elem) for elem in data if elem.tag == media.Director.TYPE]
self.genres = [media.Genre(self.server, elem) for elem in data if elem.tag == media.Genre.TYPE]
self.producers = [media.Producer(self.server, elem) for elem in data if elem.tag == media.Producer.TYPE]
self.actors = [media.Actor(self.server, elem) for elem in data if elem.tag == media.Actor.TYPE]
self.writers = [media.Writer(self.server, elem) for elem in data if elem.tag == media.Writer.TYPE]
@property
def thumbUrl(self):
@ -65,12 +64,12 @@ class Video(PlexPartialObject):
def _find_transcodeSession(self, data):
elem = data.find('TranscodeSession')
if elem is not None:
return TranscodeSession(self.server, elem)
return media.TranscodeSession(self.server, elem)
return None
def iter_parts(self):
for media in self.media:
for part in media.parts:
for item in self.media:
for part in item.parts:
yield part
def analyze(self):
@ -116,6 +115,7 @@ class Video(PlexPartialObject):
self.server.query('%s/refresh' % self.key, method=put)
@utils.register_libtype
class Movie(Video):
TYPE = 'movie'
@ -124,17 +124,18 @@ class Movie(Video):
self.studio = data.attrib.get('studio', NA)
self.contentRating = data.attrib.get('contentRating', NA)
self.rating = data.attrib.get('rating', NA)
self.viewCount = cast(int, data.attrib.get('viewCount', 0))
self.viewOffset = cast(int, data.attrib.get('viewOffset', 0))
self.year = cast(int, data.attrib.get('year', NA))
self.viewCount = utils.cast(int, data.attrib.get('viewCount', 0))
self.viewOffset = utils.cast(int, data.attrib.get('viewOffset', 0))
self.year = utils.cast(int, data.attrib.get('year', NA))
self.summary = data.attrib.get('summary', NA)
self.tagline = data.attrib.get('tagline', NA)
self.duration = cast(int, data.attrib.get('duration', NA))
self.originallyAvailableAt = toDatetime(data.attrib.get('originallyAvailableAt', NA), '%Y-%m-%d')
self.duration = utils.cast(int, data.attrib.get('duration', NA))
self.originallyAvailableAt = utils.toDatetime(data.attrib.get('originallyAvailableAt', NA), '%Y-%m-%d')
self.primaryExtraKey = data.attrib.get('primaryExtraKey', NA)
self.is_watched = bool(self.viewCount > 0) # custom attr
@utils.register_libtype
class Show(Video):
TYPE = 'show'
@ -143,30 +144,30 @@ class Show(Video):
self.studio = data.attrib.get('studio', NA)
self.contentRating = data.attrib.get('contentRating', NA)
self.rating = data.attrib.get('rating', NA)
self.year = cast(int, data.attrib.get('year', NA))
self.year = utils.cast(int, data.attrib.get('year', NA))
self.banner = data.attrib.get('banner', NA)
self.theme = data.attrib.get('theme', NA)
self.duration = cast(int, data.attrib.get('duration', NA))
self.originallyAvailableAt = toDatetime(data.attrib.get('originallyAvailableAt', NA), '%Y-%m-%d')
self.leafCount = cast(int, data.attrib.get('leafCount', NA))
self.viewedLeafCount = cast(int, data.attrib.get('viewedLeafCount', NA))
self.childCount = cast(int, data.attrib.get('childCount', NA))
self.duration = utils.cast(int, data.attrib.get('duration', NA))
self.originallyAvailableAt = utils.toDatetime(data.attrib.get('originallyAvailableAt', NA), '%Y-%m-%d')
self.leafCount = utils.cast(int, data.attrib.get('leafCount', NA))
self.viewedLeafCount = utils.cast(int, data.attrib.get('viewedLeafCount', NA))
self.childCount = utils.cast(int, data.attrib.get('childCount', NA))
def seasons(self):
path = '/library/metadata/%s/children' % self.ratingKey
return list_items(self.server, path, Season.TYPE)
return utils.list_items(self.server, path, Season.TYPE)
def season(self, title):
path = '/library/metadata/%s/children' % self.ratingKey
return find_item(self.server, path, title)
return utils.find_item(self.server, path, title)
def episodes(self, watched=None):
leavesKey = '/library/metadata/%s/allLeaves' % self.ratingKey
return list_items(self.server, leavesKey, watched=watched)
return utils.list_items(self.server, leavesKey, watched=watched)
def episode(self, title):
path = '/library/metadata/%s/allLeaves' % self.ratingKey
return find_item(self.server, path, title)
return utils.find_item(self.server, path, title)
def watched(self):
return self.episodes(watched=True)
@ -181,6 +182,7 @@ class Show(Video):
self.server.query('/library/metadata/%s/refresh' % self.ratingKey)
@utils.register_libtype
class Season(Video):
TYPE = 'season'
@ -196,22 +198,22 @@ class Season(Video):
self.parentIndex = data.attrib.get('parentIndex', NA)
self.parentThumb = data.attrib.get('parentThumb', NA)
self.parentTheme = data.attrib.get('parentTheme', NA)
self.leafCount = cast(int, data.attrib.get('leafCount', NA))
self.viewedLeafCount = cast(int, data.attrib.get('viewedLeafCount', NA))
self.leafCount = utils.cast(int, data.attrib.get('leafCount', NA))
self.viewedLeafCount = utils.cast(int, data.attrib.get('viewedLeafCount', NA))
def episodes(self, watched=None):
childrenKey = '/library/metadata/%s/children' % self.ratingKey
return list_items(self.server, childrenKey, watched=watched)
return utils.list_items(self.server, childrenKey, watched=watched)
def episode(self, title):
path = '/library/metadata/%s/children' % self.ratingKey
return find_item(self.server, path, title)
return utils.find_item(self.server, path, title)
def get(self, title):
return self.episode(title)
def show(self):
return list_items(self.server, self.parentKey)[0]
return utils.list_items(self.server, self.parentKey)[0]
def watched(self):
return self.episodes(watched=True)
@ -220,6 +222,7 @@ class Season(Video):
return self.episodes(watched=False)
@utils.register_libtype
class Episode(Video):
TYPE = 'episode'
@ -236,11 +239,11 @@ class Episode(Video):
self.contentRating = data.attrib.get('contentRating', NA)
self.index = data.attrib.get('index', NA)
self.rating = data.attrib.get('rating', NA)
self.viewCount = cast(int, data.attrib.get('viewCount', 0))
self.viewOffset = cast(int, data.attrib.get('viewOffset', 0))
self.year = cast(int, data.attrib.get('year', NA))
self.duration = cast(int, data.attrib.get('duration', NA))
self.originallyAvailableAt = toDatetime(data.attrib.get('originallyAvailableAt', NA), '%Y-%m-%d')
self.viewCount = utils.cast(int, data.attrib.get('viewCount', 0))
self.viewOffset = utils.cast(int, data.attrib.get('viewOffset', 0))
self.year = utils.cast(int, data.attrib.get('year', NA))
self.duration = utils.cast(int, data.attrib.get('duration', NA))
self.originallyAvailableAt = utils.toDatetime(data.attrib.get('originallyAvailableAt', NA), '%Y-%m-%d')
self.is_watched = bool(self.viewCount > 0) # custom attr
@property
@ -248,54 +251,7 @@ class Episode(Video):
return self.server.url(self.grandparentThumb)
def season(self):
return list_items(self.server, self.parentKey)[0]
return utils.list_items(self.server, self.parentKey)[0]
def show(self):
return list_items(self.server, self.grandparentKey)[0]
def build_item(server, elem, initpath):
VIDEOCLS = {Movie.TYPE:Movie, Show.TYPE:Show, Season.TYPE:Season, Episode.TYPE:Episode}
vtype = elem.attrib.get('type')
if vtype in VIDEOCLS:
cls = VIDEOCLS[vtype]
return cls(server, elem, initpath)
raise UnknownType('Unknown video type: %s' % vtype)
def find_key(server, key):
path = '/library/metadata/{0}'.format(key)
try:
# Video seems to be the first sub element
elem = server.query(path)[0]
return build_item(server, elem, path)
except:
raise NotFound('Unable to find key: %s' % key)
def find_item(server, path, title):
for elem in server.query(path):
if elem.attrib.get('title').lower() == title.lower():
return build_item(server, elem, path)
raise NotFound('Unable to find title: %s' % title)
def list_items(server, path, videotype=None, watched=None):
items = []
for elem in server.query(path):
if videotype and elem.attrib.get('type') != videotype: continue
if watched is True and elem.attrib.get('viewCount', 0) == 0: continue
if watched is False and elem.attrib.get('viewCount', 0) >= 1: continue
try:
items.append(build_item(server, elem, path))
except UnknownType:
pass
return items
def search_type(videotype):
if videotype == Movie.TYPE: return 1
elif videotype == Show.TYPE: return 2
elif videotype == Season.TYPE: return 3
elif videotype == Episode.TYPE: return 4
raise NotFound('Unknown videotype: %s' % videotype)
return utils.list_items(self.server, self.grandparentKey)[0]

View file

@ -164,10 +164,9 @@ def test_navigate_around_show(plex, user=None):
@register('navigate,audio')
def test_navigate_around_artist(plex, user=None):
section = plex.library.section(AUDIO_SECTION)
print(section)
artist = plex.library.section(AUDIO_SECTION).get(AUDIO_ARTIST)
print(artist)
#section = plex.library.section(AUDIO_SECTION)
#artist = plex.library.section(AUDIO_SECTION).get(AUDIO_ARTIST)
assert False, 'Test not implemented!'
# seasons = show.seasons()
# season = show.season(SHOW_SEASON)

View file

@ -62,7 +62,7 @@ def run_tests(module, args):
tests['passed'] += 1
except Exception as err:
errstr = str(err)
errstr += '\n%s' % traceback.format_exc() if args.verbose else errstr
errstr += '\n%s' % traceback.format_exc() if args.verbose else ''
log(2, 'FAIL!: %s' % errstr, 'red')
tests['failed'] += 1
log(0, '')