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