From 50ac2f55e52deb8156e44e8f1e1d8ca81cc13e1c Mon Sep 17 00:00:00 2001 From: Michael Shepanski Date: Sun, 3 Apr 2016 23:55:29 -0400 Subject: [PATCH] Create Playable object to put function specific to media that is playable like getStreamURL etc.. --- plexapi/audio.py | 51 +++++++++++++++++++---------------------- plexapi/playlist.py | 13 +++++------ plexapi/utils.py | 55 +++++++++++++++++++-------------------------- plexapi/video.py | 23 +++++++++---------- 4 files changed, 63 insertions(+), 79 deletions(-) diff --git a/plexapi/audio.py b/plexapi/audio.py index 1c00e18d..80a706f6 100644 --- a/plexapi/audio.py +++ b/plexapi/audio.py @@ -3,10 +3,14 @@ PlexAPI Audio """ from plexapi import media, utils +from plexapi.utils import Playable, PlexPartialObject NA = utils.NA -class Audio(utils.PlexPartialObject): +class Audio(PlexPartialObject): + + def __init__(self, server, data, initpath): + super(Audio, self).__init__(data, initpath, server) def _loadData(self, data): self.addedAt = utils.toDatetime(data.attrib.get('addedAt', NA)) @@ -22,6 +26,13 @@ class Audio(utils.PlexPartialObject): self.type = data.attrib.get('type', NA) self.updatedAt = utils.toDatetime(data.attrib.get('updatedAt', NA)) self.viewCount = utils.cast(int, data.attrib.get('viewCount', 0)) + + @property + def thumbUrl(self): + return self.server.url(self.thumb) + + def refresh(self): + self.server.query('%s/refresh' % self.key, method=self.server.session.put) @utils.register_libtype @@ -32,7 +43,7 @@ class Artist(Audio): super(Artist, self)._loadData(data) self.art = data.attrib.get('art', NA) self.guid = data.attrib.get('guid', NA) - self.key = self.key.replace('/children', '') # plex bug? http://bit.ly/1Sc2J3V + self.key = self.key.replace('/children', '') # FIX_BUG_50 self.location = utils.findLocation(data) if self.isFullObject(): self.countries = [media.Country(self.server, e) for e in data if e.tag == media.Country.TYPE] @@ -40,31 +51,23 @@ class Artist(Audio): self.similar = [media.Similar(self.server, e) for e in data if e.tag == media.Similar.TYPE] def albums(self): - path = '/library/metadata/%s/children' % self.ratingKey + path = '%s/children' % self.key return utils.listItems(self.server, path, Album.TYPE) def album(self, title): - path = '/library/metadata/%s/children' % self.ratingKey + path = '%s/children' % self.key return utils.findItem(self.server, path, title) def tracks(self, watched=None): - leavesKey = '/library/metadata/%s/allLeaves' % self.ratingKey - return utils.listItems(self.server, leavesKey, watched=watched) + path = '%s/allLeaves' % self.key + return utils.listItems(self.server, path, watched=watched) def track(self, title): - path = '/library/metadata/%s/allLeaves' % self.ratingKey + path = '%s/allLeaves' % self.key return utils.findItem(self.server, path, title) def get(self, title): return self.track(title) - - def isFullObject(self): - # plex bug? http://bit.ly/1Sc2J3V - fixed_key = self.key.replace('/children', '') - return self.initpath == fixed_key - - def refresh(self): - self.server.query('/library/metadata/%s/refresh' % self.ratingKey) @utils.register_libtype @@ -74,7 +77,7 @@ class Album(Audio): def _loadData(self, data): super(Album, self)._loadData(data) self.art = data.attrib.get('art', NA) - self.key = self.key.replace('/children', '') # plex bug? http://bit.ly/1Sc2J3V + self.key = self.key.replace('/children', '') # FIX_BUG_50 self.originallyAvailableAt = utils.toDatetime(data.attrib.get('originallyAvailableAt', NA), '%Y-%m-%d') self.parentKey = data.attrib.get('parentKey', NA) self.parentRatingKey = data.attrib.get('parentRatingKey', NA) @@ -86,20 +89,15 @@ class Album(Audio): self.genres = [media.Genre(self.server, e) for e in data if e.tag == media.Genre.TYPE] def tracks(self, watched=None): - childrenKey = '/library/metadata/%s/children' % self.ratingKey - return utils.listItems(self.server, childrenKey, watched=watched) + path = '%s/children' % self.key + return utils.listItems(self.server, path, watched=watched) def track(self, title): - path = '/library/metadata/%s/children' % self.ratingKey + path = '%s/children' % self.key return utils.findItem(self.server, path, title) def get(self, title): return self.track(title) - - def isFullObject(self): - # plex bug? http://bit.ly/1Sc2J3V - fixed_key = self.key.replace('/children', '') - return self.initpath == fixed_key def artist(self): return utils.listItems(self.server, self.parentKey)[0] @@ -112,7 +110,7 @@ class Album(Audio): @utils.register_libtype -class Track(Audio): +class Track(Audio, Playable): TYPE = 'track' def _loadData(self, data): @@ -154,6 +152,3 @@ class Track(Audio): def artist(self): return utils.listItems(self.server, self.grandparentKey)[0] - - def getStreamURL(self, **params): - return self._getStreamURL(**params) diff --git a/plexapi/playlist.py b/plexapi/playlist.py index e727df7d..29f1d36d 100644 --- a/plexapi/playlist.py +++ b/plexapi/playlist.py @@ -4,20 +4,24 @@ PlexPlaylist """ from plexapi import utils from plexapi.utils import cast, toDatetime +from plexapi.utils import PlexPartialObject NA = utils.NA @utils.register_libtype -class Playlist(utils.PlexPartialObject): +class Playlist(PlexPartialObject): TYPE = 'playlist' + def __init__(self, server, data, initpath): + super(Playlist, self).__init__(data, initpath, server) + def _loadData(self, data): self.addedAt = toDatetime(data.attrib.get('addedAt', NA)) self.composite = data.attrib.get('composite', NA) # url to thumbnail self.duration = cast(int, data.attrib.get('duration', NA)) self.durationInSeconds = cast(int, data.attrib.get('durationInSeconds', NA)) self.guid = data.attrib.get('guid', NA) - self.key = data.attrib.get('key', NA).replace('/items', '') # plex bug? http://bit.ly/1Sc2J3V + self.key = data.attrib.get('key', NA).replace('/items', '') # FIX_BUG_50 self.leafCount = cast(int, data.attrib.get('leafCount', NA)) self.playlistType = data.attrib.get('playlistType', NA) self.ratingKey = data.attrib.get('ratingKey', NA) @@ -30,8 +34,3 @@ class Playlist(utils.PlexPartialObject): def items(self): path = '%s/items' % self.key return utils.listItems(self.server, path) - - def isFullObject(self): - # plex bug? http://bit.ly/1Sc2J3V - fixed_key = self.key.replace('/items', '') - return self.initpath == fixed_key diff --git a/plexapi/utils.py b/plexapi/utils.py index bf9bf2ac..656dd94f 100644 --- a/plexapi/utils.py +++ b/plexapi/utils.py @@ -38,8 +38,8 @@ NA = _NA() # you request is None it will fetch the full object automatically and update itself. class PlexPartialObject(object): - def __init__(self, server, data, initpath): - self.server = server # TODO: This should not be needed here.. + def __init__(self, data, initpath, server=None): + self.server = server self.initpath = initpath self._loadData(data) @@ -62,13 +62,25 @@ class PlexPartialObject(object): if value != NA: super(PlexPartialObject, self).__setattr__(attr, value) - # TODO: This shouldn't be here, move downstream - @property - def thumbUrl(self): - return self.server.url(self.thumb) - - # TODO: Move to Playable() - def _getStreamURL(self, **params): + def _loadData(self, data): + raise Exception('Abstract method not implemented.') + + def isFullObject(self): + return self.initpath == self.key + + def isPartialObject(self): + return not self.isFullObject() + + def reload(self): + """ Reload the data for this object from PlexServer XML. """ + data = self.server.query(self.key) + self.initpath = self.key + self._loadData(data[0]) + + +class Playable(object): + + def getStreamURL(self, **params): if self.TYPE not in ('movie', 'episode', 'track'): raise Unsupported('Fetching stream URL for %s is unsupported.' % self.TYPE) mvb = params.get('maxVideoBitrate') @@ -86,36 +98,15 @@ class PlexPartialObject(object): params = {k:v for k,v in params.items() if v is not None} # remove None values streamtype = 'audio' if self.TYPE in ('track', 'album') else 'video' return self.server.url('/%s/:/transcode/universal/start.m3u8?%s' % (streamtype, urlencode(params))) - - def _loadData(self, data): - raise Exception('Abstract method not implemented.') - - def isFullObject(self): - return self.initpath == self.key - - def isPartialObject(self): - return not self.isFullObject() - - # TODO: Move to Playable() + def iterParts(self): for item in self.media: for part in item.parts: yield part - - # TODO: Move to Playable() + def play(self, client): client.playMedia(self) - # TODO: This shouldn't be here, move downstream - def refresh(self): - self.server.query('%s/refresh' % self.key, method=put) - - def reload(self): - """ Reload the data for this object from PlexServer XML. """ - data = self.server.query(self.key) - self.initpath = self.key - self._loadData(data[0]) - def buildItem(server, elem, initpath, bytag=False): libtype = elem.tag if bytag else elem.attrib.get('type') diff --git a/plexapi/video.py b/plexapi/video.py index a85b02c8..88eefb3a 100644 --- a/plexapi/video.py +++ b/plexapi/video.py @@ -3,12 +3,16 @@ PlexVideo """ from plexapi import media, utils +from plexapi.utils import Playable, PlexPartialObject NA = utils.NA -class Video(utils.PlexPartialObject): +class Video(PlexPartialObject): TYPE = None + def __init__(self, server, data, initpath): + super(Video, self).__init__(data, initpath, server) + def _loadData(self, data): self.addedAt = utils.toDatetime(data.attrib.get('addedAt', NA)) self.key = data.attrib.get('key', NA) @@ -23,6 +27,10 @@ class Video(utils.PlexPartialObject): self.updatedAt = utils.toDatetime(data.attrib.get('updatedAt', NA)) self.viewCount = utils.cast(int, data.attrib.get('viewCount', 0)) + @property + def thumbUrl(self): + return self.server.url(self.thumb) + def analyze(self): """ The primary purpose of media analysis is to gather information about that media item. All of the media you add to a Library has properties that are useful to @@ -40,15 +48,12 @@ class Video(utils.PlexPartialObject): self.server.query(path) self.reload() - def play(self, client): - client.playMedia(self) - def refresh(self): self.server.query('%s/refresh' % self.key, method=self.server.session.put) @utils.register_libtype -class Movie(Video): +class Movie(Video, Playable): TYPE = 'movie' def _loadData(self, data): @@ -95,9 +100,6 @@ class Movie(Video): @property def isWatched(self): return bool(self.viewCount > 0) - - def getStreamURL(self, **params): - return self._getStreamURL(**params) @utils.register_libtype @@ -198,7 +200,7 @@ class Season(Video): @utils.register_libtype -class Episode(Video): +class Episode(Video, Playable): TYPE = 'episode' def _loadData(self, data): @@ -242,9 +244,6 @@ class Episode(Video): @property def thumbUrl(self): return self.server.url(self.grandparentThumb) - - def getStreamURL(self, **params): - return self._getStreamURL(videoResolution='800x600', **params) def season(self): return utils.listItems(self.server, self.parentKey)[0]