a5adc51affbed4666f1823a365dc724c02d19907
[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         def GUIcreate(self, parent):
115                 self.instance = eListbox(parent)
116                 self.instance.setContent(self.l)
117                 self.instance.setItemHeight(30)
118                 self.instance.selectionChanged.get().append(self.selectionChanged)
119
120         def selectionChanged(self):
121                 for x in self.onSelectionChanged:
122                         x()
123         
124         def GUIdelete(self):
125                 self.instance.selectionChanged.get().remove(self.selectionChanged)
126                 self.instance.setContent(None)
127                 self.instance = None
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):
145         skin = """
146                 <screen position="100,100" size="550,400" title="Test" >
147                         <widget name="Timeline" position="10,0" size="530,40" 
148                                 pointer="/usr/share/enigma2/position_pointer.png:3,5" />
149                         <widget name="Cutlist" position="10,50" size="530,300" scrollbarMode="showOnDemand" />
150                 </screen>"""
151         def __init__(self, session, service):
152                 self.skin = CutListEditor.skin
153                 Screen.__init__(self, session)
154                 InfoBarSeek.__init__(self)
155                 InfoBarCueSheetSupport.__init__(self)
156                 self.old_service = session.nav.getCurrentlyPlayingServiceReference()
157                 session.nav.playService(service)
158                 
159                 service = session.nav.getCurrentService()
160                 cue = service and service.cueSheet()
161                 if cue is not None:
162                         # disable cutlists. we want to freely browse around in the movie
163                         print "cut lists disabled!"
164                         cue.setCutListEnable(0)
165                 
166                 self.downloadCuesheet()
167         
168                 self["Timeline"] = ServicePositionGauge(self.session.nav)
169                 self["Cutlist"] = CutList(self.getCutlist())
170                 self["Cutlist"].onSelectionChanged.append(self.selectionChanged)
171                 
172                 self["actions"] = HelpableActionMap(self, "CutListEditorActions",
173                         {
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,
181                         })
182                 
183                 self.tutorial_seen = False
184                 
185                 self.onExecBegin.append(self.showTutorial)
186                 self.__event_tracker = ServiceEventTracker(screen=self, eventmap=
187                         {
188                                 iPlayableService.evCuesheetChanged: self.refillList
189                         })
190
191                 # to track new entries we save the last version of the cutlist
192                 self.last_cuts = [ ]
193                 self.cut_start = None
194                 
195         def showTutorial(self):
196                 if not self.tutorial_seen:
197                         self.tutorial_seen = True
198                         self.session.open(MessageBox, 
199                                 """Welcome to the Cutlist editor. It's still a bit strange to use, but anyway:
200
201 Seek to the start of the stuff you want to cut away. Press OK, select 'start cut'.
202
203 Then seek to the end, press OK, select 'end cut'. That's it.
204                                 """, MessageBox.TYPE_INFO)
205         
206         def checkSkipShowHideLock(self):
207                 pass
208         
209         def setType(self, index, type):
210                 if len(self.cut_list):
211                         self.cut_list[index] = (self.cut_list[index][0], type)
212                         self["Cutlist"].setIndex(index, CutListEntry(*self.cut_list[index]))
213         
214         def setIn(self):
215                 m = self["Cutlist"].getCurrentIndex()
216                 self.setType(m, 0)
217                 self.uploadCuesheet()
218         
219         def setOut(self):
220                 m = self["Cutlist"].getCurrentIndex()
221                 self.setType(m, 1)
222                 self.uploadCuesheet()
223
224         def setMark(self):
225                 m = self["Cutlist"].getCurrentIndex()
226                 self.setType(m, 2)
227                 self.uploadCuesheet()
228         
229         def __addMark(self):
230                 self.toggleMark(onlyadd=True, tolerance=90000) # do not allow two marks in <1s
231         
232         def __removeMark(self):
233                 m = self["Cutlist"].getCurrent()
234                 m = m and m[0]
235                 if m is not None:
236                         self.removeMark(m)
237         
238         def exit(self):
239                 self.session.nav.playService(self.old_service)
240                 self.close()
241
242         def getCutlist(self):
243                 r = [ ]
244                 for e in self.cut_list:
245                         r.append(CutListEntry(*e))
246                 return r
247
248         def selectionChanged(self):
249                 where = self["Cutlist"].getCurrent()
250                 if where is None:
251                         print "no selection"
252                         return
253                 pts = where[0][0]
254                 seek = self.getSeek()
255                 if seek is None:
256                         print "no seek"
257                         return
258                 seek.seekTo(pts)
259
260         def refillList(self):
261                 print "cue sheet changed, refilling"
262                 self.downloadCuesheet()
263                 
264                 # get the first changed entry, and select it
265                 new_list = self.getCutlist()
266                 self["Cutlist"].setList(new_list)
267                 
268                 for i in range(min(len(new_list), len(self.last_cuts))):
269                         if new_list[i] != self.last_cuts[i]:
270                                 self["Cutlist"].setSelection(i)
271                                 break
272                 self.last_cuts = new_list
273
274         def getStateForPosition(self, pos):
275                 state = 0 # in
276                 
277                 # when first point is "in", the beginning is "out"
278                 if len(self.cut_list) and self.cut_list[0][1] == 0:
279                         state = 1
280
281                 for (where, what) in self.cut_list:
282                         if where < pos:
283                                 if what == 0: # in
284                                         state = 0
285                                 elif what == 1: # out
286                                         state = 1
287                 return state
288
289         def showMenu(self):
290                 curpos = self.cueGetCurrentPosition()
291                 if curpos is None:
292                         return
293                 
294                 self.setSeekState(self.SEEK_STATE_PAUSE)
295                 
296                 self.context_position = curpos
297                 
298                 self.context_nearest_mark = self.toggleMark(onlyreturn=True)
299
300                 cur_state = self.getStateForPosition(curpos)
301                 if cur_state == 0:
302                         print "currently in 'IN'"
303                         if self.cut_start is None or self.context_position < self.cut_start:
304                                 state = CutListContextMenu.SHOW_STARTCUT
305                         else:
306                                 state = CutListContextMenu.SHOW_ENDCUT
307                 else:
308                         print "currently in 'OUT'"
309                         state = CutListContextMenu.SHOW_DELETECUT
310                 
311                 if self.context_nearest_mark is None:
312                         nearmark = False
313                 else:
314                         nearmark = True
315                 
316                 self.session.openWithCallback(self.menuCallback, CutListContextMenu, state, nearmark)
317         
318         def menuCallback(self, *result):
319                 self.setSeekState(self.SEEK_STATE_PLAY)
320                 if not len(result):
321                         return
322                 result = result[0]
323                 
324                 if result == CutListContextMenu.RET_STARTCUT:
325                         self.cut_start = self.context_position
326                 elif result == CutListContextMenu.RET_ENDCUT:
327                         # remove in/out marks between the new cut
328                         for (where, what) in self.cut_list[:]:
329                                 if self.cut_start <= where <= self.context_position and what in [0,1]:
330                                         self.cut_list.remove((where, what))
331                         
332                         bisect.insort(self.cut_list, (self.cut_start, 1))
333                         bisect.insort(self.cut_list, (self.context_position, 0))
334                         self.uploadCuesheet()
335                         self.cut_start = None
336                 elif result == CutListContextMenu.RET_DELETECUT:
337                         out_before = None
338                         in_after = None
339                         
340                         for (where, what) in self.cut_list:
341                                 if what == 1 and where < self.context_position: # out
342                                         out_before = (where, what)
343                                 elif what == 0 and where < self.context_position: # in, before out
344                                         out_before = None
345                                 elif what == 0 and where > self.context_position and in_after is None:
346                                         in_after = (where, what)
347                         
348                         if out_before is not None:
349                                 self.cut_list.remove(out_before)
350                         
351                         if in_after is not None:
352                                 self.cut_list.remove(in_after)
353                         self.uploadCuesheet()
354                 elif result == CutListContextMenu.RET_MARK:
355                         self.__addMark()
356                 elif result == CutListContextMenu.RET_DELETEMARK:
357                         self.cut_list.remove(self.context_nearest_mark)
358                         self.uploadCuesheet()
359                 elif result == CutListContextMenu.RET_REMOVEBEFORE:
360                         # remove in/out marks before current position
361                         for (where, what) in self.cut_list[:]:
362                                 if where <= self.context_position and what in [0,1]:
363                                         self.cut_list.remove((where, what))
364                         # add 'in' point
365                         bisect.insort(self.cut_list, (self.context_position, 0))
366                         self.uploadCuesheet()
367                 elif result == CutListContextMenu.RET_REMOVEAFTER:
368                         # remove in/out marks after current position
369                         for (where, what) in self.cut_list[:]:
370                                 if where >= self.context_position and what in [0,1]:
371                                         self.cut_list.remove((where, what))
372                         # add 'out' point
373                         bisect.insort(self.cut_list, (self.context_position, 1))
374                         self.uploadCuesheet()
375
376 def main(session, service, **kwargs):
377         session.open(CutListEditor, service)
378
379 def Plugins(**kwargs):
380         return PluginDescriptor(name="Cutlist Editor", description=_("Cutlist editor..."), where = PluginDescriptor.WHERE_MOVIELIST, fnc=main)