mirror of
https://github.com/pkkid/python-plexapi
synced 2024-11-22 11:43:13 +00:00
Cleanup playlist support; Fix UUID on URLs; Better method to store listTypes; Cache section IDs in library
This commit is contained in:
parent
3138ad1087
commit
748fc68406
9 changed files with 70 additions and 51 deletions
|
@ -14,6 +14,7 @@ class Audio(PlexPartialObject):
|
|||
super(Audio, self).__init__(data, initpath, server)
|
||||
|
||||
def _loadData(self, data):
|
||||
self.listType = 'audio'
|
||||
self.addedAt = utils.toDatetime(data.attrib.get('addedAt', NA))
|
||||
self.index = data.attrib.get('index', NA)
|
||||
self.key = data.attrib.get('key', NA)
|
||||
|
@ -35,18 +36,20 @@ class Audio(PlexPartialObject):
|
|||
def refresh(self):
|
||||
self.server.query('%s/refresh' % self.key, method=self.server.session.put)
|
||||
|
||||
def section(self):
|
||||
return self.server.library.sectionByID(self.librarySectionID)
|
||||
|
||||
|
||||
@utils.register_libtype
|
||||
class Artist(Audio):
|
||||
TYPE = 'artist'
|
||||
LISTTYPE = 'audio'
|
||||
|
||||
def _loadData(self, data):
|
||||
Audio._loadData(self, data)
|
||||
self.art = data.attrib.get('art', NA)
|
||||
self.guid = data.attrib.get('guid', NA)
|
||||
self.key = self.key.replace('/children', '') # FIX_BUG_50
|
||||
self.location = utils.findLocation(data)
|
||||
self.location = utils.findLocations(data, single=True)
|
||||
if self.isFullObject():
|
||||
self.countries = [media.Country(self.server, e) for e in data if e.tag == media.Country.TYPE]
|
||||
self.genres = [media.Genre(self.server, e) for e in data if e.tag == media.Genre.TYPE]
|
||||
|
@ -75,7 +78,6 @@ class Artist(Audio):
|
|||
@utils.register_libtype
|
||||
class Album(Audio):
|
||||
TYPE = 'album'
|
||||
LISTTYPE = 'audio'
|
||||
|
||||
def _loadData(self, data):
|
||||
Audio._loadData(self, data)
|
||||
|
@ -115,7 +117,6 @@ class Album(Audio):
|
|||
@utils.register_libtype
|
||||
class Track(Audio, Playable):
|
||||
TYPE = 'track'
|
||||
LISTTYPE = 'audio'
|
||||
|
||||
def _loadData(self, data):
|
||||
Audio._loadData(self, data)
|
||||
|
|
|
@ -11,11 +11,12 @@ from plexapi.exceptions import BadRequest, NotFound
|
|||
class Library(object):
|
||||
|
||||
def __init__(self, server, data):
|
||||
self.server = server
|
||||
self.identifier = data.attrib.get('identifier')
|
||||
self.mediaTagVersion = data.attrib.get('mediaTagVersion')
|
||||
self.server = server
|
||||
self.title1 = data.attrib.get('title1')
|
||||
self.title2 = data.attrib.get('title2')
|
||||
self._sectionsByID = {} # cached section UUIDs
|
||||
|
||||
def __repr__(self):
|
||||
return '<Library:%s>' % self.title1.encode('utf8')
|
||||
|
@ -33,7 +34,9 @@ class Library(object):
|
|||
stype = elem.attrib['type']
|
||||
if stype in SECTION_TYPES:
|
||||
cls = SECTION_TYPES[stype]
|
||||
items.append(cls(self.server, elem, path))
|
||||
section = cls(self.server, elem, path)
|
||||
self._sectionsByID[section.key] = section
|
||||
items.append(section)
|
||||
return items
|
||||
|
||||
def section(self, title=None):
|
||||
|
@ -42,6 +45,11 @@ class Library(object):
|
|||
return item
|
||||
raise NotFound('Invalid library section: %s' % title)
|
||||
|
||||
def sectionByID(self, sectionID):
|
||||
if not self._sectionsByID:
|
||||
self.sections()
|
||||
return self._sectionsByID[sectionID]
|
||||
|
||||
def all(self):
|
||||
return utils.listItems(self.server, '/library/all')
|
||||
|
||||
|
@ -94,12 +102,23 @@ class LibrarySection(object):
|
|||
def __init__(self, server, data, initpath):
|
||||
self.server = server
|
||||
self.initpath = initpath
|
||||
self.type = data.attrib.get('type')
|
||||
self.agent = data.attrib.get('agent')
|
||||
self.allowSync = utils.cast(bool, data.attrib.get('allowSync'))
|
||||
self.art = data.attrib.get('art')
|
||||
self.composite = data.attrib.get('composite')
|
||||
self.createdAt = utils.toDatetime(data.attrib.get('createdAt'))
|
||||
self.filters = data.attrib.get('filters')
|
||||
self.key = data.attrib.get('key')
|
||||
self.title = data.attrib.get('title')
|
||||
self.scanner = data.attrib.get('scanner')
|
||||
self.language = data.attrib.get('language')
|
||||
# TODO: Add Location
|
||||
self.language = data.attrib.get('language')
|
||||
self.locations = utils.findLocations(data)
|
||||
self.refreshing = utils.cast(bool, data.attrib.get('refreshing'))
|
||||
self.scanner = data.attrib.get('scanner')
|
||||
self.thumb = data.attrib.get('thumb')
|
||||
self.title = data.attrib.get('title')
|
||||
self.type = data.attrib.get('type')
|
||||
self.updatedAt = utils.toDatetime(data.attrib.get('updatedAt'))
|
||||
self.uuid = data.attrib.get('uuid')
|
||||
|
||||
def __repr__(self):
|
||||
title = self.title.replace(' ','.')[0:20]
|
||||
|
|
|
@ -10,12 +10,12 @@ NA = utils.NA
|
|||
@utils.register_libtype
|
||||
class Photoalbum(PlexPartialObject):
|
||||
TYPE = 'photoalbum'
|
||||
LISTTYPE = 'photo'
|
||||
|
||||
def __init__(self, server, data, initpath):
|
||||
super(Photoalbum, self).__init__(data, initpath, server)
|
||||
|
||||
def _loadData(self, data):
|
||||
self.listType = 'photo'
|
||||
self.addedAt = utils.toDatetime(data.attrib.get('addedAt', NA))
|
||||
self.art = data.attrib.get('art', NA)
|
||||
self.composite = data.attrib.get('composite', NA)
|
||||
|
@ -38,16 +38,19 @@ class Photoalbum(PlexPartialObject):
|
|||
path = '/library/metadata/%s/children' % self.ratingKey
|
||||
return utils.findItem(self.server, path, title)
|
||||
|
||||
def section(self):
|
||||
return self.server.library.sectionByID(self.librarySectionID)
|
||||
|
||||
|
||||
@utils.register_libtype
|
||||
class Photo(PlexPartialObject):
|
||||
TYPE = 'photo'
|
||||
LISTTYPE = 'photo'
|
||||
|
||||
def __init__(self, server, data, initpath):
|
||||
super(Photo, self).__init__(data, initpath, server)
|
||||
|
||||
def _loadData(self, data):
|
||||
self.listType = 'photo'
|
||||
self.addedAt = utils.toDatetime(data.attrib.get('addedAt', NA))
|
||||
self.index = utils.cast(int, data.attrib.get('index', NA))
|
||||
self.key = data.attrib.get('key', NA)
|
||||
|
|
|
@ -40,58 +40,50 @@ class Playlist(PlexPartialObject, Playable):
|
|||
return utils.listItems(self.server, path)
|
||||
|
||||
def addItems(self, items):
|
||||
# PUT /playlists/29988/items?uri=library%3A%2F%2F32268d7c-3e8c-4ab5-98ad-bad8a3b78c63%2Fitem%2F%252Flibrary%252Fmetadata%252F801
|
||||
if not isinstance(items, (list, tuple)):
|
||||
items = [items]
|
||||
ratingKeys = []
|
||||
for item in items:
|
||||
if item.__class__.LISTTYPE != self.playlistType:
|
||||
raise BadRequest('Can not mix media types when building a playlist: %s and %s' % (self.playlistType, item.__class__.LISTTYPE))
|
||||
if item.listType != self.playlistType:
|
||||
raise BadRequest('Can not mix media types when building a playlist: %s and %s' % (self.playlistType, item.listType))
|
||||
ratingKeys.append(item.ratingKey)
|
||||
uuid = items[0].section().uuid
|
||||
ratingKeys = ','.join(ratingKeys)
|
||||
path = '%s/items%s' % (self.key, utils.joinArgs({
|
||||
'uri': 'library://__GID__/directory//library/metadata/%s' % ','.join(ratingKeys),
|
||||
'uri': 'library://%s/directory//library/metadata/%s' % (uuid, ratingKeys),
|
||||
}))
|
||||
return self.server.query(path, method=self.server.session.put)
|
||||
|
||||
def removeItem(self, item):
|
||||
# DELETE /playlists/29988/items/4866
|
||||
path = '%s/items/%s' % (self.key, item.playlistItemID)
|
||||
return self.server.query(path, method=self.server.session.delete)
|
||||
|
||||
def moveItem(self, item, after=None):
|
||||
# PUT /playlists/29988/items/4556/move?after=4445
|
||||
# PUT /playlists/29988/items/4556/move (to first item)
|
||||
path = '%s/items/%s/move' % (self.key, item.playlistItemID)
|
||||
if after:
|
||||
path += '?after=%s' % after.playlistItemID
|
||||
if after: path += '?after=%s' % after.playlistItemID
|
||||
return self.server.query(path, method=self.server.session.put)
|
||||
|
||||
def edit(self, title=None, summary=None):
|
||||
# PUT /library/metadata/29988?title=You%20Look%20Like%20Gollum2&summary=foobar
|
||||
path = '/library/metadata/%s%s' % (self.ratingKey, utils.joinArgs({'title':title, 'summary':summary}))
|
||||
return self.server.query(path, method=self.server.session.put)
|
||||
|
||||
def delete(self):
|
||||
# DELETE /library/metadata/29988
|
||||
return self.server.query(self.key, method=self.server.session.delete)
|
||||
|
||||
@classmethod
|
||||
def create(cls, server, title, items):
|
||||
# NOTE: I have not yet figured out what __GID__ is below or where the proper value
|
||||
# can be obtained. However, the good news is passing anything in seems to work.
|
||||
if not isinstance(items, (list, tuple)):
|
||||
items = [items]
|
||||
# collect a list of itemkeys and make sure all items share the same listtype
|
||||
listtype = items[0].__class__.LISTTYPE
|
||||
ratingKeys = []
|
||||
for item in items:
|
||||
if item.__class__.LISTTYPE != listtype:
|
||||
if item.listType != items[0].listType:
|
||||
raise BadRequest('Can not mix media types when building a playlist')
|
||||
ratingKeys.append(item.ratingKey)
|
||||
# build and send the request
|
||||
uuid = items[0].section().uuid
|
||||
ratingKeys = ','.join(ratingKeys)
|
||||
path = '/playlists%s' % utils.joinArgs({
|
||||
'uri': 'library://__GID__/directory//library/metadata/%s' % ','.join(ratingKeys),
|
||||
'type': listtype,
|
||||
'uri': 'library://%s/directory//library/metadata/%s' % (uuid, ratingKeys),
|
||||
'type': items[0].listType,
|
||||
'title': title,
|
||||
'smart': 0
|
||||
})
|
||||
|
|
|
@ -23,11 +23,9 @@ class PlayQueue(object):
|
|||
|
||||
@classmethod
|
||||
def create(cls, server, video, shuffle=0, continuous=0):
|
||||
# TODO: Fix this up, create tests..
|
||||
# NOTE: I have not yet figured out what __GID__ is below or where the proper value
|
||||
# can be obtained. However, the good news is passing anything in seems to work.
|
||||
uuid = video.section().uuid
|
||||
path = '/playQueues%s' % utils.joinArgs({
|
||||
'uri': 'library://__GID__/item/%s' % video.key,
|
||||
'uri': 'library://%s/item/%s' % (uuid, video.key),
|
||||
'key': video.key,
|
||||
'type': 'video',
|
||||
'shuffle': shuffle,
|
||||
|
|
|
@ -37,6 +37,7 @@ class PlexServer(object):
|
|||
self.transcoderActiveVideoSessions = int(data.attrib.get('transcoderActiveVideoSessions', 0))
|
||||
self.updatedAt = int(data.attrib.get('updatedAt', 0))
|
||||
self.version = data.attrib.get('version')
|
||||
self._library = None # cached library
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s:%s>' % (self.__class__.__name__, self.baseurl)
|
||||
|
@ -50,7 +51,9 @@ class PlexServer(object):
|
|||
|
||||
@property
|
||||
def library(self):
|
||||
return Library(self, self.query('/library/'))
|
||||
if not self._library:
|
||||
self._library = Library(self, self.query('/library/'))
|
||||
return self._library
|
||||
|
||||
def account(self):
|
||||
data = self.query('/myplex/account')
|
||||
|
|
|
@ -163,11 +163,14 @@ def findItem(server, path, title):
|
|||
raise NotFound('Unable to find item: %s' % title)
|
||||
|
||||
|
||||
def findLocation(data):
|
||||
elem = data.find('Location')
|
||||
if elem is not None:
|
||||
return elem.attrib.get('path')
|
||||
return None
|
||||
def findLocations(data, single=False):
|
||||
locations = []
|
||||
for elem in data:
|
||||
if elem.tag == 'Location':
|
||||
locations.append(elem.attrib.get('path'))
|
||||
if single:
|
||||
return locations[0] if locations else None
|
||||
return locations
|
||||
|
||||
|
||||
def findPlayer(server, data):
|
||||
|
|
|
@ -14,6 +14,7 @@ class Video(PlexPartialObject):
|
|||
super(Video, self).__init__(data, initpath, server)
|
||||
|
||||
def _loadData(self, data):
|
||||
self.listType = 'video'
|
||||
self.addedAt = utils.toDatetime(data.attrib.get('addedAt', NA))
|
||||
self.key = data.attrib.get('key', NA)
|
||||
self.lastViewedAt = utils.toDatetime(data.attrib.get('lastViewedAt', NA))
|
||||
|
@ -51,11 +52,13 @@ class Video(PlexPartialObject):
|
|||
def refresh(self):
|
||||
self.server.query('%s/refresh' % self.key, method=self.server.session.put)
|
||||
|
||||
def section(self):
|
||||
return self.server.library.sectionByID(self.librarySectionID)
|
||||
|
||||
|
||||
@utils.register_libtype
|
||||
class Movie(Video, Playable):
|
||||
TYPE = 'movie'
|
||||
LISTTYPE = 'video'
|
||||
|
||||
def _loadData(self, data):
|
||||
Video._loadData(self, data)
|
||||
|
@ -102,7 +105,6 @@ class Movie(Video, Playable):
|
|||
@utils.register_libtype
|
||||
class Show(Video):
|
||||
TYPE = 'show'
|
||||
LISTTYPE = 'video'
|
||||
|
||||
def _loadData(self, data):
|
||||
Video._loadData(self, data)
|
||||
|
@ -113,7 +115,7 @@ class Show(Video):
|
|||
self.duration = utils.cast(int, data.attrib.get('duration', NA))
|
||||
self.guid = data.attrib.get('guid', NA)
|
||||
self.leafCount = utils.cast(int, data.attrib.get('leafCount', NA))
|
||||
self.location = utils.findLocation(data)
|
||||
self.location = utils.findLocations(data, single=True)
|
||||
self.originallyAvailableAt = utils.toDatetime(data.attrib.get('originallyAvailableAt', NA), '%Y-%m-%d')
|
||||
self.rating = utils.cast(float, data.attrib.get('rating', NA))
|
||||
self.studio = data.attrib.get('studio', NA)
|
||||
|
@ -164,7 +166,6 @@ class Show(Video):
|
|||
@utils.register_libtype
|
||||
class Season(Video):
|
||||
TYPE = 'season'
|
||||
LISTTYPE = 'video'
|
||||
|
||||
def _loadData(self, data):
|
||||
Video._loadData(self, data)
|
||||
|
@ -201,7 +202,6 @@ class Season(Video):
|
|||
@utils.register_libtype
|
||||
class Episode(Video, Playable):
|
||||
TYPE = 'episode'
|
||||
LISTTYPE = 'video'
|
||||
|
||||
def _loadData(self, data):
|
||||
Video._loadData(self, data)
|
||||
|
|
|
@ -284,12 +284,12 @@ def test_list_playlists(plex, account=None):
|
|||
|
||||
@register('playlist')
|
||||
def test_create_playlist(plex, account=None):
|
||||
try:
|
||||
# create the playlist
|
||||
title = 'test_create_playlist'
|
||||
log(2, 'Creating playlist %s..' % title)
|
||||
episodes = plex.library.section(SHOW_SECTION).get(SHOW_TITLE).episodes()
|
||||
playlist = plex.createPlaylist(title, episodes[:3])
|
||||
try:
|
||||
items = playlist.items()
|
||||
log(4, 'Title: %s' % playlist.title)
|
||||
log(4, 'Items: %s' % items)
|
||||
|
|
Loading…
Reference in a new issue