Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions plexapi/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,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)
Expand Down
20 changes: 20 additions & 0 deletions plexapi/media.py
Original file line number Diff line number Diff line change
Expand Up @@ -695,6 +695,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.
Expand Down
12 changes: 12 additions & 0 deletions plexapi/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'
40 changes: 38 additions & 2 deletions plexapi/video.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import os
from urllib.parse import quote_plus, urlencode

from plexapi import media, utils
from plexapi import media, utils, settings, library
from plexapi.base import Playable, PlexPartialObject
from plexapi.exceptions import BadRequest, NotFound

Expand Down Expand Up @@ -390,6 +390,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
Expand All @@ -399,6 +403,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'))
Expand Down Expand Up @@ -431,6 +436,29 @@ 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 hubs(self):
""" Returns a list of :class:`~plexapi.library.Hub` objects. """
data = self._server.query(self._details_key)
for item in data.iter('Related'):
return self.findItems(item, library.Hub)

def onDeck(self):
""" 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]

def seasons(self, **kwargs):
""" Returns a list of :class:`~plexapi.video.Season` objects. """
key = '/library/metadata/%s/children?excludeAllLeaves=1' % self.ratingKey
Expand Down Expand Up @@ -645,7 +673,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. """
Expand Down Expand Up @@ -681,6 +709,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 [
Expand Down Expand Up @@ -712,6 +741,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 any(marker.type == 'intro' for marker in self.markers)

def season(self):
"""" Return this episodes :func:`~plexapi.video.Season`.. """
return self.fetchItem(self.parentKey)
Expand Down