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