Update creating smart playlist using search filters

This commit is contained in:
JonnyWong16 2021-05-27 19:53:38 -07:00
parent 005cbb5916
commit a131482cdc
No known key found for this signature in database
GPG key ID: B1F1F9807184697A
3 changed files with 113 additions and 91 deletions

View file

@ -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.

View file

@ -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.

View file

@ -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`.