mirror of
https://github.com/pkkid/python-plexapi
synced 2024-11-10 06:04:15 +00:00
Update creating smart playlist using search filters
This commit is contained in:
parent
005cbb5916
commit
a131482cdc
3 changed files with 113 additions and 91 deletions
|
@ -966,6 +966,37 @@ class LibrarySection(PlexObject):
|
|||
|
||||
return validatedFilters
|
||||
|
||||
def _buildSearchKey(self, title=None, sort=None, libtype=None, filters=None, returnKwargs=False, **kwargs):
|
||||
""" Returns the validated and formatted search query API key
|
||||
(``/library/sections/<sectionKey>/all?<params>``).
|
||||
"""
|
||||
args = {}
|
||||
filter_args = []
|
||||
for field, values in list(kwargs.items()):
|
||||
if field.split('__')[-1] not in OPERATORS:
|
||||
filter_args.append(self._validateFilterField(field, values, libtype))
|
||||
del kwargs[field]
|
||||
if title is not None:
|
||||
if isinstance(title, (list, tuple)):
|
||||
filter_args.append(self._validateFilterField('title', title, libtype))
|
||||
else:
|
||||
args['title'] = title
|
||||
if filters is not None:
|
||||
filter_args.extend(self._validateAdvancedSearch(filters, libtype))
|
||||
if sort is not None:
|
||||
args['sort'] = self._validateSortFields(sort, libtype)
|
||||
if libtype is not None:
|
||||
args['type'] = utils.searchType(libtype)
|
||||
|
||||
joined_args = utils.joinArgs(args).lstrip('?')
|
||||
joined_filter_args = '&'.join(filter_args) if filter_args else ''
|
||||
params = '&'.join([joined_args, joined_filter_args]).strip('&')
|
||||
key = '/library/sections/%s/all?%s' % (self.key, params)
|
||||
|
||||
if returnKwargs:
|
||||
return key, kwargs
|
||||
return key
|
||||
|
||||
def hubSearch(self, query, mediatype=None, limit=None):
|
||||
""" Returns the hub search results for this library. See :func:`plexapi.server.PlexServer.search`
|
||||
for details and parameters.
|
||||
|
@ -1205,30 +1236,8 @@ class LibrarySection(PlexObject):
|
|||
library.search(genre="holiday", viewCount__gte=3)
|
||||
|
||||
"""
|
||||
# cleanup the core arguments
|
||||
args = {}
|
||||
filter_args = []
|
||||
for field, values in list(kwargs.items()):
|
||||
if field.split('__')[-1] not in OPERATORS:
|
||||
filter_args.append(self._validateFilterField(field, values, libtype))
|
||||
del kwargs[field]
|
||||
if title is not None:
|
||||
if isinstance(title, (list, tuple)):
|
||||
filter_args.append(self._validateFilterField('title', title, libtype))
|
||||
else:
|
||||
args['title'] = title
|
||||
if filters is not None:
|
||||
filter_args.extend(self._validateAdvancedSearch(filters, libtype))
|
||||
if sort is not None:
|
||||
args['sort'] = self._validateSortFields(sort, libtype)
|
||||
if libtype is not None:
|
||||
args['type'] = utils.searchType(libtype)
|
||||
|
||||
joined_args = utils.joinArgs(args).lstrip('?')
|
||||
joined_filter_args = '&'.join(filter_args) if filter_args else ''
|
||||
params = '&'.join([joined_args, joined_filter_args]).strip('&')
|
||||
key = '/library/sections/%s/all?%s' % (self.key, params)
|
||||
|
||||
key, kwargs = self._buildSearchKey(
|
||||
title=title, sort=sort, libtype=libtype, filters=filters, returnKwargs=True, **kwargs)
|
||||
return self._search(key, maxresults, container_start, container_size, **kwargs)
|
||||
|
||||
def _search(self, key, maxresults, container_start, container_size, **kwargs):
|
||||
|
@ -1326,15 +1335,6 @@ class LibrarySection(PlexObject):
|
|||
if not self.allowSync:
|
||||
raise BadRequest('The requested library is not allowed to sync')
|
||||
|
||||
args = {}
|
||||
filter_args = []
|
||||
for field, values in kwargs.items():
|
||||
filter_args.append(self._validateFilterField(field, values, libtype))
|
||||
if sort is not None:
|
||||
args['sort'] = self._validateSortFields(sort, libtype)
|
||||
if libtype is not None:
|
||||
args['type'] = utils.searchType(libtype)
|
||||
|
||||
myplex = self._server.myPlexAccount()
|
||||
sync_item = SyncItem(self._server, None)
|
||||
sync_item.title = title if title else self.title
|
||||
|
@ -1343,10 +1343,7 @@ class LibrarySection(PlexObject):
|
|||
sync_item.metadataType = self.METADATA_TYPE
|
||||
sync_item.machineIdentifier = self._server.machineIdentifier
|
||||
|
||||
joined_args = utils.joinArgs(args).lstrip('?')
|
||||
joined_filter_args = '&'.join(filter_args) if filter_args else ''
|
||||
params = '&'.join([joined_args, joined_filter_args]).strip('&')
|
||||
key = '/library/sections/%s/all?%s' % (self.key, params)
|
||||
key = self._buildSearchKey(title=title, sort=sort, libtype=libtype, **kwargs)
|
||||
|
||||
sync_item.location = 'library://%s/directory/%s' % (self.uuid, quote_plus(key))
|
||||
sync_item.policy = policy
|
||||
|
@ -1453,7 +1450,6 @@ class ShowSection(LibrarySection):
|
|||
TAG (str): 'Directory'
|
||||
TYPE (str): 'show'
|
||||
"""
|
||||
|
||||
TAG = 'Directory'
|
||||
TYPE = 'show'
|
||||
METADATA_TYPE = 'episode'
|
||||
|
@ -1540,9 +1536,8 @@ class MusicSection(LibrarySection):
|
|||
"""
|
||||
TAG = 'Directory'
|
||||
TYPE = 'artist'
|
||||
|
||||
CONTENT_TYPE = 'audio'
|
||||
METADATA_TYPE = 'track'
|
||||
CONTENT_TYPE = 'audio'
|
||||
|
||||
def albums(self):
|
||||
""" Returns a list of :class:`~plexapi.audio.Album` objects in this section. """
|
||||
|
@ -1634,8 +1629,8 @@ class PhotoSection(LibrarySection):
|
|||
"""
|
||||
TAG = 'Directory'
|
||||
TYPE = 'photo'
|
||||
CONTENT_TYPE = 'photo'
|
||||
METADATA_TYPE = 'photo'
|
||||
CONTENT_TYPE = 'photo'
|
||||
|
||||
def all(self, libtype=None, **kwargs):
|
||||
""" Returns a list of all items from this library section.
|
||||
|
|
|
@ -172,22 +172,27 @@ class Playlist(PlexPartialObject, Playable, ArtMixin, PosterMixin):
|
|||
|
||||
@classmethod
|
||||
def _create(cls, server, title, items):
|
||||
""" Create a playlist. """
|
||||
""" Create a regular playlist. """
|
||||
if not items:
|
||||
raise BadRequest('Must include items to add when creating new playlist')
|
||||
|
||||
if items and not isinstance(items, (list, tuple)):
|
||||
items = [items]
|
||||
|
||||
listType = items[0].listType
|
||||
ratingKeys = []
|
||||
for item in items:
|
||||
if item.listType != items[0].listType: # pragma: no cover
|
||||
if item.listType != listType: # pragma: no cover
|
||||
raise BadRequest('Can not mix media types when building a playlist')
|
||||
ratingKeys.append(str(item.ratingKey))
|
||||
|
||||
ratingKeys = ','.join(ratingKeys)
|
||||
uuid = items[0].section().uuid
|
||||
uuid = server.machineIdentifier
|
||||
uri = 'server://%s/com.plexapp.plugins.library/library/metadata/%s' % (uuid, ratingKeys)
|
||||
|
||||
key = '/playlists%s' % utils.joinArgs({
|
||||
'uri': 'library://%s/directory//library/metadata/%s' % (uuid, ratingKeys),
|
||||
'type': items[0].listType,
|
||||
'uri': uri,
|
||||
'type': listType,
|
||||
'title': title,
|
||||
'smart': 0
|
||||
})
|
||||
|
@ -195,54 +200,19 @@ class Playlist(PlexPartialObject, Playable, ArtMixin, PosterMixin):
|
|||
return cls(server, data, initpath=key)
|
||||
|
||||
@classmethod
|
||||
def create(cls, server, title, items=None, section=None, limit=None, smart=False, **kwargs):
|
||||
"""Create a playlist.
|
||||
|
||||
Parameters:
|
||||
server (:class:`~plexapi.server.PlexServer`): Server your connected to.
|
||||
title (str): Title of the playlist.
|
||||
items (Iterable): Iterable of objects that should be in the playlist.
|
||||
section (:class:`~plexapi.library.LibrarySection`, str):
|
||||
limit (int): default None.
|
||||
smart (bool): default False.
|
||||
|
||||
**kwargs (dict): is passed to the filters. For a example see the search method.
|
||||
|
||||
Raises:
|
||||
:class:`plexapi.exceptions.BadRequest`: when no items are included in create request.
|
||||
|
||||
Returns:
|
||||
:class:`~plexapi.playlist.Playlist`: an instance of created Playlist.
|
||||
"""
|
||||
if smart:
|
||||
return cls._createSmart(server, title, section, limit, **kwargs)
|
||||
|
||||
else:
|
||||
return cls._create(server, title, items)
|
||||
|
||||
@classmethod
|
||||
def _createSmart(cls, server, title, section, limit=None, **kwargs):
|
||||
def _createSmart(cls, server, title, section, limit=None, sort=None, filters=None, **kwargs):
|
||||
""" Create a Smart playlist. """
|
||||
|
||||
if not isinstance(section, LibrarySection):
|
||||
section = server.library.section(section)
|
||||
|
||||
sectionType = utils.searchType(section.type)
|
||||
sectionId = section.key
|
||||
uuid = section.uuid
|
||||
uri = 'library://%s/directory//library/sections/%s/all?type=%s' % (uuid,
|
||||
sectionId,
|
||||
sectionType)
|
||||
searchKey = section._buildSearchKey(
|
||||
sort=sort, libtype=section.METADATA_TYPE, filters=filters, **kwargs)
|
||||
uuid = server.machineIdentifier
|
||||
uri = 'server://%s/com.plexapp.plugins.library%s' % (uuid, searchKey)
|
||||
|
||||
if limit:
|
||||
uri = uri + '&limit=%s' % str(limit)
|
||||
|
||||
for category, value in kwargs.items():
|
||||
sectionChoices = section.listFilterChoices(category)
|
||||
for choice in sectionChoices:
|
||||
if str(choice.title).lower() == str(value).lower():
|
||||
uri = uri + '&%s=%s' % (category.lower(), str(choice.key))
|
||||
|
||||
uri = uri + '&sourceType=%s' % sectionType
|
||||
key = '/playlists%s' % utils.joinArgs({
|
||||
'uri': uri,
|
||||
'type': section.CONTENT_TYPE,
|
||||
|
@ -252,6 +222,41 @@ class Playlist(PlexPartialObject, Playable, ArtMixin, PosterMixin):
|
|||
data = server.query(key, method=server._session.post)[0]
|
||||
return cls(server, data, initpath=key)
|
||||
|
||||
@classmethod
|
||||
def create(cls, server, title, items=None, section=None, limit=None, smart=False,
|
||||
sort=None, filters=None, **kwargs):
|
||||
"""Create a playlist.
|
||||
|
||||
Parameters:
|
||||
server (:class:`~plexapi.server.PlexServer`): Server to create the playlist on.
|
||||
title (str): Title of the playlist.
|
||||
items (List<:class:`~plexapi.audio.Audio`> or List<:class:`~plexapi.video.Video`>
|
||||
or List<:class:`~plexapi.photo.Photo`>): Regular playlists only, list of audio,
|
||||
video, or photo objects to be added to the playlist.
|
||||
smart (bool): True to create a smart playlist. Default False.
|
||||
section (:class:`~plexapi.library.LibrarySection`, str): Smart playlists only,
|
||||
library section to create the playlist in.
|
||||
limit (int): Smart playlists only, limit the number of items in the playlist.
|
||||
sort (str or list, optional): Smart playlists only, a string of comma separated sort fields
|
||||
or a list of sort fields in the format ``column:dir``.
|
||||
See :func:`plexapi.library.LibrarySection.search` for more info.
|
||||
filters (dict): Smart playlists only, a dictionary of advanced filters.
|
||||
See :func:`plexapi.library.LibrarySection.search` for more info.
|
||||
**kwargs (dict): Smart playlists only, additional custom filters to apply to the
|
||||
search results. See :func:`plexapi.library.LibrarySection.search` for more info.
|
||||
|
||||
Raises:
|
||||
:class:`plexapi.exceptions.BadRequest`: When no items are included to create the playlist.
|
||||
:class:`plexapi.exceptions.BadRequest`: When mixing media types in the playlist.
|
||||
|
||||
Returns:
|
||||
:class:`~plexapi.playlist.Playlist`: An instance of created Playlist.
|
||||
"""
|
||||
if smart:
|
||||
return cls._createSmart(server, title, section, limit, sort, filters, **kwargs)
|
||||
else:
|
||||
return cls._create(server, title, items)
|
||||
|
||||
def copyToUser(self, user):
|
||||
""" Copy playlist to another user account.
|
||||
|
||||
|
|
|
@ -391,14 +391,36 @@ class PlexServer(PlexObject):
|
|||
|
||||
raise NotFound('Unknown client name: %s' % name)
|
||||
|
||||
def createPlaylist(self, title, items=None, section=None, limit=None, smart=None, **kwargs):
|
||||
def createPlaylist(self, title, items=None, section=None, limit=None, smart=False,
|
||||
sort=None, filters=None, **kwargs):
|
||||
""" Creates and returns a new :class:`~plexapi.playlist.Playlist`.
|
||||
|
||||
Parameters:
|
||||
title (str): Title of the playlist to be created.
|
||||
items (list<Media>): List of media items to include in the playlist.
|
||||
title (str): Title of the playlist.
|
||||
items (List<:class:`~plexapi.audio.Audio`> or List<:class:`~plexapi.video.Video`>
|
||||
or List<:class:`~plexapi.photo.Photo`>): Regular playlists only, list of audio,
|
||||
video, or photo objects to be added to the playlist.
|
||||
smart (bool): True to create a smart playlist. Default False.
|
||||
section (:class:`~plexapi.library.LibrarySection`, str): Smart playlists only,
|
||||
library section to create the playlist in.
|
||||
limit (int): Smart playlists only, limit the number of items in the playlist.
|
||||
sort (str or list, optional): Smart playlists only, a string of comma separated sort fields
|
||||
or a list of sort fields in the format ``column:dir``.
|
||||
See :func:`plexapi.library.LibrarySection.search` for more info.
|
||||
filters (dict): Smart playlists only, a dictionary of advanced filters.
|
||||
See :func:`plexapi.library.LibrarySection.search` for more info.
|
||||
**kwargs (dict): Smart playlists only, additional custom filters to apply to the
|
||||
search results. See :func:`plexapi.library.LibrarySection.search` for more info.
|
||||
|
||||
Raises:
|
||||
:class:`plexapi.exceptions.BadRequest`: When no items are included to create the playlist.
|
||||
:class:`plexapi.exceptions.BadRequest`: When mixing media types in the playlist.
|
||||
|
||||
Returns:
|
||||
:class:`~plexapi.playlist.Playlist`: A new instance of the created Playlist.
|
||||
"""
|
||||
return Playlist.create(self, title, items=items, limit=limit, section=section, smart=smart, **kwargs)
|
||||
return Playlist.create(self, title, items=items, section=section, limit=limit, smart=smart,
|
||||
sort=sort, filters=filters, **kwargs)
|
||||
|
||||
def createPlayQueue(self, item, **kwargs):
|
||||
""" Creates and returns a new :class:`~plexapi.playqueue.PlayQueue`.
|
||||
|
|
Loading…
Reference in a new issue