deliver service events only to last created class which is based on
[enigma2.git] / lib / python / Plugins / Extensions / CutListEditor / plugin.py
1 from Plugins.Plugin import PluginDescriptor
2
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 Screens.InfoBarGenerics import InfoBarSeek, InfoBarCueSheetSupport
11 from Components.GUIComponent import GUIComponent
12 from enigma import eListboxPythonMultiContent, eListbox, gFont, iPlayableService, RT_HALIGN_RIGHT
13 from Screens.FixedMenu import FixedMenu
14 from Screens.HelpMenu import HelpableScreen
15 import bisect
16
17 def CutListEntry(where, what):
18         res = [ (where, what) ]
19         w = where / 90
20         ms = w % 1000
21         s = (w / 1000) % 60
22         m = (w / 60000) % 60
23         h = w / 3600000
24         if what == 0:
25                 type = "IN"
26         elif what == 1:
27                 type = "OUT"
28         elif what == 2:
29                 type = "MARK"
30         elif what == 3:
31                 type = "LAST"
32         res.append(MultiContentEntryText(size=(400, 20), text = "%dh:%02dm:%02ds:%03d" % (h, m, s, ms)))
33         res.append(MultiContentEntryText(pos=(400,0), size=(130, 20), text = type, flags = RT_HALIGN_RIGHT))
34
35         return res
36
37 class CutListContextMenu(FixedMenu):
38         RET_STARTCUT = 0
39         RET_ENDCUT = 1
40         RET_DELETECUT = 2
41         RET_MARK = 3
42         RET_DELETEMARK = 4
43         RET_REMOVEBEFORE = 5
44         RET_REMOVEAFTER = 6
45
46         SHOW_STARTCUT = 0
47         SHOW_ENDCUT = 1
48         SHOW_DELETECUT = 2
49
50         def __init__(self, session, state, nearmark):
51                 menu = [(_("back"), self.close)] #, (None, )]
52
53                 if state == self.SHOW_STARTCUT:
54                         menu.append((_("start cut here"), self.startCut))
55                 else:
56                         menu.append((_("start cut here"), ))
57
58                 if state == self.SHOW_ENDCUT:
59                         menu.append((_("end cut here"), self.endCut))
60                 else:
61                         menu.append((_("end cut here"), ))
62
63                 if state == self.SHOW_DELETECUT:
64                         menu.append((_("delete cut"), self.deleteCut))
65                 else:
66                         menu.append((_("delete cut"), ))
67
68                 menu.append((_("remove before this position"), self.removeBefore))
69                 menu.append((_("remove after this position"), self.removeAfter))
70
71 #               menu.append((None, ))
72
73                 if not nearmark:
74                         menu.append((_("insert mark here"), self.insertMark))
75                 else:
76                         menu.append((_("remove this mark"), self.removeMark))
77
78                 FixedMenu.__init__(self, session, _("Cut"), menu)
79                 self.skinName = "Menu"
80
81         def startCut(self):
82                 self.close(self.RET_STARTCUT)
83
84         def endCut(self):
85                 self.close(self.RET_ENDCUT)
86
87         def deleteCut(self):
88                 self.close(self.RET_DELETECUT)
89
90         def insertMark(self):
91                 self.close(self.RET_MARK)
92
93         def removeMark(self):
94                 self.close(self.RET_DELETEMARK)
95
96         def removeBefore(self):
97                 self.close(self.RET_REMOVEBEFORE)
98
99         def removeAfter(self):
100                 self.close(self.RET_REMOVEAFTER)
101
102
103 class CutList(GUIComponent):
104         def __init__(self, list):
105                 GUIComponent.__init__(self)
106                 self.l = eListboxPythonMultiContent()
107                 self.setList(list)
108                 self.l.setFont(0, gFont("Regular", 20))
109                 self.onSelectionChanged = [ ]
110
111         def getCurrent(self):
112                 return self.l.getCurrentSelection()
113
114         def getCurrentIndex(self):
115                 return self.l.getCurrentSelectionIndex()
116
117         GUI_WIDGET = eListbox
118
119         def postWidgetCreate(self, instance):
120                 instance.setContent(self.l)
121                 instance.setItemHeight(30)
122                 instance.selectionChanged.get().append(self.selectionChanged)
123
124         def preWidgetRemove(self, instance):
125                 instance.setContent(None)
126                 instance.selectionChanged.get().remove(self.selectionChanged)
127
128         def selectionChanged(self):
129                 for x in self.onSelectionChanged:
130                         x()
131
132         def invalidateEntry(self, index):
133                 self.l.invalidateEntry(index)
134
135         def setIndex(self, index, data):
136                 self.list[index] = data
137                 self.invalidateEntry(index)
138
139         def setList(self, list):
140                 self.list = list
141                 self.l.setList(self.list)
142
143         def setSelection(self, index):
144                 if self.instance is not None:
145                         self.instance.moveSelectionTo(index)
146
147 class CutListEditor(Screen, InfoBarBase, InfoBarSeek, InfoBarCueSheetSupport, HelpableScreen):
148         skin = """
149         <screen position="0,0" size="720,576" title="Cutlist editor" flags="wfNoBorder">
150                 <eLabel text="Cutlist editor" position="65,60" size="300,25" font="Regular;20" />
151                 <widget source="global.CurrentTime" render="Label" position="268,60" size="394,20" font="Regular;20" halign="right">
152                         <convert type="ClockToText">Format:%A %B %d, %H:%M</convert>
153                 </widget>
154                 <eLabel position="268,98" size="394,304" backgroundColor="#505555" />
155                 <widget name="Video" position="270,100" zPosition="1" size="390,300" backgroundColor="transparent" />
156                 <widget source="session.CurrentService" render="Label" position="135,405" size="450,50" font="Regular;22" halign="center" valign="center">
157                         <convert type="ServiceName">Name</convert>
158                 </widget>
159                 <widget source="session.CurrentService" render="Label" position="50,450" zPosition="1" size="620,25" font="Regular;20" halign="center" valign="center">
160                         <convert type="ServicePosition">Position,Detailed</convert>
161                 </widget>
162                 <eLabel position="62,98" size="179,274" backgroundColor="#505555" />
163                 <eLabel position="64,100" size="175,270" backgroundColor="#000000" />
164                 <widget name="Cutlist" position="64,100" zPosition="1" size="175,270" scrollbarMode="showOnDemand" transparent="1" />
165                 <widget name="Timeline" position="50,485" size="615,20" backgroundColor="#505555" pointer="skin_default/position_arrow.png:3,5" foregroundColor="black" />
166                 <ePixmap pixmap="skin_default/icons/mp_buttons.png" position="305,515" size="109,13" alphatest="on" />
167         </screen>"""
168
169         def __init__(self, session, service):
170                 self.skin = CutListEditor.skin
171                 Screen.__init__(self, session)
172                 InfoBarSeek.__init__(self, actionmap = "CutlistSeekActions")
173                 InfoBarCueSheetSupport.__init__(self)
174                 InfoBarBase.__init__(self)
175                 HelpableScreen.__init__(self)
176                 self.old_service = session.nav.getCurrentlyPlayingServiceReference()
177                 session.nav.playService(service)
178
179                 service = session.nav.getCurrentService()
180                 cue = service and service.cueSheet()
181                 if cue is not None:
182                         # disable cutlists. we want to freely browse around in the movie
183                         print "cut lists disabled!"
184                         cue.setCutListEnable(0)
185
186                 self.downloadCuesheet()
187
188                 self["Timeline"] = ServicePositionGauge(self.session.nav)
189                 self["Cutlist"] = CutList(self.getCutlist())
190                 self["Cutlist"].onSelectionChanged.append(self.selectionChanged)
191
192                 self["Video"] = VideoWindow(decoder = 0)
193
194                 self["actions"] = HelpableActionMap(self, "CutListEditorActions",
195                         {
196                                 "setIn": (self.setIn, _("Make this mark an 'in' point")),
197                                 "setOut": (self.setOut, _("Make this mark an 'out' point")),
198                                 "setMark": (self.setMark, _("Make this mark just a mark")),
199                                 "addMark": (self.__addMark, _("Add a mark")),
200                                 "removeMark": (self.__removeMark, _("Remove a mark")),
201                                 "leave": (self.exit, _("Exit editor")),
202                                 "showMenu": (self.showMenu, _("menu")),
203                         }, prio=-4)
204
205                 self.tutorial_seen = False
206
207                 self.onExecBegin.append(self.showTutorial)
208                 self.__event_tracker = ServiceEventTracker(screen=self, eventmap=
209                         {
210                                 iPlayableService.evCuesheetChanged: self.refillList
211                         })
212
213                 # to track new entries we save the last version of the cutlist
214                 self.last_cuts = [ ]
215                 self.cut_start = None
216
217         def showTutorial(self):
218                 if not self.tutorial_seen:
219                         self.tutorial_seen = True
220                         self.session.open(MessageBox, 
221                                 """Welcome to the Cutlist editor. 
222
223 Seek to the start of the stuff you want to cut away. Press OK, select 'start cut'.
224
225 Then seek to the end, press OK, select 'end cut'. That's it.
226                                 """, MessageBox.TYPE_INFO)
227
228         def checkSkipShowHideLock(self):
229                 pass
230
231         def setType(self, index, type):
232                 if len(self.cut_list):
233                         self.cut_list[index] = (self.cut_list[index][0], type)
234                         self["Cutlist"].setIndex(index, CutListEntry(*self.cut_list[index]))
235
236         def setIn(self):
237                 m = self["Cutlist"].getCurrentIndex()
238                 self.setType(m, 0)
239                 self.uploadCuesheet()
240
241         def setOut(self):
242                 m = self["Cutlist"].getCurrentIndex()
243                 self.setType(m, 1)
244                 self.uploadCuesheet()
245
246         def setMark(self):
247                 m = self["Cutlist"].getCurrentIndex()
248                 self.setType(m, 2)
249                 self.uploadCuesheet()
250
251         def __addMark(self):
252                 self.toggleMark(onlyadd=True, tolerance=90000) # do not allow two marks in <1s
253
254         def __removeMark(self):
255                 m = self["Cutlist"].getCurrent()
256                 m = m and m[0]
257                 if m is not None:
258                         self.removeMark(m)
259
260         def exit(self):
261                 self.session.nav.playService(self.old_service)
262                 self.close()
263
264         def getCutlist(self):
265                 r = [ ]
266                 for e in self.cut_list:
267                         r.append(CutListEntry(*e))
268                 return r
269
270         def selectionChanged(self):
271                 where = self["Cutlist"].getCurrent()
272                 if where is None:
273                         print "no selection"
274                         return
275                 pts = where[0][0]
276                 seek = self.getSeek()
277                 if seek is None:
278                         print "no seek"
279                         return
280                 seek.seekTo(pts)
281
282         def refillList(self):
283                 print "cue sheet changed, refilling"
284                 self.downloadCuesheet()
285
286                 # get the first changed entry, and select it
287                 new_list = self.getCutlist()
288                 self["Cutlist"].setList(new_list)
289
290                 for i in range(min(len(new_list), len(self.last_cuts))):
291                         if new_list[i] != self.last_cuts[i]:
292                                 self["Cutlist"].setSelection(i)
293                                 break
294                 self.last_cuts = new_list
295
296         def getStateForPosition(self, pos):
297                 state = 0 # in
298
299                 # when first point is "in", the beginning is "out"
300                 if len(self.cut_list) and self.cut_list[0][1] == 0:
301                         state = 1
302
303                 for (where, what) in self.cut_list:
304                         if where < pos:
305                                 if what == 0: # in
306                                         state = 0
307                                 elif what == 1: # out
308                                         state = 1
309                 return state
310
311         def showMenu(self):
312                 curpos = self.cueGetCurrentPosition()
313                 if curpos is None:
314                         return
315
316                 self.setSeekState(self.SEEK_STATE_PAUSE)
317
318                 self.context_position = curpos
319
320                 self.context_nearest_mark = self.toggleMark(onlyreturn=True)
321
322                 cur_state = self.getStateForPosition(curpos)
323                 if cur_state == 0:
324                         print "currently in 'IN'"
325                         if self.cut_start is None or self.context_position < self.cut_start:
326                                 state = CutListContextMenu.SHOW_STARTCUT
327                         else:
328                                 state = CutListContextMenu.SHOW_ENDCUT
329                 else:
330                         print "currently in 'OUT'"
331                         state = CutListContextMenu.SHOW_DELETECUT
332
333                 if self.context_nearest_mark is None:
334                         nearmark = False
335                 else:
336                         nearmark = True
337
338                 self.session.openWithCallback(self.menuCallback, CutListContextMenu, state, nearmark)
339
340         def menuCallback(self, *result):
341                 if not len(result):
342                         return
343                 result = result[0]
344
345                 if result == CutListContextMenu.RET_STARTCUT:
346                         self.cut_start = self.context_position
347                 elif result == CutListContextMenu.RET_ENDCUT:
348                         # remove in/out marks between the new cut
349                         for (where, what) in self.cut_list[:]:
350                                 if self.cut_start <= where <= self.context_position and what in [0,1]:
351                                         self.cut_list.remove((where, what))
352
353                         bisect.insort(self.cut_list, (self.cut_start, 1))
354                         bisect.insort(self.cut_list, (self.context_position, 0))
355                         self.uploadCuesheet()
356                         self.cut_start = None
357                 elif result == CutListContextMenu.RET_DELETECUT:
358                         out_before = None
359                         in_after = None
360
361                         for (where, what) in self.cut_list:
362                                 if what == 1 and where < self.context_position: # out
363                                         out_before = (where, what)
364                                 elif what == 0 and where < self.context_position: # in, before out
365                                         out_before = None
366                                 elif what == 0 and where > self.context_position and in_after is None:
367                                         in_after = (where, what)
368
369                         if out_before is not None:
370                                 self.cut_list.remove(out_before)
371
372                         if in_after is not None:
373                                 self.cut_list.remove(in_after)
374                         self.uploadCuesheet()
375                 elif result == CutListContextMenu.RET_MARK:
376                         self.__addMark()
377                 elif result == CutListContextMenu.RET_DELETEMARK:
378                         self.cut_list.remove(self.context_nearest_mark)
379                         self.uploadCuesheet()
380                 elif result == CutListContextMenu.RET_REMOVEBEFORE:
381                         # remove in/out marks before current position
382                         for (where, what) in self.cut_list[:]:
383                                 if where <= self.context_position and what in [0,1]:
384                                         self.cut_list.remove((where, what))
385                         # add 'in' point
386                         bisect.insort(self.cut_list, (self.context_position, 0))
387                         self.uploadCuesheet()
388                 elif result == CutListContextMenu.RET_REMOVEAFTER:
389                         # remove in/out marks after current position
390                         for (where, what) in self.cut_list[:]:
391                                 if where >= self.context_position and what in [0,1]:
392                                         self.cut_list.remove((where, what))
393                         # add 'out' point
394                         bisect.insort(self.cut_list, (self.context_position, 1))
395                         self.uploadCuesheet()
396
397         # we modify the "play" behavior a bit:
398         # if we press pause while being in slowmotion, we will pause (and not play)
399         def playpauseService(self):
400                 if self.seekstate != self.SEEK_STATE_PLAY and not self.isStateSlowMotion(self.seekstate):
401                         self.unPauseService()
402                 else:
403                         self.pauseService()
404
405 def main(session, service, **kwargs):
406         session.open(CutListEditor, service)
407
408 def Plugins(**kwargs):
409         return PluginDescriptor(name="Cutlist Editor", description=_("Cutlist editor..."), where = PluginDescriptor.WHERE_MOVIELIST, fnc=main)