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, 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 self["Video"] = VideoWindow(decoder = 0)
172 self["actions"] = HelpableActionMap(self, "CutListEditorActions",
174 "setIn": (self.setIn, _("Make this mark an 'in' point")),
175 "setOut": (self.setOut, _("Make this mark an 'out' point")),
176 "setMark": (self.setMark, _("Make this mark just a mark")),
177 "addMark": (self.__addMark, _("Add a mark")),
178 "removeMark": (self.__removeMark, _("Remove a mark")),
179 "leave": (self.exit, _("Exit editor")),
180 "showMenu": (self.showMenu, _("menu")),
183 self.tutorial_seen = False
185 self.onExecBegin.append(self.showTutorial)
186 self.__event_tracker = ServiceEventTracker(screen=self, eventmap=
188 iPlayableService.evCuesheetChanged: self.refillList
191 # to track new entries we save the last version of the cutlist
192 self.last_cuts = self.getCutlist()
193 self.cut_start = None
194 self.inhibit_seek = False
195 self.onClose.append(self.__onClose)
198 self.session.nav.playService(self.old_service)
200 def updateStateLabel(self, state):
201 self["SeekState"].setText(state[3].strip())
203 def showTutorial(self):
204 if not self.tutorial_seen:
205 self.tutorial_seen = True
206 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)
208 def checkSkipShowHideLock(self):
211 def setType(self, index, type):
212 if len(self.cut_list):
213 self.cut_list[index] = (self.cut_list[index][0], type)
214 self["cutlist"].modifyEntry(index, CutListEntry(*self.cut_list[index]))
217 m = self["cutlist"].getIndex()
219 self.uploadCuesheet()
222 m = self["cutlist"].getIndex()
224 self.uploadCuesheet()
227 m = self["cutlist"].getIndex()
229 self.uploadCuesheet()
232 self.toggleMark(onlyadd=True, tolerance=90000) # do not allow two marks in <1s
234 def __removeMark(self):
235 m = self["cutlist"].getCurrent()
243 def getCutlist(self):
245 for e in self.cut_list:
246 r.append(CutListEntry(*e))
249 def selectionChanged(self):
250 if not self.inhibit_seek:
251 where = self["cutlist"].getCurrent()
256 seek = self.getSeek()
262 def refillList(self):
263 print "cue sheet changed, refilling"
264 self.downloadCuesheet()
266 # get the first changed entry, counted from the end, and select it
267 new_list = self.getCutlist()
268 self["cutlist"].list = new_list
271 l2 = len(self.last_cuts)
272 for i in range(min(l1, l2)):
273 if new_list[l1-i-1] != self.last_cuts[l2-i-1]:
274 self["cutlist"].setIndex(l1-i-1)
276 self.last_cuts = new_list
278 def getStateForPosition(self, pos):
280 for (where, what) in self.cut_list:
293 curpos = self.cueGetCurrentPosition()
297 self.setSeekState(self.SEEK_STATE_PAUSE)
299 self.context_position = curpos
301 self.context_nearest_mark = self.toggleMark(onlyreturn=True)
303 cur_state = self.getStateForPosition(curpos)
305 print "currently in 'IN'"
306 if self.cut_start is None or self.context_position < self.cut_start:
307 state = CutListContextMenu.SHOW_STARTCUT
309 state = CutListContextMenu.SHOW_ENDCUT
311 print "currently in 'OUT'"
312 state = CutListContextMenu.SHOW_DELETECUT
314 if self.context_nearest_mark is None:
319 self.session.openWithCallback(self.menuCallback, CutListContextMenu, state, nearmark)
321 def menuCallback(self, *result):
326 if result == CutListContextMenu.RET_STARTCUT:
327 self.cut_start = self.context_position
328 elif result == CutListContextMenu.RET_ENDCUT:
329 # remove in/out marks between the new cut
330 for (where, what) in self.cut_list[:]:
331 if self.cut_start <= where <= self.context_position and what in (0,1):
332 self.cut_list.remove((where, what))
334 bisect.insort(self.cut_list, (self.cut_start, 1))
335 bisect.insort(self.cut_list, (self.context_position, 0))
336 self.uploadCuesheet()
337 self.cut_start = None
338 elif result == CutListContextMenu.RET_DELETECUT:
342 for (where, what) in self.cut_list:
343 if what == 1 and where <= self.context_position: # out
344 out_before = (where, what)
345 elif what == 0 and where < self.context_position: # in, before out
347 elif what == 0 and where >= self.context_position and in_after is None:
348 in_after = (where, what)
350 if out_before is not None:
351 self.cut_list.remove(out_before)
353 if in_after is not None:
354 self.cut_list.remove(in_after)
355 self.inhibit_seek = True
356 self.uploadCuesheet()
357 self.inhibit_seek = False
358 elif result == CutListContextMenu.RET_MARK:
360 elif result == CutListContextMenu.RET_DELETEMARK:
361 self.cut_list.remove(self.context_nearest_mark)
362 self.inhibit_seek = True
363 self.uploadCuesheet()
364 self.inhibit_seek = False
365 elif result == CutListContextMenu.RET_REMOVEBEFORE:
366 # remove in/out marks before current position
367 for (where, what) in self.cut_list[:]:
368 if where <= self.context_position and what in (0,1):
369 self.cut_list.remove((where, what))
371 bisect.insort(self.cut_list, (self.context_position, 0))
372 self.inhibit_seek = True
373 self.uploadCuesheet()
374 self.inhibit_seek = False
375 elif result == CutListContextMenu.RET_REMOVEAFTER:
376 # remove in/out marks after current position
377 for (where, what) in self.cut_list[:]:
378 if where >= self.context_position and what in (0,1):
379 self.cut_list.remove((where, what))
381 bisect.insort(self.cut_list, (self.context_position, 1))
382 self.inhibit_seek = True
383 self.uploadCuesheet()
384 self.inhibit_seek = False
385 elif result == CutListContextMenu.RET_GRABFRAME:
388 # we modify the "play" behavior a bit:
389 # if we press pause while being in slowmotion, we will pause (and not play)
390 def playpauseService(self):
391 if self.seekstate != self.SEEK_STATE_PLAY and not self.isStateSlowMotion(self.seekstate):
392 self.unPauseService()
397 path = self.session.nav.getCurrentlyPlayingServiceReference().getPath()
398 from Components.Console import Console
399 grabConsole = Console()
400 cmd = 'grab -vblpr%d "%s"' % (180, path.rsplit('.',1)[0] + ".png")
401 grabConsole.ePopen(cmd)
402 self.playpauseService()
404 def main(session, service, **kwargs):
405 session.open(CutListEditor, service)
407 def Plugins(**kwargs):
408 return PluginDescriptor(name="Cutlist Editor", description=_("Cutlist editor..."), where = PluginDescriptor.WHERE_MOVIELIST, fnc=main)