diff --git a/plexapi/audio.py b/plexapi/audio.py index 2cbd82d61..caecfbe7c 100644 --- a/plexapi/audio.py +++ b/plexapi/audio.py @@ -4,7 +4,8 @@ from plexapi import library, media, utils from plexapi.base import Playable, PlexPartialObject from plexapi.exceptions import BadRequest -from plexapi.mixins import ArtMixin, PosterMixin, SplitMergeMixin, UnmatchMatchMixin +from plexapi.mixins import ArtUrlMixin, ArtMixin, PosterUrlMixin, PosterMixin +from plexapi.mixins import SplitMergeMixin, UnmatchMatchMixin from plexapi.mixins import CollectionMixin, CountryMixin, GenreMixin, LabelMixin, MoodMixin, SimilarArtistMixin, StyleMixin @@ -67,18 +68,6 @@ def _loadData(self, data): self.userRating = utils.cast(float, data.attrib.get('userRating', 0)) self.viewCount = utils.cast(int, data.attrib.get('viewCount', 0)) - @property - def thumbUrl(self): - """ Return url to for the thumbnail image. """ - key = self.firstAttr('thumb', 'parentThumb', 'granparentThumb') - return self._server.url(key, includeToken=True) if key else None - - @property - def artUrl(self): - """ Return the first art url starting on the most specific for that item.""" - art = self.firstAttr('art', 'grandparentArt') - return self._server.url(art, includeToken=True) if art else None - def url(self, part): """ Returns the full URL for the audio item. Typically used for getting a specific track. """ return self._server.url(part, includeToken=True) if part else None @@ -336,7 +325,7 @@ def _defaultSyncTitle(self): @utils.registerPlexObject -class Track(Audio, Playable, MoodMixin): +class Track(Audio, Playable, ArtUrlMixin, PosterUrlMixin, MoodMixin): """ Represents a single Track. Attributes: diff --git a/plexapi/base.py b/plexapi/base.py index 4fd37a530..9c3446be3 100644 --- a/plexapi/base.py +++ b/plexapi/base.py @@ -232,7 +232,7 @@ def findItems(self, data, cls=None, initpath=None, **kwargs): def firstAttr(self, *attrs): """ Return the first attribute in attrs that is not None. """ for attr in attrs: - value = self.__dict__.get(attr) + value = getattr(self, attr, None) if value is not None: return value @@ -580,11 +580,6 @@ def iterParts(self): for part in item.parts: yield part - def unmatch(self): - """Unmatch a media file.""" - key = '%s/unmatch' % self.key - return self._server.query(key, method=self._server._session.put) - def play(self, client): """ Start playback on the specified client. diff --git a/plexapi/collection.py b/plexapi/collection.py index 4a07a20a3..6482cd9fc 100644 --- a/plexapi/collection.py +++ b/plexapi/collection.py @@ -79,16 +79,6 @@ def _loadData(self, data): def children(self): return self.items() - @property - def thumbUrl(self): - """ Return the thumbnail url for the collection.""" - return self._server.url(self.thumb, includeToken=True) if self.thumb else None - - @property - def artUrl(self): - """ Return the art url for the collection.""" - return self._server.url(self.art, includeToken=True) if self.art else None - def item(self, title): """ Returns the item in the collection that matches the specified title. diff --git a/plexapi/media.py b/plexapi/media.py index 9c247d688..735bbe1bf 100644 --- a/plexapi/media.py +++ b/plexapi/media.py @@ -807,8 +807,8 @@ class Style(MediaTag): FILTER = 'style' -class BasePosterArt(PlexObject): - """ Base class for all Poster and Art objects. +class BaseImage(PlexObject): + """ Base class for all Art, Banner, and Poster objects. Attributes: TAG (str): 'Photo' @@ -837,12 +837,16 @@ def select(self): pass -class Poster(BasePosterArt): - """ Represents a single Poster object. """ +class Art(BaseImage): + """ Represents a single Art object. """ -class Art(BasePosterArt): - """ Represents a single Art object. """ +class Banner(BaseImage): + """ Represents a single Banner object. """ + + +class Poster(BaseImage): + """ Represents a single Poster object. """ @utils.registerPlexObject diff --git a/plexapi/mixins.py b/plexapi/mixins.py index 6faabeffe..4aa6fbcb4 100644 --- a/plexapi/mixins.py +++ b/plexapi/mixins.py @@ -5,15 +5,25 @@ from plexapi.exceptions import NotFound -class ArtMixin(object): - """ Mixin for Plex objects that can have artwork.""" +class ArtUrlMixin(object): + """ Mixin for Plex objects that can have a background artwork url. """ + + @property + def artUrl(self): + """ Return the art url for the Plex object. """ + art = self.firstAttr('art', 'grandparentArt') + return self._server.url(art, includeToken=True) if art else None + + +class ArtMixin(ArtUrlMixin): + """ Mixin for Plex objects that can have background artwork. """ def arts(self): """ Returns list of available :class:`~plexapi.media.Art` objects. """ return self.fetchItems('/library/metadata/%s/arts' % self.ratingKey, cls=media.Art) def uploadArt(self, url=None, filepath=None): - """ Upload art from url or filepath and set it as the selected art. + """ Upload a background artwork from a url or filepath. Parameters: url (str): The full URL to the image to upload. @@ -28,7 +38,7 @@ def uploadArt(self, url=None, filepath=None): self._server.query(key, method=self._server._session.post, data=data) def setArt(self, art): - """ Set the artwork for a Plex object. + """ Set the background artwork for a Plex object. Parameters: art (:class:`~plexapi.media.Art`): The art object to select. @@ -36,15 +46,71 @@ def setArt(self, art): art.select() -class PosterMixin(object): - """ Mixin for Plex objects that can have posters.""" +class BannerUrlMixin(object): + """ Mixin for Plex objects that can have a banner url. """ + + @property + def bannerUrl(self): + """ Return the banner url for the Plex object. """ + banner = self.firstAttr('banner') + return self._server.url(banner, includeToken=True) if banner else None + + +class BannerMixin(BannerUrlMixin): + """ Mixin for Plex objects that can have banners. """ + + def banners(self): + """ Returns list of available :class:`~plexapi.media.Banner` objects. """ + return self.fetchItems('/library/metadata/%s/banners' % self.ratingKey, cls=media.Banner) + + def uploadBanner(self, url=None, filepath=None): + """ Upload a banner from a url or filepath. + + Parameters: + url (str): The full URL to the image to upload. + filepath (str): The full file path the the image to upload. + """ + if url: + key = '/library/metadata/%s/banners?url=%s' % (self.ratingKey, quote_plus(url)) + self._server.query(key, method=self._server._session.post) + elif filepath: + key = '/library/metadata/%s/banners?' % self.ratingKey + data = open(filepath, 'rb').read() + self._server.query(key, method=self._server._session.post, data=data) + + def setBanner(self, banner): + """ Set the banner for a Plex object. + + Parameters: + banner (:class:`~plexapi.media.Banner`): The banner object to select. + """ + banner.select() + + +class PosterUrlMixin(object): + """ Mixin for Plex objects that can have a poster url. """ + + @property + def thumbUrl(self): + """ Return the thumb url for the Plex object. """ + thumb = self.firstAttr('thumb', 'parentThumb', 'granparentThumb') + return self._server.url(thumb, includeToken=True) if thumb else None + + @property + def posterUrl(self): + """ Alias to self.thumbUrl. """ + return self.thumbUrl + + +class PosterMixin(PosterUrlMixin): + """ Mixin for Plex objects that can have posters. """ def posters(self): """ Returns list of available :class:`~plexapi.media.Poster` objects. """ return self.fetchItems('/library/metadata/%s/posters' % self.ratingKey, cls=media.Poster) def uploadPoster(self, url=None, filepath=None): - """ Upload poster from url or filepath and set it as the selected poster. + """ Upload a poster from a url or filepath. Parameters: url (str): The full URL to the image to upload. @@ -68,7 +134,7 @@ def setPoster(self, poster): class SplitMergeMixin(object): - """ Mixin for Plex objects that can be split and merged.""" + """ Mixin for Plex objects that can be split and merged. """ def split(self): """ Split duplicated Plex object into separate objects. """ @@ -89,7 +155,7 @@ def merge(self, ratingKeys): class UnmatchMatchMixin(object): - """ Mixin for Plex objects that can be unmatched and matched.""" + """ Mixin for Plex objects that can be unmatched and matched. """ def unmatch(self): """ Unmatches metadata match from object. """ diff --git a/plexapi/photo.py b/plexapi/photo.py index eb2867517..398cd7daf 100644 --- a/plexapi/photo.py +++ b/plexapi/photo.py @@ -4,11 +4,11 @@ from plexapi import media, utils, video from plexapi.base import Playable, PlexPartialObject from plexapi.exceptions import BadRequest -from plexapi.mixins import TagMixin +from plexapi.mixins import ArtUrlMixin, ArtMixin, PosterUrlMixin, PosterMixin, TagMixin @utils.registerPlexObject -class Photoalbum(PlexPartialObject): +class Photoalbum(PlexPartialObject, ArtMixin, PosterMixin): """ Represents a single Photoalbum (collection of photos). Attributes: @@ -137,7 +137,7 @@ def download(self, savepath=None, keep_original_name=False, showstatus=False): @utils.registerPlexObject -class Photo(PlexPartialObject, Playable, TagMixin): +class Photo(PlexPartialObject, Playable, ArtUrlMixin, PosterUrlMixin, TagMixin): """ Represents a single Photo. Attributes: @@ -208,12 +208,6 @@ def _loadData(self, data): self.updatedAt = utils.toDatetime(data.attrib.get('updatedAt')) self.year = utils.cast(int, data.attrib.get('year')) - @property - def thumbUrl(self): - """Return URL for the thumbnail image.""" - key = self.firstAttr('thumb', 'parentThumb', 'granparentThumb') - return self._server.url(key, includeToken=True) if key else None - def photoalbum(self): """ Return the photo's :class:`~plexapi.photo.Photoalbum`. """ return self.fetchItem(self.parentKey) diff --git a/plexapi/playlist.py b/plexapi/playlist.py index 399d55aa9..36179dc58 100644 --- a/plexapi/playlist.py +++ b/plexapi/playlist.py @@ -63,6 +63,11 @@ def __iter__(self): # pragma: no cover for item in self.items(): yield item + @property + def thumb(self): + """ Alias to self.composite. """ + return self.composite + @property def metadataType(self): if self.isVideo: diff --git a/plexapi/video.py b/plexapi/video.py index 98a24c7d7..e32deca14 100644 --- a/plexapi/video.py +++ b/plexapi/video.py @@ -5,7 +5,8 @@ from plexapi import library, media, settings, utils from plexapi.base import Playable, PlexPartialObject from plexapi.exceptions import BadRequest, NotFound -from plexapi.mixins import ArtMixin, PosterMixin, SplitMergeMixin, UnmatchMatchMixin +from plexapi.mixins import ArtUrlMixin, ArtMixin, BannerMixin, PosterUrlMixin, PosterMixin +from plexapi.mixins import SplitMergeMixin, UnmatchMatchMixin from plexapi.mixins import CollectionMixin, CountryMixin, DirectorMixin, GenreMixin, LabelMixin, ProducerMixin, WriterMixin @@ -66,20 +67,6 @@ def isWatched(self): """ Returns True if this video is watched. """ return bool(self.viewCount > 0) if self.viewCount else False - @property - def thumbUrl(self): - """ Return the first first thumbnail url starting on - the most specific thumbnail for that item. - """ - thumb = self.firstAttr('thumb', 'parentThumb', 'granparentThumb') - return self._server.url(thumb, includeToken=True) if thumb else None - - @property - def artUrl(self): - """ Return the first first art url starting on the most specific for that item.""" - art = self.firstAttr('art', 'grandparentArt') - return self._server.url(art, includeToken=True) if art else None - def url(self, part): """ Returns the full url for something. Typically used for getting a specific image. """ return self._server.url(part, includeToken=True) if part else None @@ -388,7 +375,7 @@ def download(self, savepath=None, keep_original_name=False, **kwargs): @utils.registerPlexObject -class Show(Video, ArtMixin, PosterMixin, SplitMergeMixin, UnmatchMatchMixin, +class Show(Video, ArtMixin, BannerMixin, PosterMixin, SplitMergeMixin, UnmatchMatchMixin, CollectionMixin, GenreMixin, LabelMixin): """ Represents a single Show (including all seasons and episodes). @@ -853,8 +840,8 @@ def _defaultSyncTitle(self): @utils.registerPlexObject -class Clip(Video, Playable): - """Represents a single Clip. +class Clip(Video, Playable, ArtUrlMixin, PosterUrlMixin): + """ Represents a single Clip. Attributes: TAG (str): 'Video' @@ -876,7 +863,7 @@ class Clip(Video, Playable): METADATA_TYPE = 'clip' def _loadData(self, data): - """Load attribute values from Plex XML response.""" + """ Load attribute values from Plex XML response. """ Video._loadData(self, data) Playable._loadData(self, data) self._data = data diff --git a/tests/conftest.py b/tests/conftest.py index 2039a0774..15ce7fdc0 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -274,6 +274,11 @@ def photoalbum(photos): return photos.get("photo_album1") +@pytest.fixture() +def photo(photoalbum): + return photoalbum.photo("photo1") + + @pytest.fixture() def subtitle(): mopen = mock_open() @@ -375,6 +380,14 @@ def is_string(value, gte=1): return isinstance(value, str) and len(value) >= gte +def is_art(key): + return is_metadata(key, contains="/art/") + + +def is_banner(key): + return is_metadata(key, contains="/banner/") + + def is_thumb(key): return is_metadata(key, contains="/thumb/") diff --git a/tests/test_audio.py b/tests/test_audio.py index 13ab8066a..d6a0d24bf 100644 --- a/tests/test_audio.py +++ b/tests/test_audio.py @@ -1,6 +1,4 @@ # -*- coding: utf-8 -*- -from datetime import datetime - from . import conftest as utils from . import test_mixins @@ -8,6 +6,8 @@ def test_audio_Artist_attr(artist): artist.reload() assert utils.is_datetime(artist.addedAt) + if artist.art: + assert utils.is_art(artist.art) if artist.countries: assert "United States of America" in [i.tag for i in artist.countries] #assert "Electronic" in [i.tag for i in artist.genres] @@ -24,6 +24,8 @@ def test_audio_Artist_attr(artist): assert isinstance(artist.similar, list) if artist.summary: assert "Alias" in artist.summary + if artist.thumb: + assert utils.is_thumb(artist.thumb) assert artist.title == "Broke For Free" assert artist.titleSort == "Broke For Free" assert artist.type == "artist" @@ -64,6 +66,13 @@ def test_audio_Artist_albums(artist): assert len(albums) == 1 and albums[0].title == "Layers" +def test_audio_Artist_mixins_images(artist): + test_mixins.edit_art(artist) + test_mixins.edit_poster(artist) + test_mixins.attr_artUrl(artist) + test_mixins.attr_posterUrl(artist) + + def test_audio_Artist_mixins_tags(artist): test_mixins.edit_collection(artist) test_mixins.edit_country(artist) @@ -75,6 +84,8 @@ def test_audio_Artist_mixins_tags(artist): def test_audio_Album_attrs(album): assert utils.is_datetime(album.addedAt) + if album.art: + assert utils.is_art(album.art) assert isinstance(album.genres, list) assert album.index == 1 assert utils.is_metadata(album._initpath) @@ -85,21 +96,20 @@ def test_audio_Album_attrs(album): assert utils.is_metadata(album.parentKey) assert utils.is_int(album.parentRatingKey) if album.parentThumb: - assert utils.is_metadata(album.parentThumb, contains="/thumb/") + assert utils.is_thumb(album.parentThumb) assert album.parentTitle == "Broke For Free" assert album.ratingKey >= 1 assert album._server._baseurl == utils.SERVER_BASEURL assert album.studio == "[no label]" assert album.summary == "" if album.thumb: - assert utils.is_metadata(album.thumb, contains="/thumb/") + assert utils.is_thumb(album.thumb) assert album.title == "Layers" assert album.titleSort == "Layers" assert album.type == "album" assert utils.is_datetime(album.updatedAt) assert utils.is_int(album.viewCount, gte=0) assert album.year in (2012,) - assert album.artUrl is None def test_audio_Album_history(album): @@ -114,35 +124,7 @@ def test_audio_Track_history(track): def test_audio_Album_tracks(album): tracks = album.tracks() - track = tracks[0] assert len(tracks) == 1 - assert utils.is_metadata(track.grandparentKey) - assert utils.is_int(track.grandparentRatingKey) - assert track.grandparentTitle == "Broke For Free" - assert track.index == 1 - assert utils.is_metadata(track._initpath) - assert utils.is_metadata(track.key) - assert track.listType == "audio" - assert track.originalTitle in (None, "Broke For Free") - # assert utils.is_int(track.parentIndex) - assert utils.is_metadata(track.parentKey) - assert utils.is_int(track.parentRatingKey) - if track.parentThumb: - assert utils.is_metadata(track.parentThumb, contains="/thumb/") - assert track.parentTitle == "Layers" - # assert track.ratingCount == 9 # Flaky - assert utils.is_int(track.ratingKey) - assert track._server._baseurl == utils.SERVER_BASEURL - assert track.summary == "" - if track.thumb: - assert utils.is_metadata(track.thumb, contains="/thumb/") - assert track.title == "As Colourful as Ever" - assert track.titleSort == "As Colourful as Ever" - assert not track.transcodeSessions - assert track.type == "track" - assert utils.is_datetime(track.updatedAt) - assert utils.is_int(track.viewCount, gte=0) - assert track.viewOffset == 0 def test_audio_Album_track(album, track=None): @@ -150,64 +132,6 @@ def test_audio_Album_track(album, track=None): track = track or album.track("As Colourful As Ever") track2 = album.track(track=1) assert track == track2 - assert utils.is_datetime(track.addedAt) - assert utils.is_int(track.duration) - assert utils.is_metadata(track.grandparentKey) - assert utils.is_int(track.grandparentRatingKey) - assert track.grandparentTitle == "Broke For Free" - assert int(track.index) == 1 - assert utils.is_metadata(track._initpath) - assert utils.is_metadata(track.key) - assert track.listType == "audio" - # Assign 0 track.media - media = track.media[0] - assert track.originalTitle in (None, "As Colourful As Ever") - # Fix me - assert utils.is_int(track.parentIndex) - assert utils.is_metadata(track.parentKey) - assert utils.is_int(track.parentRatingKey) - if track.parentThumb: - assert utils.is_metadata(track.parentThumb, contains="/thumb/") - assert track.parentTitle == "Layers" - # assert track.ratingCount == 9 - assert utils.is_int(track.ratingKey) - assert track._server._baseurl == utils.SERVER_BASEURL - assert track.summary == "" - if track.thumb: - assert utils.is_metadata(track.thumb, contains="/thumb/") - assert track.title == "As Colourful as Ever" - assert track.titleSort == "As Colourful as Ever" - assert not track.transcodeSessions - assert track.type == "track" - assert utils.is_datetime(track.updatedAt) - assert utils.is_int(track.viewCount, gte=0) - assert track.viewOffset == 0 - assert media.aspectRatio is None - assert media.audioChannels == 2 - assert media.audioCodec == "mp3" - assert media.bitrate == 128 - assert media.container == "mp3" - assert utils.is_int(media.duration) - assert media.height in (None, 1080) - assert utils.is_int(media.id, gte=1) - assert utils.is_metadata(media._initpath) - assert media.optimizedForStreaming in (None, True) - # Assign 0 media.parts - part = media.parts[0] - assert media._server._baseurl == utils.SERVER_BASEURL - assert media.videoCodec is None - assert media.videoFrameRate is None - assert media.videoResolution is None - assert media.width is None - assert part.container == "mp3" - assert utils.is_int(part.duration) - assert part.file.endswith(".mp3") - assert utils.is_int(part.id) - assert utils.is_metadata(part._initpath) - assert utils.is_part(part.key) - assert part._server._baseurl == utils.SERVER_BASEURL - assert part.size == 3761053 - assert track.artUrl is None def test_audio_Album_get(album): @@ -221,6 +145,13 @@ def test_audio_Album_artist(album): artist.title == "Broke For Free" +def test_audio_Album_mixins_images(album): + test_mixins.edit_art(album) + test_mixins.edit_poster(album) + test_mixins.attr_artUrl(album) + test_mixins.attr_posterUrl(album) + + def test_audio_Album_mixins_tags(album): test_mixins.edit_collection(album) test_mixins.edit_genre(album) @@ -232,14 +163,16 @@ def test_audio_Album_mixins_tags(album): def test_audio_Track_attrs(album): track = album.get("As Colourful As Ever").reload() assert utils.is_datetime(track.addedAt) - assert track.art is None + if track.art: + assert utils.is_art(track.art) assert track.chapterSource is None assert utils.is_int(track.duration) - assert track.grandparentArt is None + if track.grandparentArt: + assert utils.is_art(track.grandparentArt) assert utils.is_metadata(track.grandparentKey) assert utils.is_int(track.grandparentRatingKey) if track.grandparentThumb: - assert utils.is_metadata(track.grandparentThumb, contains="/thumb/") + assert utils.is_thumb(track.grandparentThumb) assert track.grandparentTitle == "Broke For Free" assert track.guid.startswith("mbid://") or track.guid.startswith("plex://track/") assert int(track.index) == 1 @@ -258,7 +191,7 @@ def test_audio_Track_attrs(album): assert utils.is_metadata(track.parentKey) assert utils.is_int(track.parentRatingKey) if track.parentThumb: - assert utils.is_metadata(track.parentThumb, contains="/thumb/") + assert utils.is_thumb(track.parentThumb) assert track.parentTitle == "Layers" assert track.playlistItemID is None assert track.primaryExtraKey is None @@ -268,7 +201,7 @@ def test_audio_Track_attrs(album): assert track.sessionKey is None assert track.summary == "" if track.thumb: - assert utils.is_metadata(track.thumb, contains="/thumb/") + assert utils.is_thumb(track.thumb) assert track.title == "As Colourful as Ever" assert track.titleSort == "As Colourful as Ever" assert not track.transcodeSessions @@ -346,6 +279,11 @@ def test_audio_Track_artist(album, artist): assert tracks[0].artist() == artist +def test_audio_Track_mixins_images(track): + test_mixins.attr_artUrl(track) + test_mixins.attr_posterUrl(track) + + def test_audio_Track_mixins_tags(track): test_mixins.edit_mood(track) diff --git a/tests/test_collection.py b/tests/test_collection.py index 18f93caa7..dbb0d64fc 100644 --- a/tests/test_collection.py +++ b/tests/test_collection.py @@ -93,16 +93,6 @@ def test_Collection_items(collection): assert len(items) == 1 -def test_Collection_thumbUrl(collection): - assert utils.SERVER_BASEURL in collection.thumbUrl - assert "/library/collections/" in collection.thumbUrl - assert "/composite/" in collection.thumbUrl - - -def test_Collection_artUrl(collection): - assert collection.artUrl is None # Collections don't have default art - - def test_Collection_posters(collection): posters = collection.posters() assert posters @@ -113,5 +103,12 @@ def test_Collection_art(collection): assert not arts # Collection has no default art +def test_Collection_mixins_images(collection): + test_mixins.edit_art(collection) + test_mixins.edit_poster(collection) + test_mixins.attr_artUrl(collection) + test_mixins.attr_posterUrl(collection) + + def test_Collection_mixins_tags(collection): test_mixins.edit_label(collection) diff --git a/tests/test_mixins.py b/tests/test_mixins.py index c0ff9e445..cb7549b6a 100644 --- a/tests/test_mixins.py +++ b/tests/test_mixins.py @@ -1,7 +1,10 @@ # -*- coding: utf-8 -*- from plexapi.utils import tag_singular +from . import conftest as utils + TEST_MIXIN_TAG = "Test Tag" +CUTE_CAT_SHA1 = "9f7003fc401761d8e0b0364d428b2dab2f789dbb" def _test_mixins_tag(obj, attr, tag_method): @@ -71,3 +74,75 @@ def edit_tag(obj): def edit_writer(obj): _test_mixins_tag(obj, "writers", "Writer") + + +def _test_mixins_image(obj, attr): + cap_attr = attr[:-1].capitalize() + get_img_method = getattr(obj, attr) + set_img_method = getattr(obj, "set" + cap_attr) + upload_img_method = getattr(obj, "upload" + cap_attr) + images = get_img_method() + if images: + default_image = images[0] + image = images[0] + assert len(image.key) >= 10 + if not image.ratingKey.startswith(("default://", "id://", "media://", "upload://")): + assert image.provider + assert len(image.ratingKey) >= 10 + assert utils.is_bool(image.selected) + assert len(image.thumb) >= 10 + if len(images) >= 2: + # Select a different image + set_img_method(images[1]) + images = get_img_method() + assert images[0].selected is False + assert images[1].selected is True + else: + default_image = None + # Test upload image from file + upload_img_method(filepath=utils.STUB_IMAGE_PATH) + images = get_img_method() + file_image = [ + i for i in images + if i.ratingKey.startswith('upload://') and i.ratingKey.endswith(CUTE_CAT_SHA1) + ] + assert file_image + # Reset to default image + if default_image: + set_img_method(default_image) + + +def edit_art(obj): + _test_mixins_image(obj, 'arts') + + +def edit_banner(obj): + _test_mixins_image(obj, 'banners') + + +def edit_poster(obj): + _test_mixins_image(obj, 'posters') + + +def _test_mixins_imageUrl(obj, attr): + 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 + else: + assert url is None + + +def attr_artUrl(obj): + _test_mixins_imageUrl(obj, 'art') + + +def attr_bannerUrl(obj): + _test_mixins_imageUrl(obj, 'banner') + + +def attr_posterUrl(obj): + _test_mixins_imageUrl(obj, 'thumb') diff --git a/tests/test_photo.py b/tests/test_photo.py index b4eadb98f..ebe85ff7a 100644 --- a/tests/test_photo.py +++ b/tests/test_photo.py @@ -1,3 +1,7 @@ +# -*- coding: utf-8 -*- +from . import test_mixins + + def test_photo_Photoalbum(photoalbum): assert len(photoalbum.albums()) == 3 assert len(photoalbum.photos()) == 3 @@ -5,3 +9,14 @@ def test_photo_Photoalbum(photoalbum): assert len(cats_in_bed.photos()) == 7 a_pic = cats_in_bed.photo("photo7") assert a_pic + + +def test_photo_Photoalbum_mixins_images(photoalbum): + test_mixins.edit_art(photoalbum) + test_mixins.edit_poster(photoalbum) + test_mixins.attr_artUrl(photoalbum) + test_mixins.attr_posterUrl(photoalbum) + + +def test_photo_Photo_mixins_tags(photo): + test_mixins.edit_tag(photo) diff --git a/tests/test_video.py b/tests/test_video.py index 5e14cae83..4eba01b0d 100644 --- a/tests/test_video.py +++ b/tests/test_video.py @@ -40,6 +40,11 @@ def test_video_Movie_merge(movie, patched_http_call): movie.merge(1337) +def test_video_Movie_mixins_images(movie): + test_mixins.edit_art(movie) + test_mixins.edit_poster(movie) + + def test_video_Movie_mixins_tags(movie): test_mixins.edit_collection(movie) test_mixins.edit_country(movie) @@ -152,8 +157,8 @@ def test_video_Movie_attrs(movies): assert len(movie.locations) == 1 assert len(movie.locations[0]) >= 10 assert utils.is_datetime(movie.addedAt) - assert utils.is_metadata(movie.art) - assert movie.artUrl + if movie.art: + assert utils.is_art(movie.art) assert float(movie.rating) >= 6.4 assert movie.ratingImage == 'rottentomatoes://image.rating.ripe' assert movie.audienceRating >= 8.5 @@ -194,7 +199,8 @@ def test_video_Movie_attrs(movies): assert movie.studio == "Nina Paley" assert utils.is_string(movie.summary, gte=100) assert movie.tagline == "The Greatest Break-Up Story Ever Told" - assert utils.is_thumb(movie.thumb) + if movie.thumb: + assert utils.is_thumb(movie.thumb) assert movie.title == "Sita Sings the Blues" assert movie.titleSort == "Sita Sings the Blues" assert not movie.transcodeSessions @@ -495,50 +501,6 @@ def parse_params(key): assert len(results) == 0 -def test_video_Movie_poster(movie): - posters = movie.posters() - poster = posters[0] - assert len(poster.key) >= 10 - if not poster.ratingKey.startswith("media://"): - assert poster.provider - assert len(poster.ratingKey) >= 10 - assert utils.is_bool(poster.selected) - assert len(poster.thumb) >= 10 - # Select a different poster - movie.setPoster(posters[1]) - posters = movie.posters() - assert posters[0].selected is False - assert posters[1].selected is True - # Test upload poster from file - movie.uploadPoster(filepath=utils.STUB_IMAGE_PATH) - posters = movie.posters() - file_poster = next(p for p in posters if p.ratingKey.startswith('upload://')) - assert file_poster.selected is True - movie.setPoster(posters[0]) # Reset to default poster - - -def test_video_Movie_art(movie): - arts = movie.arts() - art = arts[0] - assert len(art.key) >= 10 - if not art.ratingKey.startswith("media://"): - assert art.provider - assert len(art.ratingKey) >= 10 - assert utils.is_bool(art.selected) - assert len(art.thumb) >= 10 - # Select a different art - movie.setArt(arts[1]) - arts = movie.arts() - assert arts[0].selected is False - assert arts[1].selected is True - # Test upload poster from file - movie.uploadArt(filepath=utils.STUB_IMAGE_PATH) - arts = movie.arts() - file_art = next(a for a in arts if a.ratingKey.startswith('upload://')) - assert file_art.selected is True - movie.setArt(arts[0]) # Reset to default art - - def test_video_Movie_hubs(movies): movie = movies.get('Big Buck Bunny') hubs = movie.hubs() @@ -567,10 +529,6 @@ def test_video_Show(show): assert show.title == "Game of Thrones" -def test_video_Episode_unmatch(episode, patched_http_call): - episode.unmatch() - - def test_video_Episode_updateProgress(episode, patched_http_call): episode.updateProgress(10 * 60 * 1000) # 10 minutes. @@ -590,8 +548,10 @@ def test_video_Episode_stop(episode, mocker, patched_http_call): def test_video_Show_attrs(show): assert utils.is_datetime(show.addedAt) - assert utils.is_metadata(show.art, contains="/art/") - assert utils.is_metadata(show.banner, contains="/banner/") + if show.art: + assert utils.is_art(show.art) + if show.banner: + assert utils.is_banner(show.banner) assert utils.is_int(show.childCount) assert show.contentRating in utils.CONTENTRATINGS assert utils.is_int(show.duration, gte=1600000) @@ -629,7 +589,8 @@ def test_video_Show_attrs(show): assert show.studio == "HBO" assert utils.is_string(show.summary, gte=100) assert utils.is_metadata(show.theme, contains="/theme/") - assert utils.is_metadata(show.thumb, contains="/thumb/") + if show.thumb: + assert utils.is_thumb(show.thumb) assert show.title == "Game of Thrones" assert show.titleSort == "Game of Thrones" assert show.type == "show" @@ -724,12 +685,6 @@ def test_video_Episode_download(monkeydownload, tmpdir, episode): assert len(with_sceen_size) == 1 -def test_video_Show_thumbUrl(show): - assert utils.SERVER_BASEURL in show.thumbUrl - assert "/library/metadata/" in show.thumbUrl - assert "/thumb/" in show.thumbUrl - - # Analyze seems to fail intermittently @pytest.mark.xfail def test_video_Show_analyze(show): @@ -763,6 +718,15 @@ def test_video_Show_section(show): assert section.title == "TV Shows" +def test_video_Show_mixins_images(show): + test_mixins.edit_art(show) + test_mixins.edit_banner(show) + test_mixins.edit_poster(show) + test_mixins.attr_artUrl(show) + test_mixins.attr_bannerUrl(show) + test_mixins.attr_posterUrl(show) + + def test_video_Show_mixins_tags(show): test_mixins.edit_collection(show) test_mixins.edit_genre(show) @@ -819,10 +783,16 @@ def test_video_Episode_analyze(tvshows): def test_video_Episode_attrs(episode): assert utils.is_datetime(episode.addedAt) + if episode.art: + assert utils.is_art(episode.art) assert episode.contentRating in utils.CONTENTRATINGS if len(episode.directors): assert [i.tag for i in episode.directors] == ["Tim Van Patten"] assert utils.is_int(episode.duration, gte=120000) + if episode.grandparentArt: + assert utils.is_art(episode.grandparentArt) + if episode.grandparentThumb: + assert utils.is_thumb(episode.grandparentThumb) assert episode.grandparentTitle == "Game of Thrones" assert episode.index == 1 assert utils.is_metadata(episode._initpath) @@ -832,13 +802,15 @@ def test_video_Episode_attrs(episode): assert utils.is_int(episode.parentIndex) assert utils.is_metadata(episode.parentKey) assert utils.is_int(episode.parentRatingKey) - assert utils.is_metadata(episode.parentThumb, contains="/thumb/") + if episode.parentThumb: + assert utils.is_thumb(episode.parentThumb) assert episode.rating >= 7.7 assert utils.is_int(episode.ratingKey) assert episode._server._baseurl == utils.SERVER_BASEURL assert episode.skipParent is False assert utils.is_string(episode.summary, gte=100) - assert utils.is_metadata(episode.thumb, contains="/thumb/") + if episode.thumb: + assert utils.is_thumb(episode.thumb) assert episode.title == "Winter Is Coming" assert episode.titleSort == "Winter Is Coming" assert not episode.transcodeSessions @@ -885,6 +857,13 @@ def test_video_Episode_attrs(episode): assert part.accessible +def test_video_Episode_mixins_images(episode): + #test_mixins.edit_art(episode) # Uploading episode artwork is broken in Plex + test_mixins.edit_poster(episode) + test_mixins.attr_artUrl(episode) + test_mixins.attr_posterUrl(episode) + + def test_video_Episode_mixins_tags(episode): test_mixins.edit_director(episode) test_mixins.edit_writer(episode) @@ -908,6 +887,8 @@ def test_video_Season_history(show): def test_video_Season_attrs(show): season = show.season("Season 1") assert utils.is_datetime(season.addedAt) + if season.art: + assert utils.is_art(season.art) assert season.index == 1 assert utils.is_metadata(season._initpath) assert utils.is_metadata(season.key) @@ -916,11 +897,14 @@ def test_video_Season_attrs(show): assert season.listType == "video" assert utils.is_metadata(season.parentKey) assert utils.is_int(season.parentRatingKey) + if season.parentThumb: + assert utils.is_thumb(season.parentThumb) assert season.parentTitle == "Game of Thrones" assert utils.is_int(season.ratingKey) assert season._server._baseurl == utils.SERVER_BASEURL assert season.summary == "" - assert utils.is_metadata(season.thumb, contains="/thumb/") + if season.thumb: + assert utils.is_thumb(season.thumb) assert season.title == "Season 1" assert season.titleSort == "Season 1" assert season.type == "season" @@ -969,6 +953,14 @@ def test_video_Season_episodes(show): assert len(episodes) >= 1 +def test_video_Season_mixins_images(show): + season = show.season(season=1) + test_mixins.edit_art(season) + test_mixins.edit_poster(season) + test_mixins.attr_artUrl(season) + test_mixins.attr_posterUrl(season) + + def test_that_reload_return_the_same_object(plex): # we want to check this that all the urls are correct movie_library_search = plex.library.section("Movies").search("Elephants Dream")[0]