mirror of
https://github.com/pkkid/python-plexapi
synced 2024-11-10 06:04:15 +00:00
Cleanup search a bit; Get existing tests passing and add a few new search tests
This commit is contained in:
parent
b10faf8560
commit
d63339bd24
3 changed files with 63 additions and 43 deletions
|
@ -1,33 +1,10 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
PlexLibrary
|
||||
|
||||
# --- SEARCH ---
|
||||
# type=1
|
||||
# sort=column:[asc|desc]
|
||||
# -> column in {addedAt,originallyAvailableAt,lastViewedAt,titleSort,rating,mediaHeight,duration}
|
||||
# unwatched=1
|
||||
# duplicate=1
|
||||
# year=yyyy,yyyy,yyyy
|
||||
# decade=<key>,<key>
|
||||
# genre=<id>,<id>
|
||||
# contentRating=<key>,<key>
|
||||
# collection=<id>,<id>
|
||||
# director=<id>,<id>
|
||||
# actor=<id>,<id>
|
||||
# studio=<key>,<key>
|
||||
# resolution=720,480,sd ??
|
||||
# X-Plex-Container-Start=0
|
||||
# X-Plex-Container-Size=0
|
||||
|
||||
# --- CANNED ---
|
||||
# /library/sections/1/onDeck
|
||||
# /library/sections/1/recentlyViewed
|
||||
# /library/sections/1/all?sort=addedAt:desc
|
||||
|
||||
"""
|
||||
from plexapi import log, utils
|
||||
from plexapi import X_PLEX_CONTAINER_SIZE
|
||||
from plexapi.media import MediaTag
|
||||
from plexapi.exceptions import BadRequest, NotFound
|
||||
|
||||
|
||||
|
@ -83,7 +60,8 @@ class Library(object):
|
|||
""" Searching within a library section is much more powerful. It seems certain attributes on
|
||||
the media objects can be targeted to filter this search down a bit, but I havent found the
|
||||
documentation for it. For example: "studio=Comedy%20Central" or "year=1999" "title=Kung Fu"
|
||||
all work.
|
||||
all work. Other items such as actor=<id> seem to work, but require you already know the id
|
||||
of the actor.
|
||||
"""
|
||||
# TODO: FIGURE THIS OUT!
|
||||
args = {}
|
||||
|
@ -148,7 +126,7 @@ class LibrarySection(object):
|
|||
args[category] = self._cleanSearchFilter(subcategory, value)
|
||||
if libtype is not None: args['type'] = utils.searchType(libtype)
|
||||
query = '/library/sections/%s/%s%s' % (self.key, category, utils.joinArgs(args))
|
||||
return utils.listItems(self.server, query)
|
||||
return utils.listItems(self.server, query, bytag=True)
|
||||
|
||||
def search(self, title=None, sort=None, maxresults=999999, libtype=None, **kwargs):
|
||||
""" Search the library. If there are many results, they will be fetched from the server
|
||||
|
@ -205,26 +183,28 @@ class LibrarySection(object):
|
|||
self.server.query('/library/sections/%s/refresh' % self.key)
|
||||
|
||||
def _cleanSearchFilter(self, category, value, libtype=None):
|
||||
result = set()
|
||||
# check a few things before we begin
|
||||
if category not in self.ALLOWED_FILTERS:
|
||||
raise BadRequest('Unknown filter category: %s' % category)
|
||||
if category in self.BOOLEAN_FILTERS:
|
||||
return '1' if value else '0'
|
||||
if not isinstance(value, (list, tuple)):
|
||||
value = [value]
|
||||
# convert list of values to list of keys or ids
|
||||
result = set()
|
||||
choices = self.listChoices(category, libtype)
|
||||
lookup = {c.title.lower():c.key for c in choices}
|
||||
allowed = set(c.key for c in choices)
|
||||
for dirtykey in value:
|
||||
dirtykey = str(dirtykey).lower()
|
||||
if dirtykey in allowed:
|
||||
result.add(dirtykey); continue
|
||||
if dirtykey in lookup:
|
||||
result.add(lookup[dirtykey]); continue
|
||||
for key in [k for t,k in lookup.items() if dirtykey in t]:
|
||||
result.add(key)
|
||||
if not result:
|
||||
log.warning('No known filter values: %s; Will probably yield no results.' % ', '.join(value))
|
||||
for item in value:
|
||||
item = str(item.id if isinstance(item, MediaTag) else item).lower()
|
||||
# find most logical choice(s) to use in url
|
||||
if item in allowed: result.add(item); continue
|
||||
if item in lookup: result.add(lookup[item]); continue
|
||||
matches = [k for t,k in lookup.items() if item in t]
|
||||
if matches: map(result.add, matches); continue
|
||||
# nothing matched; use raw item value
|
||||
log.warning('Filter value not listed, using raw item value: %s' % item)
|
||||
result.add(item)
|
||||
return ','.join(result)
|
||||
|
||||
def _cleanSearchSort(self, sort):
|
||||
|
@ -282,7 +262,9 @@ class FilterChoice(object):
|
|||
self.initpath = initpath
|
||||
self.fastKey = data.attrib.get('fastKey')
|
||||
self.key = data.attrib.get('key')
|
||||
self.thumb = data.attrib.get('thumb')
|
||||
self.title = data.attrib.get('title')
|
||||
self.type = data.attrib.get('type')
|
||||
|
||||
def __repr__(self):
|
||||
title = self.title.replace(' ','.')[0:20]
|
||||
|
|
|
@ -47,7 +47,7 @@ class PlexPartialObject(object):
|
|||
|
||||
def __repr__(self):
|
||||
title = self.title.replace(' ','.')[0:20]
|
||||
return '<%s:%s:%s>' % (self.__class__.__name__, self.key, title.encode('utf8'))
|
||||
return '<%s:%s:%s>' % (self.__class__.__name__, self.ratingKey, title.encode('utf8'))
|
||||
|
||||
def __getattr__(self, attr):
|
||||
if self.isPartialObject():
|
||||
|
@ -144,8 +144,8 @@ class PlexPartialObject(object):
|
|||
self._loadData(data[0])
|
||||
|
||||
|
||||
def buildItem(server, elem, initpath):
|
||||
libtype = elem.attrib.get('type') or elem.tag
|
||||
def buildItem(server, elem, initpath, bytag=False):
|
||||
libtype = elem.tag if bytag else elem.attrib.get('type')
|
||||
if libtype in LIBRARY_TYPES:
|
||||
cls = LIBRARY_TYPES[libtype]
|
||||
return cls(server, elem, initpath)
|
||||
|
@ -182,6 +182,14 @@ def findItem(server, path, title):
|
|||
raise NotFound('Unable to find item: %s' % title)
|
||||
|
||||
|
||||
def isInt(string):
|
||||
try:
|
||||
int(string)
|
||||
return True
|
||||
except ValueError:
|
||||
return False
|
||||
|
||||
|
||||
def joinArgs(args):
|
||||
if not args: return ''
|
||||
arglist = []
|
||||
|
@ -195,14 +203,14 @@ def listChoices(server, path):
|
|||
return {c.attrib['title']:c.attrib['key'] for c in server.query(path)}
|
||||
|
||||
|
||||
def listItems(server, path, libtype=None, watched=None):
|
||||
def listItems(server, path, libtype=None, watched=None, bytag=False):
|
||||
items = []
|
||||
for elem in server.query(path):
|
||||
if libtype and elem.attrib.get('type') != libtype: continue
|
||||
if watched is True and elem.attrib.get('viewCount', 0) == 0: continue
|
||||
if watched is False and elem.attrib.get('viewCount', 0) >= 1: continue
|
||||
try:
|
||||
items.append(buildItem(server, elem, path))
|
||||
items.append(buildItem(server, elem, path, bytag))
|
||||
except UnknownType:
|
||||
pass
|
||||
return items
|
||||
|
|
|
@ -109,10 +109,40 @@ def test_search_audio(plex, user=None):
|
|||
assert result_server == result_library == result_music, 'Audio searches not consistent.'
|
||||
|
||||
|
||||
@register('search,audio')
|
||||
def test_search_related(plex, user=None):
|
||||
movies = plex.library.section(MOVIE_SECTION)
|
||||
movie = movies.get(MOVIE_TITLE)
|
||||
related_by_actors = movies.search(actor=movie.actors, maxresults=3)
|
||||
log(2, u'Actors: %s..' % movie.actors)
|
||||
log(2, u'Related by Actors: %s..' % related_by_actors)
|
||||
assert related_by_actors, 'No related movies found by actor.'
|
||||
related_by_genre = movies.search(genre=movie.genres, maxresults=3)
|
||||
log(2, u'Genres: %s..' % movie.genres)
|
||||
log(2, u'Related by Genre: %s..' % related_by_genre)
|
||||
assert related_by_genre, 'No related movies found by genre.'
|
||||
related_by_director = movies.search(director=movie.directors, maxresults=3)
|
||||
log(2, 'Directors: %s..' % movie.directors)
|
||||
log(2, 'Related by Director: %s..' % related_by_director)
|
||||
assert related_by_director, 'No related movies found by director.'
|
||||
|
||||
|
||||
@register('search,show')
|
||||
def test_crazy_search(plex, user=None):
|
||||
movies = plex.library.section(MOVIE_SECTION)
|
||||
print(movies.search(duplicate=True))
|
||||
movie = movies.get('Jurassic World')
|
||||
log(2, u'Search by Actor: "Chris Pratt"')
|
||||
assert movie in movies.search(actor='Chris Pratt'), 'Unable to search movie by actor.'
|
||||
log(2, u'Search by Director: ["Trevorrow"]')
|
||||
assert movie in movies.search(director=['Trevorrow']), 'Unable to search movie by director.'
|
||||
log(2, u'Search by Year: ["2014", "2015"]')
|
||||
assert movie in movies.search(year=['2014', '2015']), 'Unable to search movie by year.'
|
||||
log(2, u'Filter by Year: 2014')
|
||||
assert movie not in movies.search(year=2014), 'Unable to filter movie by year.'
|
||||
judy = [a for a in movie.actors if 'Judy' in a.tag][0]
|
||||
log(2, u'Search by Unpopular Actor: %s' % judy)
|
||||
assert movie in movies.search(actor=judy.id), 'Unable to filter movie by year.'
|
||||
|
||||
|
||||
|
||||
#-----------------------
|
||||
|
|
Loading…
Reference in a new issue