mirror of
https://github.com/pkkid/python-plexapi
synced 2024-11-10 14:14:19 +00:00
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:
parent
168f1d331c
commit
692a23586c
9 changed files with 107 additions and 29 deletions
|
@ -505,6 +505,17 @@ class PlexPartialObject(PlexObject):
|
||||||
""" Returns True if this is not a full object. """
|
""" Returns True if this is not a full object. """
|
||||||
return not self.isFullObject()
|
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):
|
def edit(self, **kwargs):
|
||||||
""" Edit an object.
|
""" Edit an object.
|
||||||
|
|
||||||
|
@ -517,14 +528,7 @@ class PlexPartialObject(PlexObject):
|
||||||
'collection[0].tag.tag': 'Super',
|
'collection[0].tag.tag': 'Super',
|
||||||
'collection.locked': 0}
|
'collection.locked': 0}
|
||||||
"""
|
"""
|
||||||
if 'id' not in kwargs:
|
self._edit(**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_tags(self, tag, items, locked=True, remove=False):
|
def _edit_tags(self, tag, items, locked=True, remove=False):
|
||||||
""" Helper to edit tags.
|
""" Helper to edit tags.
|
||||||
|
|
|
@ -97,6 +97,14 @@ class ArtMixin(ArtUrlMixin):
|
||||||
"""
|
"""
|
||||||
art.select()
|
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):
|
class BannerUrlMixin(object):
|
||||||
""" Mixin for Plex objects that can have a banner url. """
|
""" Mixin for Plex objects that can have a banner url. """
|
||||||
|
@ -138,6 +146,14 @@ class BannerMixin(BannerUrlMixin):
|
||||||
"""
|
"""
|
||||||
banner.select()
|
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):
|
class PosterUrlMixin(object):
|
||||||
""" Mixin for Plex objects that can have a poster url. """
|
""" Mixin for Plex objects that can have a poster url. """
|
||||||
|
@ -184,6 +200,14 @@ class PosterMixin(PosterUrlMixin):
|
||||||
"""
|
"""
|
||||||
poster.select()
|
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):
|
class RatingMixin(object):
|
||||||
""" Mixin for Plex objects that can have user star ratings. """
|
""" Mixin for Plex objects that can have user star ratings. """
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
import re
|
import re
|
||||||
from urllib.parse import quote_plus, unquote
|
from urllib.parse import quote_plus, unquote
|
||||||
|
|
||||||
from plexapi import utils
|
from plexapi import media, utils
|
||||||
from plexapi.base import Playable, PlexPartialObject
|
from plexapi.base import Playable, PlexPartialObject
|
||||||
from plexapi.exceptions import BadRequest, NotFound, Unsupported
|
from plexapi.exceptions import BadRequest, NotFound, Unsupported
|
||||||
from plexapi.library import LibrarySection
|
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.
|
content (str): The filter URI string for smart playlists.
|
||||||
duration (int): Duration of the playlist in milliseconds.
|
duration (int): Duration of the playlist in milliseconds.
|
||||||
durationInSeconds (int): Duration of the playlist in seconds.
|
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).
|
guid (str): Plex GUID for the playlist (com.plexapp.agents.none://XXXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXX).
|
||||||
icon (str): Icon URI string for smart playlists.
|
icon (str): Icon URI string for smart playlists.
|
||||||
key (str): API URL (/playlist/<ratingkey>).
|
key (str): API URL (/playlist/<ratingkey>).
|
||||||
|
@ -48,8 +49,9 @@ class Playlist(PlexPartialObject, Playable, ArtMixin, PosterMixin, SmartFilterMi
|
||||||
self.content = data.attrib.get('content')
|
self.content = data.attrib.get('content')
|
||||||
self.duration = utils.cast(int, data.attrib.get('duration'))
|
self.duration = utils.cast(int, data.attrib.get('duration'))
|
||||||
self.durationInSeconds = utils.cast(int, data.attrib.get('durationInSeconds'))
|
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.guid = data.attrib.get('guid')
|
||||||
|
self.icon = data.attrib.get('icon')
|
||||||
self.key = data.attrib.get('key', '').replace('/items', '') # FIX_BUG_50
|
self.key = data.attrib.get('key', '').replace('/items', '') # FIX_BUG_50
|
||||||
self.leafCount = utils.cast(int, data.attrib.get('leafCount'))
|
self.leafCount = utils.cast(int, data.attrib.get('leafCount'))
|
||||||
self.playlistType = data.attrib.get('playlistType')
|
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)
|
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):
|
def edit(self, title=None, summary=None):
|
||||||
""" Edit the playlist.
|
""" Edit the playlist.
|
||||||
|
|
||||||
|
@ -300,9 +307,7 @@ class Playlist(PlexPartialObject, Playable, ArtMixin, PosterMixin, SmartFilterMi
|
||||||
args['title'] = title
|
args['title'] = title
|
||||||
if summary:
|
if summary:
|
||||||
args['summary'] = summary
|
args['summary'] = summary
|
||||||
|
self._edit(**args)
|
||||||
key = '%s%s' % (self.key, utils.joinArgs(args))
|
|
||||||
self._server.query(key, method=self._server._session.put)
|
|
||||||
|
|
||||||
def delete(self):
|
def delete(self):
|
||||||
""" Delete the playlist. """
|
""" Delete the playlist. """
|
||||||
|
|
|
@ -74,6 +74,8 @@ def test_audio_Artist_mixins_edit_advanced_settings(artist):
|
||||||
|
|
||||||
|
|
||||||
def test_audio_Artist_mixins_images(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_art(artist)
|
||||||
test_mixins.edit_poster(artist)
|
test_mixins.edit_poster(artist)
|
||||||
test_mixins.attr_artUrl(artist)
|
test_mixins.attr_artUrl(artist)
|
||||||
|
@ -169,6 +171,8 @@ def test_audio_Album_artist(album):
|
||||||
|
|
||||||
|
|
||||||
def test_audio_Album_mixins_images(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_art(album)
|
||||||
test_mixins.edit_poster(album)
|
test_mixins.edit_poster(album)
|
||||||
test_mixins.attr_artUrl(album)
|
test_mixins.attr_artUrl(album)
|
||||||
|
|
|
@ -271,6 +271,8 @@ def test_Collection_art(collection):
|
||||||
|
|
||||||
|
|
||||||
def test_Collection_mixins_images(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_art(collection)
|
||||||
test_mixins.edit_poster(collection)
|
test_mixins.edit_poster(collection)
|
||||||
test_mixins.attr_artUrl(collection)
|
test_mixins.attr_artUrl(collection)
|
||||||
|
|
|
@ -78,7 +78,34 @@ def edit_writer(obj):
|
||||||
_test_mixins_tag(obj, "writers", "Writer")
|
_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()
|
cap_attr = attr[:-1].capitalize()
|
||||||
get_img_method = getattr(obj, attr)
|
get_img_method = getattr(obj, attr)
|
||||||
set_img_method = getattr(obj, "set" + cap_attr)
|
set_img_method = getattr(obj, "set" + cap_attr)
|
||||||
|
@ -106,7 +133,7 @@ def _test_mixins_image(obj, attr):
|
||||||
images = get_img_method()
|
images = get_img_method()
|
||||||
file_image = [
|
file_image = [
|
||||||
i for i in images
|
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
|
assert file_image
|
||||||
# Reset to default image
|
# Reset to default image
|
||||||
|
@ -115,39 +142,39 @@ def _test_mixins_image(obj, attr):
|
||||||
|
|
||||||
|
|
||||||
def edit_art(obj):
|
def edit_art(obj):
|
||||||
_test_mixins_image(obj, 'arts')
|
_test_mixins_edit_image(obj, "arts")
|
||||||
|
|
||||||
|
|
||||||
def edit_banner(obj):
|
def edit_banner(obj):
|
||||||
_test_mixins_image(obj, 'banners')
|
_test_mixins_edit_image(obj, "banners")
|
||||||
|
|
||||||
|
|
||||||
def edit_poster(obj):
|
def edit_poster(obj):
|
||||||
_test_mixins_image(obj, 'posters')
|
_test_mixins_edit_image(obj, "posters")
|
||||||
|
|
||||||
|
|
||||||
def _test_mixins_imageUrl(obj, attr):
|
def _test_mixins_imageUrl(obj, attr):
|
||||||
url = getattr(obj, attr + 'Url')
|
url = getattr(obj, attr + "Url")
|
||||||
if getattr(obj, attr):
|
if getattr(obj, attr):
|
||||||
assert url.startswith(utils.SERVER_BASEURL)
|
assert url.startswith(utils.SERVER_BASEURL)
|
||||||
assert "/library/metadata/" in url or "/library/collections/" in url
|
assert "/library/metadata/" in url or "/library/collections/" in url
|
||||||
assert attr in url or "composite" in url
|
assert attr in url or "composite" in url
|
||||||
if attr == 'thumb':
|
if attr == "thumb":
|
||||||
assert getattr(obj, 'posterUrl') == url
|
assert getattr(obj, "posterUrl") == url
|
||||||
else:
|
else:
|
||||||
assert url is None
|
assert url is None
|
||||||
|
|
||||||
|
|
||||||
def attr_artUrl(obj):
|
def attr_artUrl(obj):
|
||||||
_test_mixins_imageUrl(obj, 'art')
|
_test_mixins_imageUrl(obj, "art")
|
||||||
|
|
||||||
|
|
||||||
def attr_bannerUrl(obj):
|
def attr_bannerUrl(obj):
|
||||||
_test_mixins_imageUrl(obj, 'banner')
|
_test_mixins_imageUrl(obj, "banner")
|
||||||
|
|
||||||
|
|
||||||
def attr_posterUrl(obj):
|
def attr_posterUrl(obj):
|
||||||
_test_mixins_imageUrl(obj, 'thumb')
|
_test_mixins_imageUrl(obj, "thumb")
|
||||||
|
|
||||||
|
|
||||||
def _test_mixins_editAdvanced(obj):
|
def _test_mixins_editAdvanced(obj):
|
||||||
|
@ -163,7 +190,7 @@ def _test_mixins_editAdvanced(obj):
|
||||||
|
|
||||||
def _test_mixins_editAdvanced_bad_pref(obj):
|
def _test_mixins_editAdvanced_bad_pref(obj):
|
||||||
with pytest.raises(NotFound):
|
with pytest.raises(NotFound):
|
||||||
assert obj.preference('bad-pref')
|
assert obj.preference("bad-pref")
|
||||||
|
|
||||||
|
|
||||||
def _test_mixins_defaultAdvanced(obj):
|
def _test_mixins_defaultAdvanced(obj):
|
||||||
|
@ -188,7 +215,7 @@ def edit_rating(obj):
|
||||||
obj.reload()
|
obj.reload()
|
||||||
assert obj.userRating is None
|
assert obj.userRating is None
|
||||||
with pytest.raises(BadRequest):
|
with pytest.raises(BadRequest):
|
||||||
assert obj.rate('bad-rating')
|
assert obj.rate("bad-rating")
|
||||||
with pytest.raises(BadRequest):
|
with pytest.raises(BadRequest):
|
||||||
assert obj.rate(-1)
|
assert obj.rate(-1)
|
||||||
with pytest.raises(BadRequest):
|
with pytest.raises(BadRequest):
|
||||||
|
|
|
@ -12,6 +12,8 @@ def test_photo_Photoalbum(photoalbum):
|
||||||
|
|
||||||
|
|
||||||
def test_photo_Photoalbum_mixins_images(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_art(photoalbum)
|
||||||
test_mixins.edit_poster(photoalbum)
|
test_mixins.edit_poster(photoalbum)
|
||||||
test_mixins.attr_artUrl(photoalbum)
|
test_mixins.attr_artUrl(photoalbum)
|
||||||
|
|
|
@ -259,5 +259,7 @@ def test_Playlist_exceptions(plex, movies, movie, artist):
|
||||||
|
|
||||||
|
|
||||||
def test_Playlist_mixins_images(playlist):
|
def test_Playlist_mixins_images(playlist):
|
||||||
|
# test_mixins.lock_art(playlist)
|
||||||
|
test_mixins.lock_poster(playlist)
|
||||||
# test_mixins.edit_art(playlist)
|
# test_mixins.edit_art(playlist)
|
||||||
test_mixins.edit_poster(playlist)
|
test_mixins.edit_poster(playlist)
|
||||||
|
|
|
@ -45,6 +45,8 @@ def test_video_Movie_mixins_edit_advanced_settings(movie):
|
||||||
|
|
||||||
|
|
||||||
def test_video_Movie_mixins_images(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_art(movie)
|
||||||
test_mixins.edit_poster(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")
|
@pytest.mark.xfail(reason="Changing show art fails randomly")
|
||||||
def test_video_Show_mixins_images(show):
|
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_art(show)
|
||||||
test_mixins.edit_poster(show)
|
test_mixins.edit_poster(show)
|
||||||
test_mixins.attr_artUrl(show)
|
test_mixins.attr_artUrl(show)
|
||||||
|
@ -896,6 +900,8 @@ def test_video_Season_episodes(show):
|
||||||
|
|
||||||
def test_video_Season_mixins_images(show):
|
def test_video_Season_mixins_images(show):
|
||||||
season = show.season(season=1)
|
season = show.season(season=1)
|
||||||
|
test_mixins.lock_art(season)
|
||||||
|
test_mixins.lock_poster(season)
|
||||||
test_mixins.edit_art(season)
|
test_mixins.edit_art(season)
|
||||||
test_mixins.edit_poster(season)
|
test_mixins.edit_poster(season)
|
||||||
test_mixins.attr_artUrl(season)
|
test_mixins.attr_artUrl(season)
|
||||||
|
@ -1096,6 +1102,8 @@ def test_video_Episode_unwatched(tvshows):
|
||||||
|
|
||||||
|
|
||||||
def test_video_Episode_mixins_images(episode):
|
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_art(episode) # Uploading episode artwork is broken in Plex
|
||||||
test_mixins.edit_poster(episode)
|
test_mixins.edit_poster(episode)
|
||||||
test_mixins.attr_artUrl(episode)
|
test_mixins.attr_artUrl(episode)
|
||||||
|
|
Loading…
Reference in a new issue