From 796adbf93a30122d60c7e2c0d2f7c19a20658d1d Mon Sep 17 00:00:00 2001 From: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> Date: Thu, 9 Jul 2026 18:54:07 -0700 Subject: [PATCH 1/3] Add EditionsMixin --- plexapi/mixins/__init__.py | 9 +++++---- plexapi/mixins/editions.py | 12 ++++++++++++ 2 files changed, 17 insertions(+), 4 deletions(-) create mode 100644 plexapi/mixins/editions.py diff --git a/plexapi/mixins/__init__.py b/plexapi/mixins/__init__.py index 740382eee..2665dc2af 100644 --- a/plexapi/mixins/__init__.py +++ b/plexapi/mixins/__init__.py @@ -15,6 +15,7 @@ TrackArtistMixin, TrackDiscNumberMixin, TrackNumberMixin, UserRatingMixin, WriterMixin ) +from .editions import EditionsMixin from .objects import ExtrasMixin, HubsMixin from .played_unplayed import PlayedUnplayedMixin from .rating import RatingMixin @@ -128,7 +129,7 @@ class PlaylistEditMixins( class MovieMixins( - AdvancedSettingsMixin, SplitMergeMixin, UnmatchMatchMixin, ExtrasMixin, HubsMixin, RatingMixin, + AdvancedSettingsMixin, SplitMergeMixin, UnmatchMatchMixin, ExtrasMixin, HubsMixin, RatingMixin, EditionsMixin, ArtMixin, LogoMixin, PosterMixin, SquareArtMixin, ThemeMixin, MovieEditMixins, WatchlistMixin @@ -137,7 +138,7 @@ class MovieMixins( class ShowMixins( - AdvancedSettingsMixin, SplitMergeMixin, UnmatchMatchMixin, ExtrasMixin, HubsMixin, RatingMixin, + AdvancedSettingsMixin, SplitMergeMixin, UnmatchMatchMixin, ExtrasMixin, HubsMixin, RatingMixin, EditionsMixin, ArtMixin, LogoMixin, PosterMixin, SquareArtMixin, ThemeMixin, ShowEditMixins, WatchlistMixin @@ -146,7 +147,7 @@ class ShowMixins( class SeasonMixins( - AdvancedSettingsMixin, ExtrasMixin, RatingMixin, + AdvancedSettingsMixin, ExtrasMixin, RatingMixin, EditionsMixin, ArtMixin, LogoMixin, PosterMixin, SquareArtMixin, ThemeUrlMixin, SeasonEditMixins ): @@ -154,7 +155,7 @@ class SeasonMixins( class EpisodeMixins( - ExtrasMixin, RatingMixin, + ExtrasMixin, RatingMixin, EditionsMixin, ArtMixin, LogoMixin, PosterMixin, SquareArtMixin, ThemeUrlMixin, EpisodeEditMixins ): diff --git a/plexapi/mixins/editions.py b/plexapi/mixins/editions.py new file mode 100644 index 000000000..931cf8d87 --- /dev/null +++ b/plexapi/mixins/editions.py @@ -0,0 +1,12 @@ +class EditionsMixin: + """ Mixin for Plex objects that can have edition. """ + + def editions(self): + """ Returns a list of :class:`~plexapi.base.PlexPartialObject` objects + for other editions of the same media. + """ + filters = { + 'guid': self.guid, + 'id!': self.ratingKey + } + return self.section().search(libtype=self.TYPE, filters=filters) From 794ed9837fe9911861bbf3d32ebfd054bb3a7b4b Mon Sep 17 00:00:00 2001 From: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> Date: Wed, 8 Jul 2026 16:54:22 -0700 Subject: [PATCH 2/3] Add edition support for shows, seasons, and episodes --- plexapi/mixins/__init__.py | 2 +- plexapi/video.py | 16 ++++++---------- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/plexapi/mixins/__init__.py b/plexapi/mixins/__init__.py index 2665dc2af..7875d68e7 100644 --- a/plexapi/mixins/__init__.py +++ b/plexapi/mixins/__init__.py @@ -44,7 +44,7 @@ class MovieEditMixins( class ShowEditMixins( ArtLockMixin, PosterLockMixin, ThemeLockMixin, - AddedAtMixin, AudienceRatingMixin, ContentRatingMixin, CriticRatingMixin, + AddedAtMixin, AudienceRatingMixin, ContentRatingMixin, CriticRatingMixin, EditionTitleMixin, OriginallyAvailableMixin, OriginalTitleMixin, SortTitleMixin, StudioMixin, SummaryMixin, TaglineMixin, TitleMixin, UserRatingMixin, CollectionMixin, GenreMixin, LabelMixin, diff --git a/plexapi/video.py b/plexapi/video.py index 910104a00..44858db23 100644 --- a/plexapi/video.py +++ b/plexapi/video.py @@ -512,16 +512,6 @@ def reviews(self): key = f'{self.key}?includeReviews=1' return self.fetchItems(key, cls=media.Review, rtag='Video') - def editions(self): - """ Returns a list of :class:`~plexapi.video.Movie` objects - for other editions of the same movie. - """ - filters = { - 'guid': self.guid, - 'id!': self.ratingKey - } - return self.section().search(filters=filters) - def removeFromContinueWatching(self): """ Remove the movie from continue watching. """ key = '/actions/removeFromContinueWatching' @@ -560,6 +550,7 @@ class Show( commonSenseMedia (:class:`~plexapi.media.CommonSenseMedia`): Common Sense Media object. contentRating (str) Content rating (PG-13; NR; TV-G). duration (int): Typical duration of the show episodes in milliseconds. + editionTitle (str): The edition title of the show (e.g. Director's Cut, Extended Edition, etc.). enableCreditsMarkerGeneration (int): Setting that indicates if credits markers detection is enabled. (-1 = Library default, 0 = Disabled). episodeSort (int): Setting that indicates how episodes are sorted for the show @@ -616,6 +607,7 @@ def _loadData(self, data): self.childCount = utils.cast(int, data.attrib.get('childCount')) self.contentRating = data.attrib.get('contentRating') self.duration = utils.cast(int, data.attrib.get('duration')) + self.editionTitle = data.attrib.get('editionTitle') self.enableCreditsMarkerGeneration = utils.cast(int, data.attrib.get('enableCreditsMarkerGeneration', '-1')) self.episodeSort = utils.cast(int, data.attrib.get('episodeSort', '-1')) self.flattenSeasons = utils.cast(int, data.attrib.get('flattenSeasons', '-1')) @@ -796,6 +788,7 @@ class Season( audienceRating (float): Audience rating. audioLanguage (str): Setting that indicates the preferred audio language. collections (List<:class:`~plexapi.media.Collection`>): List of collection objects. + editionTitle (str): The edition title of the show (e.g. Director's Cut, Extended Edition, etc.). guids (List<:class:`~plexapi.media.Guid`>): List of guid objects. index (int): Season number. key (str): API URL (/library/metadata/). @@ -828,6 +821,7 @@ def _loadData(self, data): Video._loadData(self, data) self.audienceRating = utils.cast(float, data.attrib.get('audienceRating')) self.audioLanguage = data.attrib.get('audioLanguage', '') + self.editionTitle = data.attrib.get('editionTitle') self.index = utils.cast(int, data.attrib.get('index')) self.key = self.key.replace('/children', '') # FIX_BUG_50 self.leafCount = utils.cast(int, data.attrib.get('leafCount')) @@ -981,6 +975,7 @@ class Episode( 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. + editionTitle (str): The edition title of the show (e.g. Director's Cut, Extended Edition, etc.). grandparentArt (str): URL to show artwork (/library/metadata//art/). grandparentGuid (str): Plex GUID for the show (plex://show/5d9c086fe9d5a1001f4d9fe6). grandparentKey (str): API URL of the show (/library/metadata/). @@ -1027,6 +1022,7 @@ def _loadData(self, data): self.chapterSource = data.attrib.get('chapterSource') self.contentRating = data.attrib.get('contentRating') self.duration = utils.cast(int, data.attrib.get('duration')) + self.editionTitle = data.attrib.get('editionTitle') self.grandparentArt = data.attrib.get('grandparentArt') self.grandparentGuid = data.attrib.get('grandparentGuid') self.grandparentKey = data.attrib.get('grandparentKey') From 2bc50f42cfffa485f87e56b7d156322c1f724b51 Mon Sep 17 00:00:00 2001 From: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> Date: Wed, 8 Jul 2026 16:54:32 -0700 Subject: [PATCH 3/3] Add tests for show editions --- tests/test_video.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/tests/test_video.py b/tests/test_video.py index c3c778a2b..6beec5084 100644 --- a/tests/test_video.py +++ b/tests/test_video.py @@ -835,6 +835,7 @@ def test_video_Show_attrs(show): assert show.audioLanguage == '' assert show.autoDeletionItemPolicyUnwatchedLibrary == 0 assert show.autoDeletionItemPolicyWatchedLibrary == 0 + assert show.editionTitle is None assert show.enableCreditsMarkerGeneration == -1 assert show.episodeSort == -1 assert show.flattenSeasons == -1 @@ -887,6 +888,10 @@ def test_video_Show_attrs(show): assert show.url(None) is None +def test_video_Show_editions(show): + assert len(show.editions()) == 0 + + def test_video_Show_episode(show): episode = show.episode("Winter Is Coming") assert episode == show.episode(season=1, episode=1) @@ -1303,6 +1308,29 @@ def test_video_Season_mixins_fields(show): test_mixins.edit_user_rating(season) +@pytest.mark.anonymously +def test_video_Show_mixins_fields_edition(show): + with pytest.raises(BadRequest): + test_mixins.edit_edition_title(show) + + +@pytest.mark.authenticated +def test_video_Show_mixins_fields_edition_authenticated(account_plexpass, show): + test_mixins.edit_edition_title(show) + + # Manually edit edition title to check season and episode + editionTitle = "Test Edition" + show.editEditionTitle(editionTitle).reload() + assert show.editionTitle == editionTitle + season = show.season(1) + assert season.editionTitle == editionTitle + episode = season.episode(1) + assert episode.editionTitle == editionTitle + + show.editEditionTitle("", locked=False).reload() + assert show.editionTitle is None + + def test_video_Season_mixins_tags(show): season = show.season(season=1) test_mixins.edit_collection(season)