Add methods to lock and unlock artwork and posters (#825)

* Add private _edit method

* Add fields attribute to playlists

* Add lock and unlock methods to art, banner, and poster mixins

* Add tests for locking and unlocking art and posters
This commit is contained in:
JonnyWong16 2021-09-12 17:56:21 -07:00 committed by GitHub
parent 168f1d331c
commit 692a23586c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 107 additions and 29 deletions

View file

@ -505,6 +505,17 @@ class PlexPartialObject(PlexObject):
""" Returns True if this is not a full object. """
return not self.isFullObject()
def _edit(self, **kwargs):
""" Actually edit an object. """
if 'id' not in kwargs:
kwargs['id'] = self.ratingKey
if 'type' not in kwargs:
kwargs['type'] = utils.searchType(self.type)
part = '/library/sections/%s/all?%s' % (self.librarySectionID,
urlencode(kwargs))
self._server.query(part, method=self._server._session.put)
def edit(self, **kwargs):
""" Edit an object.
@ -517,14 +528,7 @@ class PlexPartialObject(PlexObject):
'collection[0].tag.tag': 'Super',
'collection.locked': 0}
"""
if 'id' not in kwargs:
kwargs['id'] = self.ratingKey
if 'type' not in kwargs:
kwargs['type'] = utils.searchType(self.type)
part = '/library/sections/%s/all?%s' % (self.librarySectionID,
urlencode(kwargs))
self._server.query(part, method=self._server._session.put)
self._edit(**kwargs)
def _edit_tags(self, tag, items, locked=True, remove=False):
""" Helper to edit tags.

View file

@ -97,6 +97,14 @@ class ArtMixin(ArtUrlMixin):
"""
art.select()
def lockArt(self):
""" Lock the background artwork for a Plex object. """
self._edit(**{'art.locked': 1})
def unlockArt(self):
""" Unlock the background artwork for a Plex object. """
self._edit(**{'art.locked': 0})
class BannerUrlMixin(object):
""" Mixin for Plex objects that can have a banner url. """
@ -138,6 +146,14 @@ class BannerMixin(BannerUrlMixin):
"""
banner.select()
def lockBanner(self):
""" Lock the banner for a Plex object. """
self._edit(**{'banner.locked': 1})
def unlockBanner(self):
""" Unlock the banner for a Plex object. """
self._edit(**{'banner.locked': 0})
class PosterUrlMixin(object):
""" Mixin for Plex objects that can have a poster url. """
@ -184,6 +200,14 @@ class PosterMixin(PosterUrlMixin):
"""
poster.select()
def lockPoster(self):
""" Lock the poster for a Plex object. """
self._edit(**{'thumb.locked': 1})
def unlockPoster(self):
""" Unlock the poster for a Plex object. """
self._edit(**{'thumb.locked': 0})
class RatingMixin(object):
""" Mixin for Plex objects that can have user star ratings. """

View file

@ -2,7 +2,7 @@
import re
from urllib.parse import quote_plus, unquote
from plexapi import utils
from plexapi import media, utils
from plexapi.base import Playable, PlexPartialObject
from plexapi.exceptions import BadRequest, NotFound, Unsupported
from plexapi.library import LibrarySection
@ -24,6 +24,7 @@ class Playlist(PlexPartialObject, Playable, ArtMixin, PosterMixin, SmartFilterMi
content (str): The filter URI string for smart playlists.
duration (int): Duration of the playlist in milliseconds.
durationInSeconds (int): Duration of the playlist in seconds.
fields (List<:class:`~plexapi.media.Field`>): List of field objects.
guid (str): Plex GUID for the playlist (com.plexapp.agents.none://XXXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXX).
icon (str): Icon URI string for smart playlists.
key (str): API URL (/playlist/<ratingkey>).
@ -48,8 +49,9 @@ class Playlist(PlexPartialObject, Playable, ArtMixin, PosterMixin, SmartFilterMi
self.content = data.attrib.get('content')
self.duration = utils.cast(int, data.attrib.get('duration'))
self.durationInSeconds = utils.cast(int, data.attrib.get('durationInSeconds'))
self.icon = data.attrib.get('icon')
self.fields = self.findItems(data, media.Field)
self.guid = data.attrib.get('guid')
self.icon = data.attrib.get('icon')
self.key = data.attrib.get('key', '').replace('/items', '') # FIX_BUG_50
self.leafCount = utils.cast(int, data.attrib.get('leafCount'))
self.playlistType = data.attrib.get('playlistType')
@ -288,6 +290,11 @@ class Playlist(PlexPartialObject, Playable, ArtMixin, PosterMixin, SmartFilterMi
}))
self._server.query(key, method=self._server._session.put)
def _edit(self, **kwargs):
""" Actually edit the playlist. """
key = '%s%s' % (self.key, utils.joinArgs(kwargs))
self._server.query(key, method=self._server._session.put)
def edit(self, title=None, summary=None):
""" Edit the playlist.
@ -300,9 +307,7 @@ class Playlist(PlexPartialObject, Playable, ArtMixin, PosterMixin, SmartFilterMi
args['title'] = title
if summary:
args['summary'] = summary
key = '%s%s' % (self.key, utils.joinArgs(args))
self._server.query(key, method=self._server._session.put)
self._edit(**args)
def delete(self):
""" Delete the playlist. """

View file

@ -74,6 +74,8 @@ def test_audio_Artist_mixins_edit_advanced_settings(artist):
def test_audio_Artist_mixins_images(artist):
test_mixins.lock_art(artist)
test_mixins.lock_poster(artist)
test_mixins.edit_art(artist)
test_mixins.edit_poster(artist)
test_mixins.attr_artUrl(artist)
@ -169,6 +171,8 @@ def test_audio_Album_artist(album):
def test_audio_Album_mixins_images(album):
test_mixins.lock_art(album)
test_mixins.lock_poster(album)
test_mixins.edit_art(album)
test_mixins.edit_poster(album)
test_mixins.attr_artUrl(album)

View file

@ -271,6 +271,8 @@ def test_Collection_art(collection):
def test_Collection_mixins_images(collection):
test_mixins.lock_art(collection)
test_mixins.lock_poster(collection)
test_mixins.edit_art(collection)
test_mixins.edit_poster(collection)
test_mixins.attr_artUrl(collection)

View file

@ -78,7 +78,34 @@ def edit_writer(obj):
_test_mixins_tag(obj, "writers", "Writer")
def _test_mixins_image(obj, attr):
def _test_mixins_lock_image(obj, attr):
cap_attr = attr[:-1].capitalize()
lock_img_method = getattr(obj, "lock" + cap_attr)
unlock_img_method = getattr(obj, "unlock" + cap_attr)
field = "thumb" if attr == 'posters' else attr[:-1]
_fields = lambda: [f.name for f in obj.fields]
assert field not in _fields()
lock_img_method()
obj.reload()
assert field in _fields()
unlock_img_method()
obj.reload()
assert field not in _fields()
def lock_art(obj):
_test_mixins_lock_image(obj, "arts")
def lock_banner(obj):
_test_mixins_lock_image(obj, "banners")
def lock_poster(obj):
_test_mixins_lock_image(obj, "posters")
def _test_mixins_edit_image(obj, attr):
cap_attr = attr[:-1].capitalize()
get_img_method = getattr(obj, attr)
set_img_method = getattr(obj, "set" + cap_attr)
@ -106,7 +133,7 @@ def _test_mixins_image(obj, attr):
images = get_img_method()
file_image = [
i for i in images
if i.ratingKey.startswith('upload://') and i.ratingKey.endswith(CUTE_CAT_SHA1)
if i.ratingKey.startswith("upload://") and i.ratingKey.endswith(CUTE_CAT_SHA1)
]
assert file_image
# Reset to default image
@ -115,39 +142,39 @@ def _test_mixins_image(obj, attr):
def edit_art(obj):
_test_mixins_image(obj, 'arts')
_test_mixins_edit_image(obj, "arts")
def edit_banner(obj):
_test_mixins_image(obj, 'banners')
_test_mixins_edit_image(obj, "banners")
def edit_poster(obj):
_test_mixins_image(obj, 'posters')
_test_mixins_edit_image(obj, "posters")
def _test_mixins_imageUrl(obj, attr):
url = getattr(obj, attr + 'Url')
url = getattr(obj, attr + "Url")
if getattr(obj, attr):
assert url.startswith(utils.SERVER_BASEURL)
assert "/library/metadata/" in url or "/library/collections/" in url
assert attr in url or "composite" in url
if attr == 'thumb':
assert getattr(obj, 'posterUrl') == url
if attr == "thumb":
assert getattr(obj, "posterUrl") == url
else:
assert url is None
def attr_artUrl(obj):
_test_mixins_imageUrl(obj, 'art')
_test_mixins_imageUrl(obj, "art")
def attr_bannerUrl(obj):
_test_mixins_imageUrl(obj, 'banner')
_test_mixins_imageUrl(obj, "banner")
def attr_posterUrl(obj):
_test_mixins_imageUrl(obj, 'thumb')
_test_mixins_imageUrl(obj, "thumb")
def _test_mixins_editAdvanced(obj):
@ -163,7 +190,7 @@ def _test_mixins_editAdvanced(obj):
def _test_mixins_editAdvanced_bad_pref(obj):
with pytest.raises(NotFound):
assert obj.preference('bad-pref')
assert obj.preference("bad-pref")
def _test_mixins_defaultAdvanced(obj):
@ -188,7 +215,7 @@ def edit_rating(obj):
obj.reload()
assert obj.userRating is None
with pytest.raises(BadRequest):
assert obj.rate('bad-rating')
assert obj.rate("bad-rating")
with pytest.raises(BadRequest):
assert obj.rate(-1)
with pytest.raises(BadRequest):

View file

@ -12,6 +12,8 @@ def test_photo_Photoalbum(photoalbum):
def test_photo_Photoalbum_mixins_images(photoalbum):
# test_mixins.lock_art(photoalbum) # Unlocking photoalbum artwork is broken in Plex
# test_mixins.lock_poster(photoalbum) # Unlocking photoalbum poster is broken in Plex
test_mixins.edit_art(photoalbum)
test_mixins.edit_poster(photoalbum)
test_mixins.attr_artUrl(photoalbum)

View file

@ -259,5 +259,7 @@ def test_Playlist_exceptions(plex, movies, movie, artist):
def test_Playlist_mixins_images(playlist):
# test_mixins.lock_art(playlist)
test_mixins.lock_poster(playlist)
# test_mixins.edit_art(playlist)
test_mixins.edit_poster(playlist)

View file

@ -45,6 +45,8 @@ def test_video_Movie_mixins_edit_advanced_settings(movie):
def test_video_Movie_mixins_images(movie):
test_mixins.lock_art(movie)
test_mixins.lock_poster(movie)
test_mixins.edit_art(movie)
test_mixins.edit_poster(movie)
@ -776,6 +778,8 @@ def test_video_Show_mixins_edit_advanced_settings(show):
@pytest.mark.xfail(reason="Changing show art fails randomly")
def test_video_Show_mixins_images(show):
test_mixins.lock_art(show)
test_mixins.lock_poster(show)
test_mixins.edit_art(show)
test_mixins.edit_poster(show)
test_mixins.attr_artUrl(show)
@ -896,6 +900,8 @@ def test_video_Season_episodes(show):
def test_video_Season_mixins_images(show):
season = show.season(season=1)
test_mixins.lock_art(season)
test_mixins.lock_poster(season)
test_mixins.edit_art(season)
test_mixins.edit_poster(season)
test_mixins.attr_artUrl(season)
@ -1096,6 +1102,8 @@ def test_video_Episode_unwatched(tvshows):
def test_video_Episode_mixins_images(episode):
test_mixins.lock_art(episode)
test_mixins.lock_poster(episode)
# test_mixins.edit_art(episode) # Uploading episode artwork is broken in Plex
test_mixins.edit_poster(episode)
test_mixins.attr_artUrl(episode)