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