Add Themes Support (#879)

* Fix typo in 'grandparentThumb'

* Add Themes support

* Fix Themes for Seasons and Episodes

Themes are not available for Seasons or Episodes according to this: https://web.archive.org/web/20150113085312/http://dev.plexapp.com/docs/agents/models.html

* Add Themes to Artists, Albums, and Tracks

Themes are available for Artists according to this: https://web.archive.org/web/20150113085312/http://dev.plexapp.com/docs/agents/models.html

* Update per PR review

* Update BaseResource and Theme classes

Co-authored-by: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com>
This commit is contained in:
ReenigneArcher 2022-02-26 21:47:54 -05:00 committed by GitHub
parent 2f101e3899
commit 9a5170c2fb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 98 additions and 31 deletions

View file

@ -5,9 +5,11 @@ from urllib.parse import quote_plus
from plexapi import library, media, utils
from plexapi.base import Playable, PlexPartialObject
from plexapi.exceptions import BadRequest
from plexapi.mixins import AdvancedSettingsMixin, ArtUrlMixin, ArtMixin, PosterUrlMixin, PosterMixin
from plexapi.mixins import AdvancedSettingsMixin, ArtUrlMixin, ArtMixin, PosterUrlMixin, PosterMixin, ThemeMixin, \
ThemeUrlMixin
from plexapi.mixins import RatingMixin, SplitMergeMixin, UnmatchMatchMixin
from plexapi.mixins import CollectionMixin, CountryMixin, GenreMixin, LabelMixin, MoodMixin, SimilarArtistMixin, StyleMixin
from plexapi.mixins import CollectionMixin, CountryMixin, GenreMixin, LabelMixin, MoodMixin, SimilarArtistMixin, \
StyleMixin
from plexapi.playlist import Playlist
@ -125,8 +127,9 @@ class Audio(PlexPartialObject):
@utils.registerPlexObject
class Artist(Audio, AdvancedSettingsMixin, ArtMixin, PosterMixin, RatingMixin, SplitMergeMixin, UnmatchMatchMixin,
CollectionMixin, CountryMixin, GenreMixin, LabelMixin, MoodMixin, SimilarArtistMixin, StyleMixin):
class Artist(Audio, AdvancedSettingsMixin, ArtMixin, PosterMixin, ThemeMixin, RatingMixin, SplitMergeMixin,
UnmatchMatchMixin, CollectionMixin, CountryMixin, GenreMixin, LabelMixin, MoodMixin,
SimilarArtistMixin, StyleMixin):
""" Represents a single Artist.
Attributes:
@ -142,6 +145,7 @@ class Artist(Audio, AdvancedSettingsMixin, ArtMixin, PosterMixin, RatingMixin, S
locations (List<str>): List of folder paths where the artist is found on disk.
similar (List<:class:`~plexapi.media.Similar`>): List of similar objects.
styles (List<:class:`~plexapi.media.Style`>): List of style objects.
theme (str): URL to theme resource (/library/metadata/<ratingkey>/theme/<themeid>).
"""
TAG = 'Directory'
TYPE = 'artist'
@ -158,6 +162,7 @@ class Artist(Audio, AdvancedSettingsMixin, ArtMixin, PosterMixin, RatingMixin, S
self.locations = self.listAttrs(data, 'path', etag='Location')
self.similar = self.findItems(data, media.Similar)
self.styles = self.findItems(data, media.Style)
self.theme = data.attrib.get('theme')
def __iter__(self):
for album in self.albums():
@ -232,8 +237,8 @@ class Artist(Audio, AdvancedSettingsMixin, ArtMixin, PosterMixin, RatingMixin, S
@utils.registerPlexObject
class Album(Audio, ArtMixin, PosterMixin, RatingMixin, UnmatchMatchMixin,
CollectionMixin, GenreMixin, LabelMixin, MoodMixin, StyleMixin):
class Album(Audio, ArtMixin, PosterMixin, ThemeUrlMixin, RatingMixin, UnmatchMatchMixin,
CollectionMixin, GenreMixin, LabelMixin, MoodMixin, StyleMixin):
""" Represents a single Album.
Attributes:
@ -250,6 +255,7 @@ class Album(Audio, ArtMixin, PosterMixin, RatingMixin, UnmatchMatchMixin,
parentGuid (str): Plex GUID for the album artist (plex://artist/5d07bcb0403c64029053ac4c).
parentKey (str): API URL of the album artist (/library/metadata/<parentRatingKey>).
parentRatingKey (int): Unique key identifying the album artist.
parentTheme (str): URL to artist theme resource (/library/metadata/<parentRatingkey>/theme/<themeid>).
parentThumb (str): URL to album artist thumbnail image (/library/metadata/<parentRatingKey>/thumb/<thumbid>).
parentTitle (str): Name of the album artist.
rating (float): Album rating (7.9; 9.8; 8.1).
@ -276,6 +282,7 @@ class Album(Audio, ArtMixin, PosterMixin, RatingMixin, UnmatchMatchMixin,
self.parentGuid = data.attrib.get('parentGuid')
self.parentKey = data.attrib.get('parentKey')
self.parentRatingKey = utils.cast(int, data.attrib.get('parentRatingKey'))
self.parentTheme = data.attrib.get('parentTheme')
self.parentThumb = data.attrib.get('parentThumb')
self.parentTitle = data.attrib.get('parentTitle')
self.rating = utils.cast(float, data.attrib.get('rating'))
@ -339,7 +346,7 @@ class Album(Audio, ArtMixin, PosterMixin, RatingMixin, UnmatchMatchMixin,
@utils.registerPlexObject
class Track(Audio, Playable, ArtUrlMixin, PosterUrlMixin, RatingMixin,
class Track(Audio, Playable, ArtUrlMixin, PosterUrlMixin, ThemeUrlMixin, RatingMixin,
CollectionMixin, LabelMixin, MoodMixin):
""" Represents a single Track.
@ -353,6 +360,8 @@ class Track(Audio, Playable, ArtUrlMixin, PosterUrlMixin, RatingMixin,
grandparentGuid (str): Plex GUID for the album artist (plex://artist/5d07bcb0403c64029053ac4c).
grandparentKey (str): API URL of the album artist (/library/metadata/<grandparentRatingKey>).
grandparentRatingKey (int): Unique key identifying the album artist.
grandparentTheme (str): URL to artist theme resource (/library/metadata/<grandparentRatingkey>/theme/<themeid>).
(/library/metadata/<grandparentRatingkey>/theme/<themeid>).
grandparentThumb (str): URL to album artist thumbnail image
(/library/metadata/<grandparentRatingKey>/thumb/<thumbid>).
grandparentTitle (str): Name of the album artist for the track.
@ -384,6 +393,7 @@ class Track(Audio, Playable, ArtUrlMixin, PosterUrlMixin, RatingMixin,
self.grandparentGuid = data.attrib.get('grandparentGuid')
self.grandparentKey = data.attrib.get('grandparentKey')
self.grandparentRatingKey = utils.cast(int, data.attrib.get('grandparentRatingKey'))
self.grandparentTheme = data.attrib.get('grandparentTheme')
self.grandparentThumb = data.attrib.get('grandparentThumb')
self.grandparentTitle = data.attrib.get('grandparentTitle')
self.labels = self.findItems(data, media.Label)

View file

@ -5,14 +5,15 @@ from plexapi import media, utils
from plexapi.base import PlexPartialObject
from plexapi.exceptions import BadRequest, NotFound, Unsupported
from plexapi.library import LibrarySection
from plexapi.mixins import AdvancedSettingsMixin, ArtMixin, PosterMixin, RatingMixin
from plexapi.mixins import AdvancedSettingsMixin, ArtMixin, PosterMixin, ThemeMixin, RatingMixin
from plexapi.mixins import LabelMixin, SmartFilterMixin
from plexapi.playqueue import PlayQueue
from plexapi.utils import deprecated
@utils.registerPlexObject
class Collection(PlexPartialObject, AdvancedSettingsMixin, ArtMixin, PosterMixin, RatingMixin, LabelMixin, SmartFilterMixin):
class Collection(PlexPartialObject, AdvancedSettingsMixin, ArtMixin, PosterMixin, ThemeMixin, RatingMixin,
LabelMixin, SmartFilterMixin):
""" Represents a single Collection.
Attributes:
@ -43,6 +44,7 @@ class Collection(PlexPartialObject, AdvancedSettingsMixin, ArtMixin, PosterMixin
smart (bool): True if the collection is a smart collection.
subtype (str): Media type of the items in the collection (movie, show, artist, or album).
summary (str): Summary of the collection.
theme (str): URL to theme resource (/library/metadata/<ratingkey>/theme/<themeid>).
thumb (str): URL to thumbnail image (/library/metadata/<ratingKey>/thumb/<thumbid>).
thumbBlurHash (str): BlurHash string for thumbnail image.
title (str): Name of the collection.
@ -81,6 +83,7 @@ class Collection(PlexPartialObject, AdvancedSettingsMixin, ArtMixin, PosterMixin
self.smart = utils.cast(bool, data.attrib.get('smart', '0'))
self.subtype = data.attrib.get('subtype')
self.summary = data.attrib.get('summary')
self.theme = data.attrib.get('theme')
self.thumb = data.attrib.get('thumb')
self.thumbBlurHash = data.attrib.get('thumbBlurHash')
self.title = data.attrib.get('title')

View file

@ -917,19 +917,17 @@ class Review(PlexObject):
self.text = data.attrib.get('text')
class BaseImage(PlexObject):
""" Base class for all Art, Banner, and Poster objects.
class BaseResource(PlexObject):
""" Base class for all Art, Banner, Poster, and Theme objects.
Attributes:
TAG (str): 'Photo'
TAG (str): 'Photo' or 'Track'
key (str): API URL (/library/metadata/<ratingkey>).
provider (str): The source of the poster or art.
ratingKey (str): Unique key identifying the poster or art.
selected (bool): True if the poster or art is currently selected.
thumb (str): The URL to retrieve the poster or art thumbnail.
provider (str): The source of the art or poster, None for Theme objects.
ratingKey (str): Unique key identifying the resource.
selected (bool): True if the resource is currently selected.
thumb (str): The URL to retrieve the resource thumbnail.
"""
TAG = 'Photo'
def _loadData(self, data):
self._data = data
self.key = data.attrib.get('key')
@ -947,16 +945,24 @@ class BaseImage(PlexObject):
pass
class Art(BaseImage):
class Art(BaseResource):
""" Represents a single Art object. """
TAG = 'Photo'
class Banner(BaseImage):
class Banner(BaseResource):
""" Represents a single Banner object. """
TAG = 'Photo'
class Poster(BaseImage):
class Poster(BaseResource):
""" Represents a single Poster object. """
TAG = 'Photo'
class Theme(BaseResource):
""" Represents a single Theme object. """
TAG = 'Track'
@utils.registerPlexObject

View file

@ -161,7 +161,7 @@ class PosterUrlMixin(object):
@property
def thumbUrl(self):
""" Return the thumb url for the Plex object. """
thumb = self.firstAttr('thumb', 'parentThumb', 'granparentThumb')
thumb = self.firstAttr('thumb', 'parentThumb', 'grandparentThumb')
return self._server.url(thumb, includeToken=True) if thumb else None
@property
@ -209,6 +209,49 @@ class PosterMixin(PosterUrlMixin):
self._edit(**{'thumb.locked': 0})
class ThemeUrlMixin(object):
""" Mixin for Plex objects that can have a theme url. """
@property
def themeUrl(self):
""" Return the theme url for the Plex object. """
theme = self.firstAttr('theme', 'parentTheme', 'grandparentTheme')
return self._server.url(theme, includeToken=True) if theme else None
class ThemeMixin(ThemeUrlMixin):
""" Mixin for Plex objects that can have themes. """
def themes(self):
""" Returns list of available :class:`~plexapi.media.Theme` objects. """
return self.fetchItems('/library/metadata/%s/themes' % self.ratingKey, cls=media.Theme)
def uploadTheme(self, url=None, filepath=None):
""" Upload a theme from url or filepath.
Warning: Themes cannot be deleted using PlexAPI!
Parameters:
url (str): The full URL to the theme to upload.
filepath (str): The full file path to the theme to upload.
"""
if url:
key = '/library/metadata/%s/themes?url=%s' % (self.ratingKey, quote_plus(url))
self._server.query(key, method=self._server._session.post)
elif filepath:
key = '/library/metadata/%s/themes?' % self.ratingKey
data = open(filepath, 'rb').read()
self._server.query(key, method=self._server._session.post, data=data)
def setTheme(self, theme):
""" Set the theme for a Plex object.
Parameters:
theme (:class:`~plexapi.media.Theme`): The theme object to select.
"""
theme.select()
class RatingMixin(object):
""" Mixin for Plex objects that can have user star ratings. """

View file

@ -5,9 +5,11 @@ from urllib.parse import quote_plus, urlencode
from plexapi import library, media, utils
from plexapi.base import Playable, PlexPartialObject
from plexapi.exceptions import BadRequest
from plexapi.mixins import AdvancedSettingsMixin, ArtUrlMixin, ArtMixin, BannerMixin, PosterUrlMixin, PosterMixin
from plexapi.mixins import AdvancedSettingsMixin, ArtUrlMixin, ArtMixin, BannerMixin, PosterUrlMixin, PosterMixin, \
ThemeUrlMixin, ThemeMixin
from plexapi.mixins import RatingMixin, SplitMergeMixin, UnmatchMatchMixin
from plexapi.mixins import CollectionMixin, CountryMixin, DirectorMixin, GenreMixin, LabelMixin, ProducerMixin, WriterMixin
from plexapi.mixins import CollectionMixin, CountryMixin, DirectorMixin, GenreMixin, LabelMixin, ProducerMixin, \
WriterMixin
class Video(PlexPartialObject):
@ -261,8 +263,9 @@ class Video(PlexPartialObject):
@utils.registerPlexObject
class Movie(Video, Playable, AdvancedSettingsMixin, ArtMixin, PosterMixin, RatingMixin, SplitMergeMixin, UnmatchMatchMixin,
CollectionMixin, CountryMixin, DirectorMixin, GenreMixin, LabelMixin, ProducerMixin, WriterMixin):
class Movie(Video, Playable, AdvancedSettingsMixin, ArtMixin, PosterMixin, ThemeMixin, RatingMixin, SplitMergeMixin,
UnmatchMatchMixin, CollectionMixin, CountryMixin, DirectorMixin, GenreMixin, LabelMixin, ProducerMixin,
WriterMixin):
""" Represents a single Movie.
Attributes:
@ -293,6 +296,7 @@ class Movie(Video, Playable, AdvancedSettingsMixin, ArtMixin, PosterMixin, Ratin
similar (List<:class:`~plexapi.media.Similar`>): List of Similar objects.
studio (str): Studio that created movie (Di Bonaventura Pictures; 21 Laps Entertainment).
tagline (str): Movie tag line (Back 2 Work; Who says men can't change?).
theme (str): URL to theme resource (/library/metadata/<ratingkey>/theme/<themeid>).
useOriginalTitle (int): Setting that indicates if the original title is used for the movie
(-1 = Library default, 0 = No, 1 = Yes).
viewOffset (int): View offset in milliseconds.
@ -331,6 +335,7 @@ class Movie(Video, Playable, AdvancedSettingsMixin, ArtMixin, PosterMixin, Ratin
self.similar = self.findItems(data, media.Similar)
self.studio = data.attrib.get('studio')
self.tagline = data.attrib.get('tagline')
self.theme = data.attrib.get('theme')
self.useOriginalTitle = utils.cast(int, data.attrib.get('useOriginalTitle', '-1'))
self.viewOffset = utils.cast(int, data.attrib.get('viewOffset', 0))
self.writers = self.findItems(data, media.Writer)
@ -377,8 +382,8 @@ class Movie(Video, Playable, AdvancedSettingsMixin, ArtMixin, PosterMixin, Ratin
@utils.registerPlexObject
class Show(Video, AdvancedSettingsMixin, ArtMixin, BannerMixin, PosterMixin, RatingMixin, SplitMergeMixin, UnmatchMatchMixin,
CollectionMixin, GenreMixin, LabelMixin):
class Show(Video, AdvancedSettingsMixin, ArtMixin, BannerMixin, PosterMixin, ThemeMixin, RatingMixin, SplitMergeMixin,
UnmatchMatchMixin, CollectionMixin, GenreMixin, LabelMixin):
""" Represents a single Show (including all seasons and episodes).
Attributes:
@ -574,7 +579,7 @@ class Show(Video, AdvancedSettingsMixin, ArtMixin, BannerMixin, PosterMixin, Rat
@utils.registerPlexObject
class Season(Video, ArtMixin, PosterMixin, RatingMixin, CollectionMixin, LabelMixin):
class Season(Video, ArtMixin, PosterMixin, ThemeUrlMixin, RatingMixin, CollectionMixin, LabelMixin):
""" Represents a single Show Season (including all episodes).
Attributes:
@ -711,8 +716,8 @@ class Season(Video, ArtMixin, PosterMixin, RatingMixin, CollectionMixin, LabelMi
@utils.registerPlexObject
class Episode(Video, Playable, ArtMixin, PosterMixin, RatingMixin,
CollectionMixin, DirectorMixin, LabelMixin, WriterMixin):
class Episode(Video, Playable, ArtMixin, PosterMixin, ThemeUrlMixin, RatingMixin,
CollectionMixin, DirectorMixin, LabelMixin, WriterMixin):
""" Represents a single Shows Episode.
Attributes: