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