0d2454e49c34e8bd854848a813789c219ec5dac3
[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, steal_current_service = True)
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                 self.onClose.append(self.__onClose)
217
218         def __onClose(self):
219                 self.session.nav.playService(self.old_service)
220
221         def showTutorial(self):
222                 if not self.tutorial_seen:
223                         self.tutorial_seen = True
224                         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)
225
226         def checkSkipShowHideLock(self):
227                 pass
228
229         def setType(self, index, type):
230                 if len(self.cut_list):
231                         self.cut_list[index] = (self.cut_list[index][0], type)
232                         self["Cutlist"].setIndex(index, CutListEntry(*self.cut_list[index]))
233
234         def setIn(self):
235                 m = self["Cutlist"].getCurrentIndex()
236                 self.setType(m, 0)
237                 self.uploadCuesheet()
238
239         def setOut(self):
240                 m = self["Cutlist"].getCurrentIndex()
241                 self.setType(m, 1)
242                 self.uploadCuesheet()
243
244         def setMark(self):
245                 m = self["Cutlist"].getCurrentIndex()
246                 self.setType(m, 2)
247                 self.uploadCuesheet()
248
249         def __addMark(self):
250                 self.toggleMark(onlyadd=True, tolerance=90000) # do not allow two marks in <1s
251
252         def __removeMark(self):
253                 m = self["Cutlist"].getCurrent()
254                 m = m and m[0]
255                 if m is not None:
256                         self.removeMark(m)
257
258         def exit(self):
259                 self.close()
260
261         def getCutlist(self):
262                 r = [ ]
263                 for e in self.cut_list:
264                         r.append(CutListEntry(*e))
265                 return r
266
267         def selectionChanged(self):
268                 where = self["Cutlist"].getCurrent()
269                 if where is None:
270                         print "no selection"
271                         return
272                 pts = where[0][0]
273                 seek = self.getSeek()
274                 if seek is None:
275                         print "no seek"
276                         return
277                 seek.seekTo(pts)
278
279         def refillList(self):
280                 print "cue sheet changed, refilling"
281                 self.downloadCuesheet()
282
283                 # get the first changed entry, and select it
284                 new_list = self.getCutlist()
285                 self["Cutlist"].setList(new_list)
286
287                 for i in range(min(len(new_list), len(self.last_cuts))):
288                         if new_list[i] != self.last_cuts[i]:
289                                 self["Cutlist"].setSelection(i)
290                                 break
291                 self.last_cuts = new_list
292
293         def getStateForPosition(self, pos):
294                 state = 0 # in
295
296                 # when first point is "in", the beginning is "out"
297                 if len(self.cut_list) and self.cut_list[0][1] == 0:
298                         state = 1
299
300                 for (where, what) in self.cut_list:
301                         if where < pos:
302                                 if what == 0: # in
303                                         state = 0
304                                 elif what == 1: # out
305                                         state = 1
306                 return state
307
308         def showMenu(self):
309                 curpos = self.cueGetCurrentPosition()
310                 if curpos is None:
311                         return
312
313                 self.setSeekState(self.SEEK_STATE_PAUSE)
314
315                 self.context_position = curpos
316
317                 self.context_nearest_mark = self.toggleMark(onlyreturn=True)
318
319                 cur_state = self.getStateForPosition(curpos)
320                 if cur_state == 0:
321                         print "currently in 'IN'"
322                         if self.cut_start is None or self.context_position < self.cut_start:
323                                 state = CutListContextMenu.SHOW_STARTCUT
324                         else:
325                                 state = CutListContextMenu.SHOW_ENDCUT
326                 else:
327                         print "currently in 'OUT'"
328                         state = CutListContextMenu.SHOW_DELETECUT
329
330                 if self.context_nearest_mark is None:
331                         nearmark = False
332                 else:
333                         nearmark = True
334
335                 self.session.openWithCallback(self.menuCallback, CutListContextMenu, state, nearmark)
336
337         def menuCallback(self, *result):
338                 if not len(result):
339                         return
340                 result = result[0]
341
342                 if result == CutListContextMenu.RET_STARTCUT:
343                         self.cut_start = self.context_position
344                 elif result == CutListContextMenu.RET_ENDCUT:
345                         # remove in/out marks between the new cut
346                         for (where, what) in self.cut_list[:]:
347                                 if self.cut_start <= where <= self.context_position and what in [0,1]:
348                                         self.cut_list.remove((where, what))
349
350                         bisect.insort(self.cut_list, (self.cut_start, 1))
351                         bisect.insort(self.cut_list, (self.context_position, 0))
352                         self.uploadCuesheet()
353                         self.cut_start = None
354                 elif result == CutListContextMenu.RET_DELETECUT:
355                         out_before = None
356                         in_after = None
357
358                         for (where, what) in self.cut_list:
359                                 if what == 1 and where < self.context_position: # out
360                                         out_before = (where, what)
361                                 elif what == 0 and where < self.context_position: # in, before out
362                                         out_before = None
363                                 elif what == 0 and where > self.context_position and in_after is None:
364                                         in_after = (where, what)
365
366                         if out_before is not None:
367                                 self.cut_list.remove(out_before)
368
369                         if in_after is not None:
370                                 self.cut_list.remove(in_after)
371                         self.uploadCuesheet()
372                 elif result == CutListContextMenu.RET_MARK:
373                         self.__addMark()
374                 elif result == CutListContextMenu.RET_DELETEMARK:
375                         self.cut_list.remove(self.context_nearest_mark)
376                         self.uploadCuesheet()
377                 elif result == CutListContextMenu.RET_REMOVEBEFORE:
378                         # remove in/out marks before current position
379                         for (where, what) in self.cut_list[:]:
380                                 if where <= self.context_position and what in [0,1]:
381                                         self.cut_list.remove((where, what))
382                         # add 'in' point
383                         bisect.insort(self.cut_list, (self.context_position, 0))
384                         self.uploadCuesheet()
385                 elif result == CutListContextMenu.RET_REMOVEAFTER:
386                         # remove in/out marks after current position
387                         for (where, what) in self.cut_list[:]:
388                                 if where >= self.context_position and what in [0,1]:
389                                         self.cut_list.remove((where, what))
390                         # add 'out' point
391                         bisect.insort(self.cut_list, (self.context_position, 1))
392                         self.uploadCuesheet()
393
394         # we modify the "play" behavior a bit:
395         # if we press pause while being in slowmotion, we will pause (and not play)
396         def playpauseService(self):
397                 if self.seekstate != self.SEEK_STATE_PLAY and not self.isStateSlowMotion(self.seekstate):
398                         self.unPauseService()
399                 else:
400                         self.pauseService()
401
402 def main(session, service, **kwargs):
403         session.open(CutListEditor, service)
404
405 def Plugins(**kwargs):
406         return PluginDescriptor(name="Cutlist Editor", description=_("Cutlist editor..."), where = PluginDescriptor.WHERE_MOVIELIST, fnc=main)