Skip to content

Commit 319fe52

Browse files
authored
Merge branch 'master' into poster_change
2 parents 2235caf + 83a292a commit 319fe52

File tree

6 files changed

+198
-7
lines changed

6 files changed

+198
-7
lines changed

plexapi/base.py

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -471,6 +471,90 @@ def setArt(self, art):
471471
""" Set :class:`~plexapi.media.Poster` to :class:`~plexapi.video.Video` """
472472
art.select()
473473

474+
def unmatch(self):
475+
""" Unmatches metadata match from object. """
476+
key = '/library/metadata/%s/unmatch' % self.ratingKey
477+
self._server.query(key, method=self._server._session.put)
478+
479+
def matches(self, auto=False, agent=None, title=None, year=None, language=None):
480+
""" Return list of (:class:`~plexapi.media.SearchResult) metadata matches.
481+
482+
Parameters:
483+
auto (bool): True searches for matches automatically
484+
False allows for user to provide additional parameters to search
485+
*Auto searching
486+
agent (str): Agent name to be used (imdb, thetvdb, themoviedb, etc.)
487+
title (str): Title of item to search for
488+
year (str): Year of item to search in
489+
language (str) : Language of item to search in
490+
491+
Examples:
492+
1. video.matches()
493+
2. video.matches(title="something", year=2020)
494+
3. video.matches(title="something")
495+
4. video.matches(year=2020)
496+
5. video.matches(title="something", year="")
497+
6. video.matches(title="", year=2020)
498+
7. video.matches(title="", year="")
499+
500+
1. The default behaviour in Plex Web = no params in plexapi
501+
2. Both title and year specified by user
502+
3. Year automatically filled in
503+
4. Title automatically filled in
504+
5. Explicitly searches for title with blank year
505+
6. Explicitly searches for blank title with year
506+
7. I don't know what the user is thinking... return the same result as 1
507+
508+
For 2 to 7, the agent and language is automatically filled in
509+
"""
510+
key = '/library/metadata/%s/matches' % self.ratingKey
511+
params = {'manual': 1}
512+
513+
if any([agent, title, year, language]):
514+
if title is None:
515+
params['title'] = self.title
516+
else:
517+
params['title'] = title
518+
519+
if year is None:
520+
params['year'] = self.year
521+
else:
522+
params['year'] = year
523+
524+
params['language'] = language or self.section().language
525+
526+
if agent is None:
527+
params['agent'] = self.section().agent
528+
else:
529+
params['agent'] = utils.getAgentIdentifier(self.section(), agent)
530+
531+
key = key + '?' + urlencode(params)
532+
data = self._server.query(key, method=self._server._session.get)
533+
return self.findItems(data)
534+
535+
def fixMatch(self, searchResult=None, auto=False):
536+
""" Use match result to update show metadata.
537+
538+
Parameters:
539+
auto (bool): True uses first match from matches
540+
False allows user to provide the match
541+
*Auto matching
542+
searchResult (:class:`~plexapi.media.SearchResult): Search result from
543+
~plexapi.base.matches()
544+
"""
545+
key = '/library/metadata/%s/match' % self.ratingKey
546+
if auto:
547+
searchResult = self.matches()[0]
548+
elif not searchResult:
549+
raise NotFound('fixMatch() requires either auto=True or '
550+
'searchResult=:class:`~plexapi.media.SearchResult`.')
551+
552+
params = {'guid': searchResult.guid,
553+
'name': searchResult.name}
554+
555+
data = key + '?' + urlencode(params)
556+
self._server.query(data, method=self._server._session.put)
557+
474558
# The photo tag cant be built atm. TODO
475559
# def arts(self):
476560
# part = '%s/arts' % self.key

plexapi/library.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from plexapi.compat import unquote, urlencode, quote_plus
55
from plexapi.media import MediaTag
66
from plexapi.exceptions import BadRequest, NotFound
7+
from plexapi.settings import Setting
78

89

910
class Library(PlexObject):
@@ -365,13 +366,15 @@ def delete(self):
365366
log.error(msg)
366367
raise
367368

368-
def edit(self, **kwargs):
369+
def edit(self, agent=None, **kwargs):
369370
""" Edit a library (Note: agent is required). See :class:`~plexapi.library.Library` for example usage.
370371
371372
Parameters:
372373
kwargs (dict): Dict of settings to edit.
373374
"""
374-
part = '/library/sections/%s?%s' % (self.key, urlencode(kwargs))
375+
if not agent:
376+
agent = self.agent
377+
part = '/library/sections/%s?agent=%s&%s' % (self.key, agent, urlencode(kwargs))
375378
self._server.query(part, method=self._server._session.put)
376379

377380
# Reload this way since the self.key dont have a full path, but is simply a id.
@@ -401,6 +404,17 @@ def all(self, sort=None, **kwargs):
401404
key = '/library/sections/%s/all%s' % (self.key, sortStr)
402405
return self.fetchItems(key, **kwargs)
403406

407+
def agents(self):
408+
""" Returns a list of available `:class:`~plexapi.media.Agent` for this library section.
409+
"""
410+
return self._server.agents(utils.searchType(self.type))
411+
412+
def settings(self):
413+
""" Returns a list of all library settings. """
414+
key = '/library/sections/%s/prefs' % self.key
415+
data = self._server.query(key)
416+
return self.findItems(data, cls=Setting)
417+
404418
def onDeck(self):
405419
""" Returns a list of media items on deck from this library section. """
406420
key = '/library/sections/%s/onDeck' % self.key

plexapi/media.py

Lines changed: 72 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import xml
44

5-
from plexapi import compat, log, utils
5+
from plexapi import compat, log, utils, settings
66
from plexapi.base import PlexObject
77
from plexapi.exceptions import BadRequest
88
from plexapi.utils import cast
@@ -710,3 +710,74 @@ def _loadData(self, data):
710710
self._data = data
711711
self.name = data.attrib.get('name')
712712
self.locked = cast(bool, data.attrib.get('locked'))
713+
714+
715+
@utils.registerPlexObject
716+
class SearchResult(PlexObject):
717+
""" Represents a single SearchResult.
718+
719+
Attributes:
720+
TAG (str): 'SearchResult'
721+
"""
722+
TAG = 'SearchResult'
723+
724+
def __repr__(self):
725+
name = self._clean(self.firstAttr('name'))
726+
score = self._clean(self.firstAttr('score'))
727+
return '<%s>' % ':'.join([p for p in [self.__class__.__name__, name, score] if p])
728+
729+
def _loadData(self, data):
730+
self._data = data
731+
self.guid = data.attrib.get('guid')
732+
self.lifespanEnded = data.attrib.get('lifespanEnded')
733+
self.name = data.attrib.get('name')
734+
self.score = cast(int, data.attrib.get('score'))
735+
self.year = data.attrib.get('year')
736+
737+
738+
@utils.registerPlexObject
739+
class Agent(PlexObject):
740+
""" Represents a single Agent.
741+
742+
Attributes:
743+
TAG (str): 'Agent'
744+
"""
745+
TAG = 'Agent'
746+
747+
def __repr__(self):
748+
uid = self._clean(self.firstAttr('shortIdentifier'))
749+
return '<%s>' % ':'.join([p for p in [self.__class__.__name__, uid] if p])
750+
751+
def _loadData(self, data):
752+
self._data = data
753+
self.hasAttribution = data.attrib.get('hasAttribution')
754+
self.hasPrefs = data.attrib.get('hasPrefs')
755+
self.identifier = data.attrib.get('identifier')
756+
self.primary = data.attrib.get('primary')
757+
self.shortIdentifier = self.identifier.rsplit('.', 1)[1]
758+
if 'mediaType' in self._initpath:
759+
self.name = data.attrib.get('name')
760+
self.languageCode = []
761+
for code in data:
762+
self.languageCode += [code.attrib.get('code')]
763+
else:
764+
self.mediaTypes = [AgentMediaType(server=self._server, data=d) for d in data]
765+
766+
def _settings(self):
767+
key = '/:/plugins/%s/prefs' % self.identifier
768+
data = self._server.query(key)
769+
return self.findItems(data, cls=settings.Setting)
770+
771+
772+
class AgentMediaType(Agent):
773+
774+
def __repr__(self):
775+
uid = self._clean(self.firstAttr('name'))
776+
return '<%s>' % ':'.join([p for p in [self.__class__.__name__, uid] if p])
777+
778+
def _loadData(self, data):
779+
self.mediaType = cast(int, data.attrib.get('mediaType'))
780+
self.name = data.attrib.get('name')
781+
self.languageCode = []
782+
for code in data:
783+
self.languageCode += [code.attrib.get('code')]

plexapi/server.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,13 @@ def account(self):
184184
data = self.query(Account.key)
185185
return Account(self, data)
186186

187+
def agents(self, mediaType=None):
188+
""" Returns the `:class:`~plexapi.media.Agent` objects this server has available. """
189+
key = '/system/agents'
190+
if mediaType:
191+
key += '?mediaType=%s' % mediaType
192+
return self.fetchItems(key)
193+
187194
def createToken(self, type='delegation', scope='all'):
188195
"""Create a temp access token for the server."""
189196
q = self.query('/security/token?type=%s&scope=%s' % (type, scope))

plexapi/settings.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -124,8 +124,8 @@ def _loadData(self, data):
124124
self.enumValues = self._getEnumValues(data)
125125

126126
def _cast(self, value):
127-
""" Cast the specifief value to the type of this setting. """
128-
if self.type != 'text':
127+
""" Cast the specific value to the type of this setting. """
128+
if self.type != 'enum':
129129
value = utils.cast(self.TYPES.get(self.type)['cast'], value)
130130
return value
131131

plexapi/utils.py

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,15 +59,18 @@ def registerPlexObject(cls):
5959

6060
def cast(func, value):
6161
""" Cast the specified value to the specified type (returned by func). Currently this
62-
only support int, float, bool. Should be extended if needed.
62+
only support str, int, float, bool. Should be extended if needed.
6363
6464
Parameters:
6565
func (func): Calback function to used cast to type (int, bool, float).
6666
value (any): value to be cast and returned.
6767
"""
6868
if value is not None:
6969
if func == bool:
70-
return bool(int(value))
70+
try:
71+
return bool(int(value))
72+
except ValueError:
73+
return bool(value)
7174
elif func in (int, float):
7275
try:
7376
return func(value)
@@ -375,3 +378,15 @@ def choose(msg, items, attr): # pragma: no cover
375378

376379
except (ValueError, IndexError):
377380
pass
381+
382+
383+
def getAgentIdentifier(section, agent):
384+
""" Return the full agent identifier from a short identifier, name, or confirm full identifier. """
385+
agents = []
386+
for ag in section.agents():
387+
identifiers = [ag.identifier, ag.shortIdentifier, ag.name]
388+
if agent in identifiers:
389+
return ag.identifier
390+
agents += identifiers
391+
raise NotFound('Couldnt find "%s" in agents list (%s)' %
392+
(agent, ', '.join(agents)))

0 commit comments

Comments
 (0)