From 61c06699055d0ee7b7cff573874f407212fe8fbf Mon Sep 17 00:00:00 2001 From: blacktwin Date: Sat, 23 May 2020 02:09:22 -0400 Subject: [PATCH 01/15] create Marker class --- plexapi/media.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/plexapi/media.py b/plexapi/media.py index 50252e4f0..570978646 100644 --- a/plexapi/media.py +++ b/plexapi/media.py @@ -694,6 +694,26 @@ def _loadData(self, data): self.end = cast(int, data.attrib.get('endTimeOffset')) +@utils.registerPlexObject +class Marker(PlexObject): + """ Represents a single Marker media tag. + + Attributes: + TAG (str): 'Marker' + """ + TAG = 'Marker' + + def _loadData(self, data): + self._data = data + self.filter = data.attrib.get('filter') + self.type = data.attrib.get('type') + _tag, _id = self.filter.split('=') + self.tag = self.type + _tag.capitalize() + self.id = _id + self.start = cast(int, data.attrib.get('startTimeOffset')) + self.end = cast(int, data.attrib.get('endTimeOffset')) + + @utils.registerPlexObject class Field(PlexObject): """ Represents a single Field. From 65271c351da1e812c665f81f6c87021510e0f01a Mon Sep 17 00:00:00 2001 From: blacktwin Date: Sat, 23 May 2020 02:12:36 -0400 Subject: [PATCH 02/15] update episodes _include to include markers add markers attrib to episode --- plexapi/video.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plexapi/video.py b/plexapi/video.py index 2dcc73a7f..ea731792f 100644 --- a/plexapi/video.py +++ b/plexapi/video.py @@ -644,7 +644,7 @@ class Episode(Playable, Video): _include = ('?checkFiles=1&includeExtras=1&includeRelated=1' '&includeOnDeck=1&includeChapters=1&includePopularLeaves=1' - '&includeConcerts=1&includePreferences=1') + '&includeMarkers=1&includeConcerts=1&includePreferences=1') def _loadData(self, data): """ Load attribute values from Plex XML response. """ @@ -680,6 +680,7 @@ def _loadData(self, data): self.labels = self.findItems(data, media.Label) self.collections = self.findItems(data, media.Collection) self.chapters = self.findItems(data, media.Chapter) + self.markers = self.findItems(data, media.Marker) def __repr__(self): return '<%s>' % ':'.join([p for p in [ From f6fcf9527209afd85f9c44f9c54a1552c91b15a7 Mon Sep 17 00:00:00 2001 From: blacktwin Date: Sat, 23 May 2020 23:04:55 -0400 Subject: [PATCH 03/15] create Preferences class --- plexapi/settings.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/plexapi/settings.py b/plexapi/settings.py index 1511c0bb0..a102b67a0 100644 --- a/plexapi/settings.py +++ b/plexapi/settings.py @@ -155,3 +155,15 @@ def set(self, value): def toUrl(self): """Helper for urls""" return '%s=%s' % (self.id, self._value or self.value) + + +@utils.registerPlexObject +class Preferences(Setting): + """ Represents a single Preferences. + + Attributes: + TAG (str): 'Preferences' + FILTER (str): 'preferences' + """ + TAG = 'Preferences' + FILTER = 'preferences' From a771feac35819a60d40920704b1be4611b0c8ea3 Mon Sep 17 00:00:00 2001 From: blacktwin Date: Sat, 23 May 2020 23:06:05 -0400 Subject: [PATCH 04/15] import settings --- plexapi/video.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plexapi/video.py b/plexapi/video.py index ea731792f..c60636e11 100644 --- a/plexapi/video.py +++ b/plexapi/video.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from plexapi import media, utils +from plexapi import media, utils, settings from plexapi.exceptions import BadRequest, NotFound from plexapi.base import Playable, PlexPartialObject from plexapi.compat import quote_plus, urlencode From 4db38e683273ebc1f3b8cbec29da7c596ead28f1 Mon Sep 17 00:00:00 2001 From: blacktwin Date: Sat, 23 May 2020 23:07:21 -0400 Subject: [PATCH 05/15] add _include to show add _details_key to show --- plexapi/video.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/plexapi/video.py b/plexapi/video.py index c60636e11..487d67236 100644 --- a/plexapi/video.py +++ b/plexapi/video.py @@ -389,6 +389,10 @@ class Show(Video): TYPE = 'show' METADATA_TYPE = 'episode' + _include = ('?checkFiles=1&includeExtras=1&includeRelated=1' + '&includeOnDeck=1&includeChapters=1&includePopularLeaves=1' + '&includeMarkers=1&includeConcerts=1&includePreferences=1') + def __iter__(self): for season in self.seasons(): yield season @@ -398,6 +402,7 @@ def _loadData(self, data): Video._loadData(self, data) # fix key if loaded from search self.key = self.key.replace('/children', '') + self._details_key = self.key + self._include self.art = data.attrib.get('art') self.banner = data.attrib.get('banner') self.childCount = utils.cast(int, data.attrib.get('childCount')) From 6a69fe4810778b3ccc0a62a6e67745dca3ba53bd Mon Sep 17 00:00:00 2001 From: blacktwin Date: Sat, 23 May 2020 23:07:44 -0400 Subject: [PATCH 06/15] create preferences method --- plexapi/video.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/plexapi/video.py b/plexapi/video.py index 487d67236..b954b55ab 100644 --- a/plexapi/video.py +++ b/plexapi/video.py @@ -435,6 +435,16 @@ def isWatched(self): """ Returns True if this show is fully watched. """ return bool(self.viewedLeafCount == self.leafCount) + def preferences(self): + """ Returns a list of :class:`~plexapi.settings.Preferences` objects. """ + items = [] + data = self._server.query(self._details_key) + for item in data.iter('Preferences'): + for elem in item: + items.append(settings.Preferences(data=elem, server=self._server)) + + return items + def seasons(self, **kwargs): """ Returns a list of :class:`~plexapi.video.Season` objects. """ key = '/library/metadata/%s/children?excludeAllLeaves=1' % self.ratingKey From 81339cc3dc70c7f17e72d382ca5664dd5c907a99 Mon Sep 17 00:00:00 2001 From: blacktwin Date: Sat, 23 May 2020 23:20:22 -0400 Subject: [PATCH 07/15] import library and create hubs method --- plexapi/video.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/plexapi/video.py b/plexapi/video.py index b954b55ab..8f5fdadb3 100644 --- a/plexapi/video.py +++ b/plexapi/video.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from plexapi import media, utils, settings +from plexapi import media, utils, settings, library from plexapi.exceptions import BadRequest, NotFound from plexapi.base import Playable, PlexPartialObject from plexapi.compat import quote_plus, urlencode @@ -445,6 +445,16 @@ def preferences(self): return items + def hubs(self): + """ Returns a list of :class:`~plexapi.library.Hub` objects. """ + items = [] + data = self._server.query(self._details_key) + for item in data.iter('Related'): + for elem in item: + items.append(library.Hub(data=elem, server=self._server)) + + return items + def seasons(self, **kwargs): """ Returns a list of :class:`~plexapi.video.Season` objects. """ key = '/library/metadata/%s/children?excludeAllLeaves=1' % self.ratingKey From ce3fcc9b9db492517bb898a457cd375c4e883210 Mon Sep 17 00:00:00 2001 From: blacktwin Date: Sat, 23 May 2020 23:30:21 -0400 Subject: [PATCH 08/15] update hubs method --- plexapi/video.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/plexapi/video.py b/plexapi/video.py index 8f5fdadb3..ff4346742 100644 --- a/plexapi/video.py +++ b/plexapi/video.py @@ -447,11 +447,9 @@ def preferences(self): def hubs(self): """ Returns a list of :class:`~plexapi.library.Hub` objects. """ - items = [] data = self._server.query(self._details_key) for item in data.iter('Related'): - for elem in item: - items.append(library.Hub(data=elem, server=self._server)) + return self.findItems(item, library.Hub) return items From 1cfc9869d13dd1cd80f5e2fa1ce10a4058c53c21 Mon Sep 17 00:00:00 2001 From: blacktwin Date: Sat, 23 May 2020 23:30:44 -0400 Subject: [PATCH 09/15] create ondeck method for shows --- plexapi/video.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/plexapi/video.py b/plexapi/video.py index ff4346742..38b562b2d 100644 --- a/plexapi/video.py +++ b/plexapi/video.py @@ -451,7 +451,10 @@ def hubs(self): for item in data.iter('Related'): return self.findItems(item, library.Hub) - return items + def onDeck(self): + """ Returns a list of :class:`~plexapi.video.video` objects. """ + data = self._server.query(self._details_key) + return self.findItems([item for item in data.iter('OnDeck')][0])[0] def seasons(self, **kwargs): """ Returns a list of :class:`~plexapi.video.Season` objects. """ From 8a744f85f4d0cfd9552c604c02d41e8e2129d3c0 Mon Sep 17 00:00:00 2001 From: blacktwin Date: Sun, 24 May 2020 22:24:32 -0400 Subject: [PATCH 10/15] correcting onDeck docstring --- plexapi/video.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/plexapi/video.py b/plexapi/video.py index 38b562b2d..a270af5f1 100644 --- a/plexapi/video.py +++ b/plexapi/video.py @@ -452,7 +452,9 @@ def hubs(self): return self.findItems(item, library.Hub) def onDeck(self): - """ Returns a list of :class:`~plexapi.video.video` objects. """ + """ Returns shows On Deck :class:`~plexapi.video.Video` object. + If show is unwatched, return will likely be the first episode. + """ data = self._server.query(self._details_key) return self.findItems([item for item in data.iter('OnDeck')][0])[0] From 5e3dff4e323dc7daf885424eeefdd3c8cb58a1b8 Mon Sep 17 00:00:00 2001 From: blacktwin Date: Sun, 24 May 2020 22:55:52 -0400 Subject: [PATCH 11/15] update analyze docstring to include new intro video marker --- plexapi/base.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plexapi/base.py b/plexapi/base.py index 1fd4ad53a..79089c76a 100644 --- a/plexapi/base.py +++ b/plexapi/base.py @@ -322,6 +322,8 @@ def analyze(self): Playing screen to show a graphical representation of where playback is. Video preview thumbnails creation is a CPU-intensive process akin to transcoding the file. + * Generate intro video markers: Detects show intros, exposing the + 'Skip Intro' button in clients. """ key = '/%s/analyze' % self.key.lstrip('/') self._server.query(key, method=self._server._session.put) From a2ef4a5564a5d7d83835c8ac274ac4863656ee92 Mon Sep 17 00:00:00 2001 From: blacktwin Date: Sun, 24 May 2020 22:57:02 -0400 Subject: [PATCH 12/15] create hasIntroMarker method to quickly identify if an episode has an intro marker --- plexapi/video.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/plexapi/video.py b/plexapi/video.py index a270af5f1..b120ee3ff 100644 --- a/plexapi/video.py +++ b/plexapi/video.py @@ -740,6 +740,13 @@ def seasonEpisode(self): """ Returns the s00e00 string containing the season and episode. """ return 's%se%s' % (str(self.seasonNumber).zfill(2), str(self.index).zfill(2)) + @property + def hasIntroMarker(self): + """ Returns True if this episode has an intro marker in the xml. """ + if not self.isFullObject(): + self.reload() + return bool(self.markers) + def season(self): """" Return this episodes :func:`~plexapi.video.Season`.. """ return self.fetchItem(self.parentKey) From 660a6653664581a24f672301b86708ff8ecb505e Mon Sep 17 00:00:00 2001 From: blacktwin Date: Wed, 27 May 2020 12:15:23 -0400 Subject: [PATCH 13/15] only check for intro markers in case Plex decides to add different marker types in the future thanks @jonnywong16 --- plexapi/video.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plexapi/video.py b/plexapi/video.py index b120ee3ff..a3b1030b0 100644 --- a/plexapi/video.py +++ b/plexapi/video.py @@ -745,7 +745,7 @@ def hasIntroMarker(self): """ Returns True if this episode has an intro marker in the xml. """ if not self.isFullObject(): self.reload() - return bool(self.markers) + return bool(any(marker.type == 'intro' for marker in self.markers)) def season(self): """" Return this episodes :func:`~plexapi.video.Season`.. """ From 558eafa44feaf776160c977fce1b538490951a1c Mon Sep 17 00:00:00 2001 From: blacktwin Date: Wed, 27 May 2020 12:26:54 -0400 Subject: [PATCH 14/15] no need for double bool --- plexapi/video.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plexapi/video.py b/plexapi/video.py index a3b1030b0..ebd9dfeae 100644 --- a/plexapi/video.py +++ b/plexapi/video.py @@ -745,7 +745,7 @@ def hasIntroMarker(self): """ Returns True if this episode has an intro marker in the xml. """ if not self.isFullObject(): self.reload() - return bool(any(marker.type == 'intro' for marker in self.markers)) + return any(marker.type == 'intro' for marker in self.markers) def season(self): """" Return this episodes :func:`~plexapi.video.Season`.. """ From 2d4a919a405c1967f4abb14136bc46787f797b1b Mon Sep 17 00:00:00 2001 From: blacktwin Date: Wed, 27 May 2020 21:53:04 -0400 Subject: [PATCH 15/15] resolving conflict --- plexapi/video.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/plexapi/video.py b/plexapi/video.py index ebd9dfeae..5396d87fa 100644 --- a/plexapi/video.py +++ b/plexapi/video.py @@ -1,9 +1,10 @@ # -*- coding: utf-8 -*- +import os +from urllib.parse import quote_plus, urlencode + from plexapi import media, utils, settings, library -from plexapi.exceptions import BadRequest, NotFound from plexapi.base import Playable, PlexPartialObject -from plexapi.compat import quote_plus, urlencode -import os +from plexapi.exceptions import BadRequest, NotFound class Video(PlexPartialObject):