diff --git a/plexapi/audio.py b/plexapi/audio.py index f95d71792..0799dcf39 100644 --- a/plexapi/audio.py +++ b/plexapi/audio.py @@ -328,13 +328,14 @@ def _defaultSyncTitle(self): @utils.registerPlexObject -class Track(Audio, Playable, ArtUrlMixin, PosterUrlMixin, MoodMixin): +class Track(Audio, Playable, ArtUrlMixin, PosterUrlMixin, CollectionMixin, MoodMixin): """ Represents a single Track. Attributes: TAG (str): 'Directory' TYPE (str): 'track' chapterSource (str): Unknown + collections (List<:class:`~plexapi.media.Collection`>): List of collection objects. duration (int): Length of the track in milliseconds. grandparentArt (str): URL to album artist artwork (/library/metadata//art/). grandparentGuid (str): Plex GUID for the album artist (plex://artist/5d07bcb0403c64029053ac4c). @@ -364,6 +365,7 @@ def _loadData(self, data): Audio._loadData(self, data) Playable._loadData(self, data) self.chapterSource = data.attrib.get('chapterSource') + self.collections = self.findItems(data, media.Collection) self.duration = utils.cast(int, data.attrib.get('duration')) self.grandparentArt = data.attrib.get('grandparentArt') self.grandparentGuid = data.attrib.get('grandparentGuid') diff --git a/plexapi/collection.py b/plexapi/collection.py index b342e73e8..de4c165df 100644 --- a/plexapi/collection.py +++ b/plexapi/collection.py @@ -9,7 +9,7 @@ @utils.registerPlexObject -class Collections(PlexPartialObject, ArtMixin, PosterMixin, LabelMixin): +class Collection(PlexPartialObject, ArtMixin, PosterMixin, LabelMixin): """ Represents a single Collection. Attributes: @@ -20,7 +20,9 @@ class Collections(PlexPartialObject, ArtMixin, PosterMixin, LabelMixin): artBlurHash (str): BlurHash string for artwork image. childCount (int): Number of items in the collection. collectionMode (str): How the items in the collection are displayed. + collectionPublished (bool): True if the collection is published to the Plex homepage. collectionSort (str): How to sort the items in the collection. + content (str): The filter URI string for smart collections. contentRating (str) Content rating (PG-13; NR; TV-G). fields (List<:class:`~plexapi.media.Field`>): List of field objects. guid (str): Plex GUID for the collection (collection://XXXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXX). @@ -32,7 +34,9 @@ class Collections(PlexPartialObject, ArtMixin, PosterMixin, LabelMixin): librarySectionTitle (str): :class:`~plexapi.library.LibrarySection` title. maxYear (int): Maximum year for the items in the collection. minYear (int): Minimum year for the items in the collection. + ratingCount (int): The number of ratings. ratingKey (int): Unique key identifying the collection. + 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. thumb (str): URL to thumbnail image (/library/metadata//thumb/). @@ -52,7 +56,9 @@ def _loadData(self, data): self.artBlurHash = data.attrib.get('artBlurHash') self.childCount = utils.cast(int, data.attrib.get('childCount')) self.collectionMode = utils.cast(int, data.attrib.get('collectionMode', '-1')) + self.collectionPublished = utils.cast(bool, data.attrib.get('collectionPublished', '0')) self.collectionSort = utils.cast(int, data.attrib.get('collectionSort', '0')) + self.content = data.attrib.get('content') self.contentRating = data.attrib.get('contentRating') self.fields = self.findItems(data, media.Field) self.guid = data.attrib.get('guid') @@ -64,7 +70,9 @@ def _loadData(self, data): self.librarySectionTitle = data.attrib.get('librarySectionTitle') self.maxYear = utils.cast(int, data.attrib.get('maxYear')) self.minYear = utils.cast(int, data.attrib.get('minYear')) + self.ratingCount = utils.cast(int, data.attrib.get('ratingCount')) self.ratingKey = utils.cast(int, data.attrib.get('ratingKey')) + self.smart = utils.cast(bool, data.attrib.get('smart', '0')) self.subtype = data.attrib.get('subtype') self.summary = data.attrib.get('summary') self.thumb = data.attrib.get('thumb') @@ -119,7 +127,7 @@ def modeUpdate(self, mode=None): showItems (Show this Collection and its Items) Example: - collection = 'plexapi.library.Collections' + collection = 'plexapi.collection.Collection' collection.updateMode(mode="hide") """ mode_dict = {'default': -1, @@ -142,7 +150,7 @@ def sortUpdate(self, sort=None): Example: - colleciton = 'plexapi.library.Collections' + colleciton = 'plexapi.collection.Collection' collection.updateSort(mode="alpha") """ sort_dict = {'release': 0, diff --git a/plexapi/video.py b/plexapi/video.py index 8a3b326dc..ec826b9bb 100644 --- a/plexapi/video.py +++ b/plexapi/video.py @@ -585,12 +585,13 @@ def download(self, savepath=None, keep_original_name=False, **kwargs): @utils.registerPlexObject -class Season(Video, ArtMixin, PosterMixin): +class Season(Video, ArtMixin, PosterMixin, CollectionMixin): """ Represents a single Show Season (including all episodes). Attributes: TAG (str): 'Directory' TYPE (str): 'season' + collections (List<:class:`~plexapi.media.Collection`>): List of collection objects. guids (List<:class:`~plexapi.media.Guid`>): List of guid objects. index (int): Season number. key (str): API URL (/library/metadata/). @@ -611,6 +612,7 @@ class Season(Video, ArtMixin, PosterMixin): def _loadData(self, data): """ Load attribute values from Plex XML response. """ Video._loadData(self, data) + self.collections = self.findItems(data, media.Collection) self.guids = self.findItems(data, media.Guid) self.index = utils.cast(int, data.attrib.get('index')) self.key = self.key.replace('/children', '') # FIX_BUG_50 @@ -713,8 +715,7 @@ def _defaultSyncTitle(self): @utils.registerPlexObject -class Episode(Video, Playable, ArtMixin, PosterMixin, - DirectorMixin, WriterMixin): +class Episode(Video, Playable, ArtMixin, PosterMixin, CollectionMixin, DirectorMixin, WriterMixin): """ Represents a single Shows Episode. Attributes: @@ -724,6 +725,7 @@ class Episode(Video, Playable, ArtMixin, PosterMixin, audienceRatingImage (str): Key to audience rating image (tmdb://image.rating). chapters (List<:class:`~plexapi.media.Chapter`>): List of Chapter objects. chapterSource (str): Chapter source (agent; media; mixed). + collections (List<:class:`~plexapi.media.Collection`>): List of collection objects. contentRating (str) Content rating (PG-13; NR; TV-G). directors (List<:class:`~plexapi.media.Director`>): List of director objects. duration (int): Duration of the episode in milliseconds. @@ -765,6 +767,7 @@ def _loadData(self, data): self.audienceRatingImage = data.attrib.get('audienceRatingImage') self.chapters = self.findItems(data, media.Chapter) self.chapterSource = data.attrib.get('chapterSource') + self.collections = self.findItems(data, media.Collection) self.contentRating = data.attrib.get('contentRating') self.directors = self.findItems(data, media.Director) self.duration = utils.cast(int, data.attrib.get('duration')) diff --git a/tests/test_audio.py b/tests/test_audio.py index e06192e95..fcd131409 100644 --- a/tests/test_audio.py +++ b/tests/test_audio.py @@ -290,6 +290,7 @@ def test_audio_Track_mixins_images(track): def test_audio_Track_mixins_tags(track): + test_mixins.edit_collection(track) test_mixins.edit_mood(track) diff --git a/tests/test_collection.py b/tests/test_collection.py index dbb0d64fc..77815a6cf 100644 --- a/tests/test_collection.py +++ b/tests/test_collection.py @@ -12,7 +12,9 @@ def test_Collection_attrs(collection): assert collection.artBlurHash is None assert collection.childCount == 1 assert collection.collectionMode == -1 + assert collection.collectionPublished is False assert collection.collectionSort == 0 + assert collection.content is None assert collection.contentRating assert not collection.fields assert collection.guid.startswith("collection://") @@ -24,7 +26,9 @@ def test_Collection_attrs(collection): assert collection.librarySectionTitle == "Movies" assert utils.is_int(collection.maxYear) assert utils.is_int(collection.minYear) + assert utils.is_int(collection.ratingCount) assert utils.is_int(collection.ratingKey) + assert collection.smart is False assert collection.subtype == "movie" assert collection.summary == "" assert collection.thumb.startswith("/library/collections/%s/composite" % collection.ratingKey) diff --git a/tests/test_server.py b/tests/test_server.py index 2e0c1dcaa..0b68d0dd3 100644 --- a/tests/test_server.py +++ b/tests/test_server.py @@ -395,7 +395,7 @@ def test_server_system_devices(plex): devices = plex.systemDevices() assert len(devices) device = devices[-1] - assert device.clientIdentifier or device.clientIdentifier is None + assert device.clientIdentifier or device.clientIdentifier == "" assert utils.is_datetime(device.createdAt) assert utils.is_int(device.id) assert len(device.key) diff --git a/tests/test_video.py b/tests/test_video.py index eeb60030a..d9171505a 100644 --- a/tests/test_video.py +++ b/tests/test_video.py @@ -876,6 +876,7 @@ def test_video_Episode_mixins_images(episode): def test_video_Episode_mixins_tags(episode): + test_mixins.edit_collection(episode) test_mixins.edit_director(episode) test_mixins.edit_writer(episode) @@ -992,6 +993,11 @@ def test_video_Season_mixins_images(show): test_mixins.attr_posterUrl(season) +def test_video_Season_mixins_tags(show): + season = show.season(season=1) + test_mixins.edit_collection(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]