A few more updates to get plexattrs.py fully running

This commit is contained in:
Michael Shepanski 2017-02-06 01:28:58 -05:00
parent 6a35f50a43
commit 4624512356
9 changed files with 123 additions and 86 deletions

View file

@ -91,9 +91,9 @@ class Artist(Audio):
self.guid = data.attrib.get('guid') self.guid = data.attrib.get('guid')
self.key = self.key.replace('/children', '') # FIX_BUG_50 self.key = self.key.replace('/children', '') # FIX_BUG_50
self.location = utils.findLocations(data, single=True) self.location = utils.findLocations(data, single=True)
self.countries = self._buildSubitems(data, media.Country) self.countries = self._buildItems(data, media.Country)
self.genres = self._buildSubitems(data, media.Genre) self.genres = self._buildItems(data, media.Genre)
self.similar = self._buildSubitems(data, media.Similar) self.similar = self._buildItems(data, media.Similar)
def album(self, title): def album(self, title):
""" Returns the :class:`~plexapi.audio.Album` that matches the specified title. """ Returns the :class:`~plexapi.audio.Album` that matches the specified title.
@ -186,7 +186,7 @@ class Album(Audio):
self.parentTitle = data.attrib.get('parentTitle') self.parentTitle = data.attrib.get('parentTitle')
self.studio = data.attrib.get('studio') self.studio = data.attrib.get('studio')
self.year = utils.cast(int, data.attrib.get('year')) self.year = utils.cast(int, data.attrib.get('year'))
self.genres = self._buildSubitems(data, media.Genre) self.genres = self._buildItems(data, media.Genre)
def track(self, title): def track(self, title):
""" Returns the :class:`~plexapi.audio.Track` that matches the specified title. """ Returns the :class:`~plexapi.audio.Track` that matches the specified title.
@ -293,8 +293,8 @@ class Track(Audio, Playable):
self.ratingCount = utils.cast(int, data.attrib.get('ratingCount')) self.ratingCount = utils.cast(int, data.attrib.get('ratingCount'))
self.viewOffset = utils.cast(int, data.attrib.get('viewOffset', 0)) self.viewOffset = utils.cast(int, data.attrib.get('viewOffset', 0))
self.year = utils.cast(int, data.attrib.get('year')) self.year = utils.cast(int, data.attrib.get('year'))
self.media = self._buildSubitems(data, media.Media) self.media = self._buildItems(data, media.Media)
self.moods = self._buildSubitems(data, media.Mood) self.moods = self._buildItems(data, media.Mood)
# data for active sessions and history # data for active sessions and history
self.sessionKey = utils.cast(int, data.attrib.get('sessionKey')) self.sessionKey = utils.cast(int, data.attrib.get('sessionKey'))
self.username = utils.findUsername(data) self.username = utils.findUsername(data)

View file

@ -135,17 +135,37 @@ class PlexObject(object):
return cls(self._root, elem, initpath) return cls(self._root, elem, initpath)
raise UnknownType('Unknown library type: %s' % libtype) raise UnknownType('Unknown library type: %s' % libtype)
def _buildSubitems(self, data, cls, tag=None, filters=None, *args): def _buildItems(self, data, cls=None, tag=None, attrs=None, safe=False):
""" Build and return a list of items (optionally filtered by tag). """ """ Build and return a list of items (optionally filtered by tag).
Parameters:
data (ElementTree): XML data to search for items.
cls (PlexObject): Optionally specify the PlexObject to be built. If not specified
_buildItem will be called and the best guess item will be built.
tag (str): Only build items with the specified tag. If not specified and
cls is specified, tag will be set to cls.TYPE.
attrs (dict): Dict containing attributes to filter the elements by. If not
specified, all elements will be considered.
safe (bool): If True, dont raise an exception when unable to build an object.
"""
items = [] items = []
tag = tag or cls.TYPE tag = cls.TYPE if not tag and cls else tag
filters = filters or {} attrs = attrs or {}
for elem in data: for elem in data:
if elem.tag == tag: try:
for attr, value in filters.items(): if not tag or elem.tag == tag:
if elem.attrib.get(attr) != str(value): for attr, value in attrs.items():
continue if elem.attrib.get(attr) != str(value):
items.append(cls(self._root, elem, self._initpath, *args)) continue
if cls is not None:
items.append(cls(self._root, elem, self._initpath))
else:
items.append(self._buildItem(elem, self._initpath))
except Exception as err:
if safe:
log.warn('Failed to build %s (type=%s); %s' % (elem.tag, elem.attrib.get('type', 'NA'), err))
continue
raise
return items return items
def _fetchItem(self, key, title=None, name=None): def _fetchItem(self, key, title=None, name=None):

View file

@ -1,5 +1,4 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import logging
from plexapi import X_PLEX_CONTAINER_SIZE, log, utils from plexapi import X_PLEX_CONTAINER_SIZE, log, utils
from plexapi.base import PlexObject from plexapi.base import PlexObject
from plexapi.compat import unquote from plexapi.compat import unquote
@ -81,11 +80,11 @@ class Library(PlexObject):
def onDeck(self): def onDeck(self):
""" Returns a list of all media items on deck. """ """ Returns a list of all media items on deck. """
return utils.listItems(self.server, '/library/onDeck') return self._fetchItems('/library/onDeck')
def recentlyAdded(self): def recentlyAdded(self):
""" Returns a list of all media items recently added. """ """ Returns a list of all media items recently added. """
return utils.listItems(self.server, '/library/recentlyAdded') return self._fetchItems('/library/recentlyAdded')
def get(self, title): # this should use hub search when its merged def get(self, title): # this should use hub search when its merged
""" Return the first item from all items with the specified title. """ Return the first item from all items with the specified title.
@ -121,8 +120,8 @@ class Library(PlexObject):
args['type'] = utils.searchType(libtype) args['type'] = utils.searchType(libtype)
for attr, value in kwargs.items(): for attr, value in kwargs.items():
args[attr] = value args[attr] = value
query = '/library/all%s' % utils.joinArgs(args) key = '/library/all%s' % utils.joinArgs(args)
return utils.listItems(self.server, query) return self._fetchItems(key)
def cleanBundles(self): def cleanBundles(self):
""" Poster images and other metadata for items in your library are kept in "bundle" """ Poster images and other metadata for items in your library are kept in "bundle"
@ -475,23 +474,17 @@ class PhotoSection(LibrarySection):
@utils.register_libtype @utils.register_libtype
class Hub(object): class Hub(PlexObject):
TYPE = 'Hub' TYPE = 'Hub'
HUBTYPES = {'genre':Genre, 'director':Director, 'actor':Role} FILTERTYPES = {'genre':Genre, 'director':Director, 'actor':Role}
def __init__(self, server, data, initpath): def _loadData(self, data):
self._data = data self._data = data
self.server = server
self.initpath = initpath
self.hubIdentifier = data.attrib.get('hubIdentifier') self.hubIdentifier = data.attrib.get('hubIdentifier')
self.size = utils.cast(int, data.attrib.get('size')) self.size = utils.cast(int, data.attrib.get('size'))
self.title = data.attrib.get('title') self.title = data.attrib.get('title')
self.type = data.attrib.get('type') self.type = data.attrib.get('type')
if self.type in self.HUBTYPES: self.items = self._buildItems(data)
mediacls = self.HUBTYPES[self.type]
self.items = [mediacls(self.server, elem) for elem in data]
else:
self.items = self._safe_builditems(data)
def __repr__(self): def __repr__(self):
return '<Hub:%s>' % self.title.encode('utf8') return '<Hub:%s>' % self.title.encode('utf8')
@ -499,14 +492,11 @@ class Hub(object):
def __len__(self): def __len__(self):
return self.size return self.size
def _safe_builditems(self, data): def _buildItems(self, data):
items = [] if self.type in self.FILTERTYPES:
for elem in data: cls = self.FILTERTYPES[self.type]
try: return [cls(self._root, elem, self._initpath) for elem in data]
items.append(utils.buildItem(self.server, elem, '/hubs')) return super(Hub, self)._buildItems(data, safe=True)
except Exception as err:
logging.warn('Failed %s to build %s; Error: %s' % (self.type, self.title, err))
return items
@utils.register_libtype @utils.register_libtype

View file

@ -52,7 +52,7 @@ class Media(PlexObject):
self.videoFrameRate = data.attrib.get('videoFrameRate') self.videoFrameRate = data.attrib.get('videoFrameRate')
self.videoResolution = data.attrib.get('videoResolution') self.videoResolution = data.attrib.get('videoResolution')
self.width = cast(int, data.attrib.get('width')) self.width = cast(int, data.attrib.get('width'))
self.parts = self._buildSubitems(data, MediaPart) self.parts = self._buildItems(data, MediaPart)
def __repr__(self): def __repr__(self):
title = self.video.title.replace(' ','.')[0:20] title = self.video.title.replace(' ','.')[0:20]
@ -84,13 +84,31 @@ class MediaPart(PlexObject):
self.id = cast(int, data.attrib.get('id')) self.id = cast(int, data.attrib.get('id'))
self.key = data.attrib.get('key') self.key = data.attrib.get('key')
self.size = cast(int, data.attrib.get('size')) self.size = cast(int, data.attrib.get('size'))
self.videoStreams = self._buildSubitems(data, VideoStream, 'Stream', {'streamType':VideoStream.STREAMTYPE}) self.streams = self._buildStreams(data)
self.audioStreams = self._buildSubitems(data, AudioStream, 'Stream', {'streamType':AudioStream.STREAMTYPE})
self.subtitleStreams = self._buildSubitems(data, SubtitleStream, 'Stream', {'streamType':SubtitleStream.STREAMTYPE})
def __repr__(self): def __repr__(self):
return '<%s:%s>' % (self.__class__.__name__, self.id) return '<%s:%s>' % (self.__class__.__name__, self.id)
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._root, elem, self._initpath))
return streams
@property
def videoStreams(self):
return [s for s in self.streams if s.streamType == VideoStream.STREAMTYPE]
@property
def audioStreams(self):
return [s for s in self.streams if s.streamType == AudioStream.STREAMTYPE]
@property
def subtitleStreams(self):
return [s for s in self.streams if s.streamType == SubtitleStream.STREAMTYPE]
class MediaPartStream(PlexObject): class MediaPartStream(PlexObject):
""" Base class for media streams. These consist of video, audio and subtitles. """ Base class for media streams. These consist of video, audio and subtitles.

View file

@ -252,7 +252,7 @@ class MyPlexResource(PlexObject):
self.home = utils.cast(bool, data.attrib.get('home')) self.home = utils.cast(bool, data.attrib.get('home'))
self.synced = utils.cast(bool, data.attrib.get('synced')) self.synced = utils.cast(bool, data.attrib.get('synced'))
self.presence = utils.cast(bool, data.attrib.get('presence')) self.presence = utils.cast(bool, data.attrib.get('presence'))
self.connections = self._buildSubitems(data, ResourceConnection) self.connections = self._buildItems(data, ResourceConnection)
def __repr__(self): def __repr__(self):
return '<%s:%s>' % (self.__class__.__name__, self.name.encode('utf8')) return '<%s:%s>' % (self.__class__.__name__, self.name.encode('utf8'))

View file

@ -110,7 +110,7 @@ class Photo(PlexPartialObject):
self.type = data.attrib.get('type') self.type = data.attrib.get('type')
self.updatedAt = utils.toDatetime(data.attrib.get('updatedAt')) self.updatedAt = utils.toDatetime(data.attrib.get('updatedAt'))
self.year = utils.cast(int, data.attrib.get('year')) self.year = utils.cast(int, data.attrib.get('year'))
self.media = self._buildSubitems(data, media.Media) self.media = self._buildItems(data, media.Media)
def photoalbum(self): def photoalbum(self):
""" Return this photo's :class:`~plexapi.photo.Photoalbum`. """ """ Return this photo's :class:`~plexapi.photo.Photoalbum`. """

View file

@ -279,8 +279,8 @@ class PlexServer(PlexObject):
params['section'] = utils.SEARCHTYPES[mediatype] params['section'] = utils.SEARCHTYPES[mediatype]
if limit: if limit:
params['limit'] = limit params['limit'] = limit
url = '/hubs/search?%s' % urlencode(params) key = '/hubs/search?%s' % urlencode(params)
for hub in utils.listItems(self, url, bytag=True): for hub in self._fetchItems(key, bytag=True):
results += hub.items results += hub.items
return results return results

View file

@ -92,15 +92,15 @@ class Movie(Video, Playable):
self.userRating = utils.cast(float, data.attrib.get('userRating')) self.userRating = utils.cast(float, data.attrib.get('userRating'))
self.viewOffset = utils.cast(int, data.attrib.get('viewOffset', 0)) self.viewOffset = utils.cast(int, data.attrib.get('viewOffset', 0))
self.year = utils.cast(int, data.attrib.get('year')) self.year = utils.cast(int, data.attrib.get('year'))
self.collections = self._buildSubitems(data, media.Collection) self.collections = self._buildItems(data, media.Collection)
self.countries = self._buildSubitems(data, media.Country) self.countries = self._buildItems(data, media.Country)
self.directors = self._buildSubitems(data, media.Director) self.directors = self._buildItems(data, media.Director)
self.fields = self._buildSubitems(data, media.Field) self.fields = self._buildItems(data, media.Field)
self.genres = self._buildSubitems(data, media.Genre) self.genres = self._buildItems(data, media.Genre)
self.media = self._buildSubitems(data, media.Media) self.media = self._buildItems(data, media.Media)
self.producers = self._buildSubitems(data, media.Producer) self.producers = self._buildItems(data, media.Producer)
self.roles = self._buildSubitems(data, media.Role) self.roles = self._buildItems(data, media.Role)
self.writers = self._buildSubitems(data, media.Writer) self.writers = self._buildItems(data, media.Writer)
# self.videoStreams = utils.findStreams(self.media, 'videostream') # these dont go here # self.videoStreams = utils.findStreams(self.media, 'videostream') # these dont go here
# self.audioStreams = utils.findStreams(self.media, 'audiostream') # these dont go here # self.audioStreams = utils.findStreams(self.media, 'audiostream') # these dont go here
# self.subtitleStreams = utils.findStreams(self.media, 'subtitlestream') # these dont go here # self.subtitleStreams = utils.findStreams(self.media, 'subtitlestream') # these dont go here
@ -167,8 +167,8 @@ class Show(Video):
self.theme = data.attrib.get('theme') self.theme = data.attrib.get('theme')
self.viewedLeafCount = utils.cast(int, data.attrib.get('viewedLeafCount')) self.viewedLeafCount = utils.cast(int, data.attrib.get('viewedLeafCount'))
self.year = utils.cast(int, data.attrib.get('year')) self.year = utils.cast(int, data.attrib.get('year'))
self.genres = self._buildSubitems(data, media.Genre) self.genres = self._buildItems(data, media.Genre)
self.roles = self._buildSubitems(data, media.Role) self.roles = self._buildItems(data, media.Role)
@property @property
def actors(self): def actors(self):
@ -417,12 +417,9 @@ class Episode(Video, Playable):
self.rating = utils.cast(float, data.attrib.get('rating')) self.rating = utils.cast(float, data.attrib.get('rating'))
self.viewOffset = utils.cast(int, data.attrib.get('viewOffset', 0)) self.viewOffset = utils.cast(int, data.attrib.get('viewOffset', 0))
self.year = utils.cast(int, data.attrib.get('year')) self.year = utils.cast(int, data.attrib.get('year'))
self.directors = self._buildSubitems(data, media.Director) self.directors = self._buildItems(data, media.Director)
self.media = self._buildSubitems(data, media.Media) self.media = self._buildItems(data, media.Media)
self.writers = self._buildSubitems(data, media.Writer) self.writers = self._buildItems(data, media.Writer)
# self.videoStreams = utils.findStreams(self.media, 'videostream')
# self.audioStreams = utils.findStreams(self.media, 'audiostream')
# self.subtitleStreams = utils.findStreams(self.media, 'subtitlestream')
# data for active sessions and history # data for active sessions and history
self.sessionKey = utils.cast(int, data.attrib.get('sessionKey')) self.sessionKey = utils.cast(int, data.attrib.get('sessionKey'))
self.username = utils.findUsername(data) self.username = utils.findUsername(data)

View file

@ -48,6 +48,11 @@ DONT_RELOAD = (
'client.PlexClient', # we dont have the token to reload. 'client.PlexClient', # we dont have the token to reload.
#'server.PlexServer', # setting version to None? :( #'server.PlexServer', # setting version to None? :(
) )
TAGATTRS = {
'Media': 'media',
'Country': 'countries',
}
STOP_RECURSING_AT = ( STOP_RECURSING_AT = (
#'media.MediaPart', #'media.MediaPart',
) )
@ -64,16 +69,17 @@ class PlexAttributes():
def run(self): def run(self):
starttime = time.time() starttime = time.time()
# self._parse_myplex() self._parse_myplex()
# self._parse_server() self._parse_server()
# self._parse_library() self._parse_search()
# self._parse_audio() self._parse_library()
# self._parse_photo() self._parse_audio()
# self._parse_movie() self._parse_photo()
# self._parse_show() self._parse_movie()
# self._parse_client() self._parse_show()
# self._parse_playlist() self._parse_client()
# self._parse_sync() self._parse_playlist()
self._parse_sync()
self.runtime = round((time.time() - starttime) / 60.0, 1) self.runtime = round((time.time() - starttime) / 60.0, 1)
return self return self
@ -88,21 +94,24 @@ class PlexAttributes():
def _parse_server(self): def _parse_server(self):
self._load_attrs(self.plex, 'serv') self._load_attrs(self.plex, 'serv')
self._load_attrs(self.plex.account(), 'serv') self._load_attrs(self.plex.account(), 'serv')
self._load_attrs(self.plex.history()[:20], 'hist') self._load_attrs(self.plex.history()[:50], 'hist')
# self._load_attrs(self.plex.playlists()) self._load_attrs(self.plex.history()[50:], 'hist')
# for search in ('cre', 'ani', 'mik', 'she'): self._load_attrs(self.plex.sessions(), 'sess')
# self._load_attrs(self.plex.search('cre'))
# self._load_attrs(self.plex.sessions(), 'sess') def _parse_search(self):
for search in ('cre', 'ani', 'mik', 'she', 'bea'):
self._load_attrs(self.plex.search(search), 'hub')
def _parse_library(self): def _parse_library(self):
cat = 'lib' cat = 'lib'
self._load_attrs(self.plex.library, cat) self._load_attrs(self.plex.library, cat)
# self._load_attrs(self.plex.library.sections()) #self._load_attrs(self.plex.library.all()[:50], 'all')
# self._load_attrs(self.plex.library.all()[:20]) self._load_attrs(self.plex.library.onDeck()[:50], 'deck')
# self._load_attrs(self.plex.library.onDeck()[:20]) self._load_attrs(self.plex.library.recentlyAdded()[:50], 'add')
# self._load_attrs(self.plex.library.recentlyAdded()[:20]) for search in ('cat', 'dog', 'rat', 'gir', 'mou'):
# for search in ('cat', 'dog', 'rat'): self._load_attrs(self.plex.library.search(search)[:50], 'srch')
# self._load_attrs(self.plex.library.search(search)[:20]) # TODO: Implement section search (remove library search?)
# TODO: Implement section search filters
def _parse_audio(self): def _parse_audio(self):
cat = 'lib' cat = 'lib'
@ -142,7 +151,7 @@ class PlexAttributes():
for show in showsection.all(): for show in showsection.all():
self._load_attrs(show, cat) self._load_attrs(show, cat)
for season in show.seasons(): for season in show.seasons():
self._load_attrs(show, cat) self._load_attrs(season, cat)
for episode in season.episodes(): for episode in season.episodes():
self._load_attrs(episode, cat) self._load_attrs(episode, cat)
@ -200,6 +209,9 @@ class PlexAttributes():
if cat: categories[attr].add(cat) if cat: categories[attr].add(cat)
if elem.attrib[attr] and len(examples[attr]) <= self.opts.examples: if elem.attrib[attr] and len(examples[attr]) <= self.opts.examples:
examples[attr].add(elem.attrib[attr]) examples[attr].add(elem.attrib[attr])
for subelem in elem:
attrname = TAGATTRS.get(subelem.tag, '%ss' % subelem.tag.lower())
attrs['%s[]' % attrname] += 1
def _load_obj_attrs(self, clsname, obj, attrs): def _load_obj_attrs(self, clsname, obj, attrs):
if clsname in STOP_RECURSING_AT: return None if clsname in STOP_RECURSING_AT: return None