Skip to content

Commit 692a235

Browse files
authored
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
1 parent 168f1d3 commit 692a235

File tree

9 files changed

+107
-29
lines changed

9 files changed

+107
-29
lines changed

plexapi/base.py

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -505,6 +505,17 @@ def isPartialObject(self):
505505
""" Returns True if this is not a full object. """
506506
return not self.isFullObject()
507507

508+
def _edit(self, **kwargs):
509+
""" Actually edit an object. """
510+
if 'id' not in kwargs:
511+
kwargs['id'] = self.ratingKey
512+
if 'type' not in kwargs:
513+
kwargs['type'] = utils.searchType(self.type)
514+
515+
part = '/library/sections/%s/all?%s' % (self.librarySectionID,
516+
urlencode(kwargs))
517+
self._server.query(part, method=self._server._session.put)
518+
508519
def edit(self, **kwargs):
509520
""" Edit an object.
510521
@@ -517,14 +528,7 @@ def edit(self, **kwargs):
517528
'collection[0].tag.tag': 'Super',
518529
'collection.locked': 0}
519530
"""
520-
if 'id' not in kwargs:
521-
kwargs['id'] = self.ratingKey
522-
if 'type' not in kwargs:
523-
kwargs['type'] = utils.searchType(self.type)
524-
525-
part = '/library/sections/%s/all?%s' % (self.librarySectionID,
526-
urlencode(kwargs))
527-
self._server.query(part, method=self._server._session.put)
531+
self._edit(**kwargs)
528532

529533
def _edit_tags(self, tag, items, locked=True, remove=False):
530534
""" Helper to edit tags.

plexapi/mixins.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,14 @@ def setArt(self, art):
9797
"""
9898
art.select()
9999

100+
def lockArt(self):
101+
""" Lock the background artwork for a Plex object. """
102+
self._edit(**{'art.locked': 1})
103+
104+
def unlockArt(self):
105+
""" Unlock the background artwork for a Plex object. """
106+
self._edit(**{'art.locked': 0})
107+
100108

101109
class BannerUrlMixin(object):
102110
""" Mixin for Plex objects that can have a banner url. """
@@ -138,6 +146,14 @@ def setBanner(self, banner):
138146
"""
139147
banner.select()
140148

149+
def lockBanner(self):
150+
""" Lock the banner for a Plex object. """
151+
self._edit(**{'banner.locked': 1})
152+
153+
def unlockBanner(self):
154+
""" Unlock the banner for a Plex object. """
155+
self._edit(**{'banner.locked': 0})
156+
141157

142158
class PosterUrlMixin(object):
143159
""" Mixin for Plex objects that can have a poster url. """
@@ -184,6 +200,14 @@ def setPoster(self, poster):
184200
"""
185201
poster.select()
186202

203+
def lockPoster(self):
204+
""" Lock the poster for a Plex object. """
205+
self._edit(**{'thumb.locked': 1})
206+
207+
def unlockPoster(self):
208+
""" Unlock the poster for a Plex object. """
209+
self._edit(**{'thumb.locked': 0})
210+
187211

188212
class RatingMixin(object):
189213
""" Mixin for Plex objects that can have user star ratings. """

plexapi/playlist.py

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import re
33
from urllib.parse import quote_plus, unquote
44

5-
from plexapi import utils
5+
from plexapi import media, utils
66
from plexapi.base import Playable, PlexPartialObject
77
from plexapi.exceptions import BadRequest, NotFound, Unsupported
88
from plexapi.library import LibrarySection
@@ -24,6 +24,7 @@ class Playlist(PlexPartialObject, Playable, ArtMixin, PosterMixin, SmartFilterMi
2424
content (str): The filter URI string for smart playlists.
2525
duration (int): Duration of the playlist in milliseconds.
2626
durationInSeconds (int): Duration of the playlist in seconds.
27+
fields (List<:class:`~plexapi.media.Field`>): List of field objects.
2728
guid (str): Plex GUID for the playlist (com.plexapp.agents.none://XXXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXX).
2829
icon (str): Icon URI string for smart playlists.
2930
key (str): API URL (/playlist/<ratingkey>).
@@ -48,8 +49,9 @@ def _loadData(self, data):
4849
self.content = data.attrib.get('content')
4950
self.duration = utils.cast(int, data.attrib.get('duration'))
5051
self.durationInSeconds = utils.cast(int, data.attrib.get('durationInSeconds'))
51-
self.icon = data.attrib.get('icon')
52+
self.fields = self.findItems(data, media.Field)
5253
self.guid = data.attrib.get('guid')
54+
self.icon = data.attrib.get('icon')
5355
self.key = data.attrib.get('key', '').replace('/items', '') # FIX_BUG_50
5456
self.leafCount = utils.cast(int, data.attrib.get('leafCount'))
5557
self.playlistType = data.attrib.get('playlistType')
@@ -288,6 +290,11 @@ def updateFilters(self, limit=None, sort=None, filters=None, **kwargs):
288290
}))
289291
self._server.query(key, method=self._server._session.put)
290292

293+
def _edit(self, **kwargs):
294+
""" Actually edit the playlist. """
295+
key = '%s%s' % (self.key, utils.joinArgs(kwargs))
296+
self._server.query(key, method=self._server._session.put)
297+
291298
def edit(self, title=None, summary=None):
292299
""" Edit the playlist.
293300
@@ -300,9 +307,7 @@ def edit(self, title=None, summary=None):
300307
args['title'] = title
301308
if summary:
302309
args['summary'] = summary
303-
304-
key = '%s%s' % (self.key, utils.joinArgs(args))
305-
self._server.query(key, method=self._server._session.put)
310+
self._edit(**args)
306311

307312
def delete(self):
308313
""" Delete the playlist. """

tests/test_audio.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ def test_audio_Artist_attr(artist):
1111
assert utils.is_art(artist.art)
1212
if artist.countries:
1313
assert "United States of America" in [i.tag for i in artist.countries]
14-
#assert "Electronic" in [i.tag for i in artist.genres]
14+
# assert "Electronic" in [i.tag for i in artist.genres]
1515
assert utils.is_string(artist.guid, gte=5)
1616
assert artist.index == 1
1717
assert utils.is_metadata(artist._initpath)
@@ -74,6 +74,8 @@ def test_audio_Artist_mixins_edit_advanced_settings(artist):
7474

7575

7676
def test_audio_Artist_mixins_images(artist):
77+
test_mixins.lock_art(artist)
78+
test_mixins.lock_poster(artist)
7779
test_mixins.edit_art(artist)
7880
test_mixins.edit_poster(artist)
7981
test_mixins.attr_artUrl(artist)
@@ -169,6 +171,8 @@ def test_audio_Album_artist(album):
169171

170172

171173
def test_audio_Album_mixins_images(album):
174+
test_mixins.lock_art(album)
175+
test_mixins.lock_poster(album)
172176
test_mixins.edit_art(album)
173177
test_mixins.edit_poster(album)
174178
test_mixins.attr_artUrl(album)

tests/test_collection.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,8 @@ def test_Collection_art(collection):
271271

272272

273273
def test_Collection_mixins_images(collection):
274+
test_mixins.lock_art(collection)
275+
test_mixins.lock_poster(collection)
274276
test_mixins.edit_art(collection)
275277
test_mixins.edit_poster(collection)
276278
test_mixins.attr_artUrl(collection)

tests/test_mixins.py

Lines changed: 40 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,34 @@ def edit_writer(obj):
7878
_test_mixins_tag(obj, "writers", "Writer")
7979

8080

81-
def _test_mixins_image(obj, attr):
81+
def _test_mixins_lock_image(obj, attr):
82+
cap_attr = attr[:-1].capitalize()
83+
lock_img_method = getattr(obj, "lock" + cap_attr)
84+
unlock_img_method = getattr(obj, "unlock" + cap_attr)
85+
field = "thumb" if attr == 'posters' else attr[:-1]
86+
_fields = lambda: [f.name for f in obj.fields]
87+
assert field not in _fields()
88+
lock_img_method()
89+
obj.reload()
90+
assert field in _fields()
91+
unlock_img_method()
92+
obj.reload()
93+
assert field not in _fields()
94+
95+
96+
def lock_art(obj):
97+
_test_mixins_lock_image(obj, "arts")
98+
99+
100+
def lock_banner(obj):
101+
_test_mixins_lock_image(obj, "banners")
102+
103+
104+
def lock_poster(obj):
105+
_test_mixins_lock_image(obj, "posters")
106+
107+
108+
def _test_mixins_edit_image(obj, attr):
82109
cap_attr = attr[:-1].capitalize()
83110
get_img_method = getattr(obj, attr)
84111
set_img_method = getattr(obj, "set" + cap_attr)
@@ -106,7 +133,7 @@ def _test_mixins_image(obj, attr):
106133
images = get_img_method()
107134
file_image = [
108135
i for i in images
109-
if i.ratingKey.startswith('upload://') and i.ratingKey.endswith(CUTE_CAT_SHA1)
136+
if i.ratingKey.startswith("upload://") and i.ratingKey.endswith(CUTE_CAT_SHA1)
110137
]
111138
assert file_image
112139
# Reset to default image
@@ -115,39 +142,39 @@ def _test_mixins_image(obj, attr):
115142

116143

117144
def edit_art(obj):
118-
_test_mixins_image(obj, 'arts')
145+
_test_mixins_edit_image(obj, "arts")
119146

120147

121148
def edit_banner(obj):
122-
_test_mixins_image(obj, 'banners')
149+
_test_mixins_edit_image(obj, "banners")
123150

124151

125152
def edit_poster(obj):
126-
_test_mixins_image(obj, 'posters')
153+
_test_mixins_edit_image(obj, "posters")
127154

128155

129156
def _test_mixins_imageUrl(obj, attr):
130-
url = getattr(obj, attr + 'Url')
157+
url = getattr(obj, attr + "Url")
131158
if getattr(obj, attr):
132159
assert url.startswith(utils.SERVER_BASEURL)
133160
assert "/library/metadata/" in url or "/library/collections/" in url
134161
assert attr in url or "composite" in url
135-
if attr == 'thumb':
136-
assert getattr(obj, 'posterUrl') == url
162+
if attr == "thumb":
163+
assert getattr(obj, "posterUrl") == url
137164
else:
138165
assert url is None
139166

140167

141168
def attr_artUrl(obj):
142-
_test_mixins_imageUrl(obj, 'art')
169+
_test_mixins_imageUrl(obj, "art")
143170

144171

145172
def attr_bannerUrl(obj):
146-
_test_mixins_imageUrl(obj, 'banner')
173+
_test_mixins_imageUrl(obj, "banner")
147174

148175

149176
def attr_posterUrl(obj):
150-
_test_mixins_imageUrl(obj, 'thumb')
177+
_test_mixins_imageUrl(obj, "thumb")
151178

152179

153180
def _test_mixins_editAdvanced(obj):
@@ -163,7 +190,7 @@ def _test_mixins_editAdvanced(obj):
163190

164191
def _test_mixins_editAdvanced_bad_pref(obj):
165192
with pytest.raises(NotFound):
166-
assert obj.preference('bad-pref')
193+
assert obj.preference("bad-pref")
167194

168195

169196
def _test_mixins_defaultAdvanced(obj):
@@ -188,7 +215,7 @@ def edit_rating(obj):
188215
obj.reload()
189216
assert obj.userRating is None
190217
with pytest.raises(BadRequest):
191-
assert obj.rate('bad-rating')
218+
assert obj.rate("bad-rating")
192219
with pytest.raises(BadRequest):
193220
assert obj.rate(-1)
194221
with pytest.raises(BadRequest):

tests/test_photo.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ def test_photo_Photoalbum(photoalbum):
1212

1313

1414
def test_photo_Photoalbum_mixins_images(photoalbum):
15+
# test_mixins.lock_art(photoalbum) # Unlocking photoalbum artwork is broken in Plex
16+
# test_mixins.lock_poster(photoalbum) # Unlocking photoalbum poster is broken in Plex
1517
test_mixins.edit_art(photoalbum)
1618
test_mixins.edit_poster(photoalbum)
1719
test_mixins.attr_artUrl(photoalbum)

tests/test_playlist.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -259,5 +259,7 @@ def test_Playlist_exceptions(plex, movies, movie, artist):
259259

260260

261261
def test_Playlist_mixins_images(playlist):
262-
#test_mixins.edit_art(playlist)
262+
# test_mixins.lock_art(playlist)
263+
test_mixins.lock_poster(playlist)
264+
# test_mixins.edit_art(playlist)
263265
test_mixins.edit_poster(playlist)

tests/test_video.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ def test_video_Movie_mixins_edit_advanced_settings(movie):
4545

4646

4747
def test_video_Movie_mixins_images(movie):
48+
test_mixins.lock_art(movie)
49+
test_mixins.lock_poster(movie)
4850
test_mixins.edit_art(movie)
4951
test_mixins.edit_poster(movie)
5052

@@ -776,6 +778,8 @@ def test_video_Show_mixins_edit_advanced_settings(show):
776778

777779
@pytest.mark.xfail(reason="Changing show art fails randomly")
778780
def test_video_Show_mixins_images(show):
781+
test_mixins.lock_art(show)
782+
test_mixins.lock_poster(show)
779783
test_mixins.edit_art(show)
780784
test_mixins.edit_poster(show)
781785
test_mixins.attr_artUrl(show)
@@ -896,6 +900,8 @@ def test_video_Season_episodes(show):
896900

897901
def test_video_Season_mixins_images(show):
898902
season = show.season(season=1)
903+
test_mixins.lock_art(season)
904+
test_mixins.lock_poster(season)
899905
test_mixins.edit_art(season)
900906
test_mixins.edit_poster(season)
901907
test_mixins.attr_artUrl(season)
@@ -1096,7 +1102,9 @@ def test_video_Episode_unwatched(tvshows):
10961102

10971103

10981104
def test_video_Episode_mixins_images(episode):
1099-
#test_mixins.edit_art(episode) # Uploading episode artwork is broken in Plex
1105+
test_mixins.lock_art(episode)
1106+
test_mixins.lock_poster(episode)
1107+
# test_mixins.edit_art(episode) # Uploading episode artwork is broken in Plex
11001108
test_mixins.edit_poster(episode)
11011109
test_mixins.attr_artUrl(episode)
11021110
test_mixins.attr_posterUrl(episode)

0 commit comments

Comments
 (0)