Skip to content

Chapter skipping #271

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: develop
Choose a base branch
from
Open
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
6 changes: 6 additions & 0 deletions lib/_included_packages/plexnet/media.py
Original file line number Diff line number Diff line change
Expand Up @@ -226,3 +226,9 @@ class Similar(MediaTag):
class Writer(MediaTag):
TYPE = 'Writer'
FILTER = 'writer'

class Chapter(MediaTag):
TYPE = 'Chapter'

def startTime(self):
return self.get('startTimeOffset', -1).asInt()
1 change: 1 addition & 0 deletions lib/_included_packages/plexnet/video.py
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,7 @@ def _setData(self, data):
Video._setData(self, data)
if self.isFullObject():
self.extras = PlexVideoItemList(data.find('Extras'), initpath=self.initpath, server=self.server, container=self)
self.chapters = plexobjects.PlexItemList(data, media.Chapter, media.Chapter.TYPE)

def reload(self, *args, **kwargs):
if not kwargs.get('_soft'):
Expand Down
7 changes: 4 additions & 3 deletions lib/player.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,16 +155,17 @@ def reset(self):
self.mode = self.MODE_RELATIVE
self.ended = False

def setup(self, duration, offset, bif_url, title='', title2='', seeking=NO_SEEK):
def setup(self, duration, offset, bif_url, title='', title2='', seeking=NO_SEEK, chapters=[]):
self.ended = False
self.baseOffset = offset / 1000.0
self.seeking = seeking
self.duration = duration
self.bifURL = bif_url
self.title = title
self.title2 = title2
self.chapters = chapters
self.getDialog(setup=True)
self.dialog.setup(self.duration, int(self.baseOffset * 1000), self.bifURL, self.title, self.title2)
self.dialog.setup(self.duration, int(self.baseOffset * 1000), self.bifURL, self.title, self.title2, chapters=self.chapters)

def getDialog(self, setup=False):
if not self.dialog:
Expand Down Expand Up @@ -751,7 +752,7 @@ def _playVideo(self, offset=0, seeking=0, force_update=False, playerObject=None)

self.stopAndWait() # Stop before setting up the handler to prevent player events from causing havoc

self.handler.setup(self.video.duration.asInt(), offset, bifURL, title=self.video.grandparentTitle, title2=self.video.title, seeking=seeking)
self.handler.setup(self.video.duration.asInt(), offset, bifURL, title=self.video.grandparentTitle, title2=self.video.title, seeking=seeking, chapters=self.video.chapters)

if meta.isTranscoded:
self.handler.mode = self.handler.MODE_RELATIVE
Expand Down
7 changes: 5 additions & 2 deletions lib/windows/episodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

from lib.util import T

VIDEO_RELOAD_KW = dict(includeExtras=1, includeExtrasCount=10, includeChapters=1)

class EpisodeReloadTask(backgroundthread.Task):
def setup(self, episode, callback):
Expand All @@ -39,7 +40,7 @@ def run(self):
return

try:
self.episode.reload(checkFiles=1)
self.episode.reload(checkFiles=1, includeChapters=1)
if self.isCanceled():
return
self.callback(self.episode)
Expand Down Expand Up @@ -121,6 +122,8 @@ def onFirstInit(self):
self.postSetup()

def doAutoPlay(self):
# First reload the video to get all the other info
self.initialEpisode.reload(checkFiles=1, **VIDEO_RELOAD_KW)
return self.playButtonClicked(force_episode=self.initialEpisode)

def onReInit(self):
Expand All @@ -144,7 +147,7 @@ def setup(self):

def _setup(self):
player.PLAYER.on('new.video', self.onNewVideo)
(self.season or self.show_).reload(includeExtras=1, includeExtrasCount=10)
(self.season or self.show_).reload(checkFiles=1, **VIDEO_RELOAD_KW)

self.updateProperties()
self.fillEpisodes()
Expand Down
4 changes: 3 additions & 1 deletion lib/windows/preplay.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@

from lib.util import T

VIDEO_RELOAD_KW = dict(includeRelated=1, includeRelatedCount=10)
VIDEO_RELOAD_KW = dict(includeExtras=1, includeExtrasCount=10, includeChapters=1)


class PrePlayWindow(kodigui.ControlledWindow, windowutils.UtilMixin):
Expand Down Expand Up @@ -75,6 +75,8 @@ def onFirstInit(self):
self.setup()

def doAutoPlay(self):
# First reload the video to get all the other info
self.video.reload(checkFiles=1, **VIDEO_RELOAD_KW)
return self.playVideo()

def onReInit(self):
Expand Down
80 changes: 59 additions & 21 deletions lib/windows/seekdialog.py
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,13 @@ def onAction(self, action):

else:
self.skipBack(without_osd=True)
if action in (xbmcgui.ACTION_MOVE_UP, xbmcgui.ACTION_MOVE_DOWN):
# we're seeking from the timeline, with the OSD closed; act as we're skipping
if not self._seeking:
self.selectedOffset = self.trueOffset()

if self.skipChapter(forward=(action == xbmcgui.ACTION_MOVE_UP), without_osd=True):
return
if action in (
xbmcgui.ACTION_MOVE_UP,
xbmcgui.ACTION_MOVE_DOWN,
Expand Down Expand Up @@ -449,21 +456,44 @@ def determineSkipStep(self, direction):
return

return step

def skipChapter(self, forward=True, without_osd=False):
lastSelectedOffset = self.selectedOffset
util.DEBUG_LOG('chapter skipping from {0} with formawrd {1}'.format(lastSelectedOffset, forward))
if forward:
nextChapters = [c for c in self.chapters if c.startTime() > lastSelectedOffset]
util.DEBUG_LOG('Found {0} chapters among {1}'.format(len(nextChapters), len(self.chapters)))
if len(nextChapters) == 0:
return False
chapter = nextChapters[0]
else:
startTimeLimit = lastSelectedOffset - 2000
if startTimeLimit < 0:
startTimeLimit = 0
lastChapters = [c for c in self.chapters if c.startTime() <= startTimeLimit]
util.DEBUG_LOG('Found {0} chapters among {1}'.format(len(lastChapters), len(self.chapters)))
if len(lastChapters) == 0:
return False
chapter = lastChapters[-1]

util.DEBUG_LOG('New start time is {0}'.format(chapter.startTime()))
self.skipByOffset(chapter.startTime() - lastSelectedOffset, without_osd=without_osd)
return True

def skipForward(self, without_osd=False):
step = self.determineSkipStep("positive")
if step is not None:
self.seekByOffset(step, without_osd=without_osd)

if self.useAutoSeek:
self.delayedSeek()
else:
self.setProperty('button.seek', '1')
self.skipByStep("positive", without_osd)

def skipBack(self, without_osd=False):
step = self.determineSkipStep("negative")
if step is not None:
self.seekByOffset(step, without_osd=without_osd)
self.skipByStep("negative", without_osd)

def skipByStep(self, direction="positive", without_osd=False):
step = self.determineSkipStep(direction)
self.skipByOffset(step, without_osd)

def skipByOffset(self, offset, without_osd=False):
if offset is not None:
if not self.seekByOffset(offset, without_osd=without_osd):
return

if self.useAutoSeek:
self.delayedSeek()
Expand Down Expand Up @@ -700,24 +730,31 @@ def seekByOffset(self, offset, auto_seek=False, without_osd=False):
:param without_osd: indicates whether this seek was done with or without OSD
:return:
"""
lastSelectedOffset = self.selectedOffset
# If we are seeking forward and already past 5 seconds from end, don't seek at all
if lastSelectedOffset > self.duration - 5000 and offset > 0:
return False

self._seeking = True
self._seekingWithoutOSD = without_osd
lastSelectedOffset = self.selectedOffset
self.selectedOffset += offset
if self.selectedOffset > self.duration:
# offset = +100, at = 80000, duration = 80005, realoffset = 5
self._forcedLastSkipAmount = self.duration - lastSelectedOffset
self.selectedOffset = self.duration
elif self.selectedOffset < 0:
# offset = -100, at = 5, realat = -95, realoffset = -100 - -95 = -5
self._forcedLastSkipAmount = offset - self.selectedOffset
self.selectedOffset = 0
# Don't skip past 5 seconds from end
if self.selectedOffset > self.duration - 5000:
# offset = +100, at = 80000, duration = 80007, realoffset = 2
self._forcedLastSkipAmount = self.duration - 5000 - lastSelectedOffset
self.selectedOffset = self.duration - 5000
# Don't skip back past 1 (0 is handled specially so seeking to 0 will not do a seek)
elif self.selectedOffset < 1:
# offset = -100, at = 5, realat = -95, realoffset = 1 - 5 = -4
self._forcedLastSkipAmount = 1 - lastSelectedOffset
self.selectedOffset = 1

self.updateProgress(set_to_current=False)
self.setBigSeekShift()
if auto_seek:
self.resetAutoSeekTimer()
self.bigSeekHideTimer.reset()
return True

def seekMouse(self, action, without_osd=False, preview=False):
x = self.mouseXTrans(action.getAmount1())
Expand All @@ -740,7 +777,7 @@ def seekMouse(self, action, without_osd=False, preview=False):
self.updateProgress(set_to_current=False)
self.setProperty('button.seek', '1')

def setup(self, duration, offset=0, bif_url=None, title='', title2=''):
def setup(self, duration, offset=0, bif_url=None, title='', title2='', chapters=[]):
self.title = title
self.title2 = title2
self.setProperty('video.title', title)
Expand All @@ -750,6 +787,7 @@ def setup(self, duration, offset=0, bif_url=None, title='', title2=''):
self.baseOffset = offset
self.offset = 0
self._duration = duration
self.chapters = chapters
self.bifURL = bif_url
self.hasBif = bool(self.bifURL)
if self.hasBif:
Expand Down