1 from Plugins.Plugin import PluginDescriptor
3 from Screens.Screen import Screen
4 from Screens.MessageBox import MessageBox
5 from Components.ServicePosition import ServicePositionGauge
6 from Components.ActionMap import HelpableActionMap
7 from Components.MultiContent import MultiContentEntryText
8 from Components.ServiceEventTracker import ServiceEventTracker, InfoBarBase
9 from Components.VideoWindow import VideoWindow
10 from Components.Label import Label
11 from Screens.InfoBarGenerics import InfoBarSeek, InfoBarCueSheetSupport
12 from Components.GUIComponent import GUIComponent
13 from enigma import eListboxPythonMultiContent, eListbox, getDesktop, gFont, iPlayableService, RT_HALIGN_RIGHT
14 from Screens.FixedMenu import FixedMenu
15 from Screens.HelpMenu import HelpableScreen
16 from ServiceReference import ServiceReference
17 from Components.Sources.List import List
21 def CutListEntry(where, what):
39 return ((where, what), "%dh:%02dm:%02ds:%03d" % (h, m, s, ms), type, type_col)
41 class CutListContextMenu(FixedMenu):
55 def __init__(self, session, state, nearmark):
56 menu = [(_("back"), self.close)] #, (None, )]
58 if state == self.SHOW_STARTCUT:
59 menu.append((_("start cut here"), self.startCut))
61 menu.append((_("start cut here"), ))
63 if state == self.SHOW_ENDCUT:
64 menu.append((_("end cut here"), self.endCut))
66 menu.append((_("end cut here"), ))
68 if state == self.SHOW_DELETECUT:
69 menu.append((_("delete cut"), self.deleteCut))
71 menu.append((_("delete cut"), ))
73 menu.append((_("remove before this position"), self.removeBefore))
74 menu.append((_("remove after this position"), self.removeAfter))
76 # menu.append((None, ))
79 menu.append((_("insert mark here"), self.insertMark))
81 menu.append((_("remove this mark"), self.removeMark))
83 menu.append((_("grab this frame as bitmap"), self.grabFrame))
84 FixedMenu.__init__(self, session, _("Cut"), menu)
85 self.skinName = "Menu"
88 self.close(self.RET_STARTCUT)
91 self.close(self.RET_ENDCUT)
94 self.close(self.RET_DELETECUT)
97 self.close(self.RET_MARK)
100 self.close(self.RET_DELETEMARK)
102 def removeBefore(self):
103 self.close(self.RET_REMOVEBEFORE)
105 def removeAfter(self):
106 self.close(self.RET_REMOVEAFTER)
109 self.close(self.RET_GRABFRAME)
111 class CutListEditor(Screen, InfoBarBase, InfoBarSeek, InfoBarCueSheetSupport, HelpableScreen):
113 <screen position="0,0" size="720,576" title="Cutlist editor" flags="wfNoBorder">
114 <eLabel text="Cutlist editor" position="65,60" size="300,25" font="Regular;20" />
115 <widget source="global.CurrentTime" render="Label" position="268,60" size="394,20" font="Regular;20" halign="right">
116 <convert type="ClockToText">Format:%A %B %d, %H:%M</convert>
118 <eLabel position="268,98" size="394,304" backgroundColor="#505555" />
119 <widget name="Video" position="270,100" zPosition="1" size="390,300" backgroundColor="transparent" />
120 <widget source="session.CurrentService" render="Label" position="135,405" size="450,50" font="Regular;22" halign="center" valign="center">
121 <convert type="ServiceName">Name</convert>
123 <widget source="session.CurrentService" render="Label" position="320,450" zPosition="1" size="420,25" font="Regular;20" halign="left" valign="center">
124 <convert type="ServicePosition">Position,Detailed</convert>
126 <widget name="SeekState" position="210,450" zPosition="1" size="100,25" halign="right" font="Regular;20" valign="center" />
127 <eLabel position="48,98" size="204,274" backgroundColor="#505555" />
128 <eLabel position="50,100" size="200,270" backgroundColor="#000000" />
129 <widget source="cutlist" position="50,100" zPosition="1" size="200,270" scrollbarMode="showOnDemand" transparent="1" render="Listbox" >
130 <convert type="TemplatedMultiContent">
132 MultiContentEntryText(size=(125, 20), text = 1, backcolor = MultiContentTemplateColor(3)),
133 MultiContentEntryText(pos=(125,0), size=(50, 20), text = 2, flags = RT_HALIGN_RIGHT, backcolor = MultiContentTemplateColor(3))
135 "fonts": [gFont("Regular", 18)],
140 <widget name="Timeline" position="50,485" size="615,20" backgroundColor="#505555" pointer="skin_default/position_arrow.png:3,5" foregroundColor="black" />
141 <ePixmap pixmap="skin_default/icons/mp_buttons.png" position="305,515" size="109,13" alphatest="on" />
144 def __init__(self, session, service):
145 self.skin = CutListEditor.skin
146 Screen.__init__(self, session)
147 InfoBarSeek.__init__(self, actionmap = "CutlistSeekActions")
148 InfoBarCueSheetSupport.__init__(self)
149 InfoBarBase.__init__(self, steal_current_service = True)
150 HelpableScreen.__init__(self)
151 self.old_service = session.nav.getCurrentlyPlayingServiceReference()
152 session.nav.playService(service)
154 service = session.nav.getCurrentService()
155 cue = service and service.cueSheet()
157 # disable cutlists. we want to freely browse around in the movie
158 print "cut lists disabled!"
159 cue.setCutListEnable(0)
161 self.downloadCuesheet()
163 self["Timeline"] = ServicePositionGauge(self.session.nav)
164 self["cutlist"] = List(self.getCutlist())
165 self["cutlist"].onSelectionChanged.append(self.selectionChanged)
166 self["SeekState"] = Label()
167 self.onPlayStateChanged.append(self.updateStateLabel)
168 self.updateStateLabel(self.seekstate)
170 desktopSize = getDesktop(0).size()
171 self["Video"] = VideoWindow(decoder = 0, fb_width=desktopSize.width(), fb_height=desktopSize.height())
173 self["actions"] = HelpableActionMap(self, "CutListEditorActions",
175 "setIn": (self.setIn, _("Make this mark an 'in' point")),
176 "setOut": (self.setOut, _("Make this mark an 'out' point")),
177 "setMark": (self.setMark, _("Make this mark just a mark")),
178 "addMark": (self.__addMark, _("Add a mark")),
179 "removeMark": (self.__removeMark, _("Remove a mark")),
180 "leave": (self.exit, _("Exit editor")),
181 "showMenu": (self.showMenu, _("menu")),
184 self.tutorial_seen = False
186 self.onExecBegin.append(self.showTutorial)
187 self.__event_tracker = ServiceEventTracker(screen=self, eventmap=
189 iPlayableService.evCuesheetChanged: self.refillList
192 # to track new entries we save the last version of the cutlist
193 self.last_cuts = self.getCutlist()
194 self.cut_start = None
195 self.inhibit_seek = False
196 self.onClose.append(self.__onClose)
199 self.session.nav.playService(self.old_service, forceRestart=True)
201 def updateStateLabel(self, state):
202 self["SeekState"].setText(state[3].strip())
204 def showTutorial(self):
205 if not self.tutorial_seen:
206 self.tutorial_seen = True
207 self.session.open(MessageBox,_("Welcome to the Cutlist editor.\n\nSeek to the start of the stuff you want to cut away. Press OK, select 'start cut'.\n\nThen seek to the end, press OK, select 'end cut'. That's it."), MessageBox.TYPE_INFO)
209 def checkSkipShowHideLock(self):
212 def setType(self, index, type):
213 if len(self.cut_list):
214 self.cut_list[index] = (self.cut_list[index][0], type)
215 self["cutlist"].modifyEntry(index, CutListEntry(*self.cut_list[index]))
218 m = self["cutlist"].getIndex()
220 self.uploadCuesheet()
223 m = self["cutlist"].getIndex()
225 self.uploadCuesheet()
228 m = self["cutlist"].getIndex()
230 self.uploadCuesheet()
233 self.toggleMark(onlyadd=True, tolerance=90000) # do not allow two marks in <1s
235 def __removeMark(self):
236 m = self["cutlist"].getCurrent()
244 def getCutlist(self):
246 for e in self.cut_list:
247 r.append(CutListEntry(*e))
250 def selectionChanged(self):
251 if not self.inhibit_seek:
252 where = self["cutlist"].getCurrent()
257 seek = self.getSeek()
263 def refillList(self):
264 print "cue sheet changed, refilling"
265 self.downloadCuesheet()
267 # get the first changed entry, counted from the end, and select it
268 new_list = self.getCutlist()
269 self["cutlist"].list = new_list
272 l2 = len(self.last_cuts)
273 for i in range(min(l1, l2)):
274 if new_list[l1-i-1] != self.last_cuts[l2-i-1]:
275 self["cutlist"].setIndex(l1-i-1)
277 self.last_cuts = new_list
279 def getStateForPosition(self, pos):
281 for (where, what) in self.cut_list:
294 curpos = self.cueGetCurrentPosition()
298 self.setSeekState(self.SEEK_STATE_PAUSE)
300 self.context_position = curpos
302 self.context_nearest_mark = self.toggleMark(onlyreturn=True)
304 cur_state = self.getStateForPosition(curpos)
306 print "currently in 'IN'"
307 if self.cut_start is None or self.context_position < self.cut_start:
308 state = CutListContextMenu.SHOW_STARTCUT
310 state = CutListContextMenu.SHOW_ENDCUT
312 print "currently in 'OUT'"
313 state = CutListContextMenu.SHOW_DELETECUT
315 if self.context_nearest_mark is None:
320 self.session.openWithCallback(self.menuCallback, CutListContextMenu, state, nearmark)
322 def menuCallback(self, *result):
327 if result == CutListContextMenu.RET_STARTCUT:
328 self.cut_start = self.context_position
329 elif result == CutListContextMenu.RET_ENDCUT:
330 # remove in/out marks between the new cut
331 for (where, what) in self.cut_list[:]:
332 if self.cut_start <= where <= self.context_position and what in (0,1):
333 self.cut_list.remove((where, what))
335 bisect.insort(self.cut_list, (self.cut_start, 1))
336 bisect.insort(self.cut_list, (self.context_position, 0))
337 self.uploadCuesheet()
338 self.cut_start = None
339 elif result == CutListContextMenu.RET_DELETECUT:
343 for (where, what) in self.cut_list:
344 if what == 1 and where <= self.context_position: # out
345 out_before = (where, what)
346 elif what == 0 and where < self.context_position: # in, before out
348 elif what == 0 and where >= self.context_position and in_after is None:
349 in_after = (where, what)
351 if out_before is not None:
352 self.cut_list.remove(out_before)
354 if in_after is not None:
355 self.cut_list.remove(in_after)
356 self.inhibit_seek = True
357 self.uploadCuesheet()
358 self.inhibit_seek = False
359 elif result == CutListContextMenu.RET_MARK:
361 elif result == CutListContextMenu.RET_DELETEMARK:
362 self.cut_list.remove(self.context_nearest_mark)
363 self.inhibit_seek = True
364 self.uploadCuesheet()
365 self.inhibit_seek = False
366 elif result == CutListContextMenu.RET_REMOVEBEFORE:
367 # remove in/out marks before current position
368 for (where, what) in self.cut_list[:]:
369 if where <= self.context_position and what in (0,1):
370 self.cut_list.remove((where, what))
372 bisect.insort(self.cut_list, (self.context_position, 0))
373 self.inhibit_seek = True
374 self.uploadCuesheet()
375 self.inhibit_seek = False
376 elif result == CutListContextMenu.RET_REMOVEAFTER:
377 # remove in/out marks after current position
378 for (where, what) in self.cut_list[:]:
379 if where >= self.context_position and what in (0,1):
380 self.cut_list.remove((where, what))
382 bisect.insort(self.cut_list, (self.context_position, 1))
383 self.inhibit_seek = True
384 self.uploadCuesheet()
385 self.inhibit_seek = False
386 elif result == CutListContextMenu.RET_GRABFRAME:
389 # we modify the "play" behavior a bit:
390 # if we press pause while being in slowmotion, we will pause (and not play)
391 def playpauseService(self):
392 if self.seekstate != self.SEEK_STATE_PLAY and not self.isStateSlowMotion(self.seekstate):
393 self.unPauseService()
398 path = self.session.nav.getCurrentlyPlayingServiceReference().getPath()
399 from Components.Console import Console
400 grabConsole = Console()
401 cmd = 'grab -vblpr%d "%s"' % (180, path.rsplit('.',1)[0] + ".png")
402 grabConsole.ePopen(cmd)
403 self.playpauseService()
405 def main(session, service, **kwargs):
406 session.open(CutListEditor, service)
408 def Plugins(**kwargs):
409 return PluginDescriptor(name="Cutlist Editor", description=_("Cutlist editor..."), where = PluginDescriptor.WHERE_MOVIELIST, needsRestart = False, fnc=main)