b636632cc58a8e17dca5c8fd86656cbe896c9333
[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
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, InfoBarSeek, InfoBarCueSheetSupport, HelpableScreen):
148         skin = """
149                 <screen position="0,0" size="720,576" flags="wfNoBorder" backgroundColor="#444444">
150                         <eLabel position="360,0" size="360,313" backgroundColor="#ffffff" />
151                         <widget name="Video" position="370,10" size="340,268" backgroundColor="transparent" zPosition="1" />
152
153                         <eLabel position="50,80" size="300,24" text="Name:" font="Regular;20" foregroundColor="#cccccc" transparent="1" />
154
155                         <widget source="CurrentService" render="Label" position="50,110" size="300,60" font="Regular;22" >
156                                 <convert type="ServiceName">Name</convert>
157                         </widget>
158
159                         <widget source="CurrentService" render="Label" position="370,278" size="340,25" 
160                                 backgroundColor="#000000" foregroundColor="#ffffff" font="Regular;19" zPosition="1" >
161                                 <convert type="ServicePosition">Position,Detailed</convert>
162                         </widget>
163
164                         <widget name="Timeline" position="50,500" size="620,40" backgroundColor="#000000"
165                                 pointer="/usr/share/enigma2/skin_default/position_arrow.png:3,5" foregroundColor="#ffffff" />
166                         <widget name="Cutlist" position="50,325" size="620,175" scrollbarMode="showOnDemand" transparent="1" />
167                 </screen>"""
168         def __init__(self, session, service):
169                 self.skin = CutListEditor.skin
170                 Screen.__init__(self, session)
171                 InfoBarSeek.__init__(self, actionmap = "CutlistSeekActions")
172                 InfoBarCueSheetSupport.__init__(self)
173                 HelpableScreen.__init__(self)
174                 self.old_service = session.nav.getCurrentlyPlayingServiceReference()
175                 session.nav.playService(service)
176
177                 service = session.nav.getCurrentService()
178                 cue = service and service.cueSheet()
179                 if cue is not None:
180                         # disable cutlists. we want to freely browse around in the movie
181                         print "cut lists disabled!"
182                         cue.setCutListEnable(0)
183
184                 self.downloadCuesheet()
185
186                 self["Timeline"] = ServicePositionGauge(self.session.nav)
187                 self["Cutlist"] = CutList(self.getCutlist())
188                 self["Cutlist"].onSelectionChanged.append(self.selectionChanged)
189
190                 self["Video"] = VideoWindow(decoder = 0)
191
192                 self["actions"] = HelpableActionMap(self, "CutListEditorActions",
193                         {
194                                 "setIn": (self.setIn, _("Make this mark an 'in' point")),
195                                 "setOut": (self.setOut, _("Make this mark an 'out' point")),
196                                 "setMark": (self.setMark, _("Make this mark just a mark")),
197                                 "addMark": (self.__addMark, _("Add a mark")),
198                                 "removeMark": (self.__removeMark, _("Remove a mark")),
199                                 "leave": (self.exit, _("Exit editor")),
200                                 "showMenu": (self.showMenu, _("menu")),
201                         }, prio=-4)
202
203                 self.tutorial_seen = False
204
205                 self.onExecBegin.append(self.showTutorial)
206                 self.__event_tracker = ServiceEventTracker(screen=self, eventmap=
207                         {
208                                 iPlayableService.evCuesheetChanged: self.refillList
209                         })
210
211                 # to track new entries we save the last version of the cutlist
212                 self.last_cuts = [ ]
213                 self.cut_start = None
214
215         def showTutorial(self):
216                 if not self.tutorial_seen:
217                         self.tutorial_seen = True
218                         self.session.open(MessageBox, 
219                                 """Welcome to the Cutlist editor. 
220
221 Seek to the start of the stuff you want to cut away. Press OK, select 'start cut'.
222
223 Then seek to the end, press OK, select 'end cut'. That's it.
224                                 """, 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.session.nav.playService(self.old_service)
260                 self.close()
261
262         def getCutlist(self):
263                 r = [ ]
264                 for e in self.cut_list:
265                         r.append(CutListEntry(*e))
266                 return r
267
268         def selectionChanged(self):
269                 where = self["Cutlist"].getCurrent()
270                 if where is None:
271                         print "no selection"
272                         return
273                 pts = where[0][0]
274                 seek = self.getSeek()
275                 if seek is None:
276                         print "no seek"
277                         return
278                 seek.seekTo(pts)
279
280         def refillList(self):
281                 print "cue sheet changed, refilling"
282                 self.downloadCuesheet()
283
284                 # get the first changed entry, and select it
285                 new_list = self.getCutlist()
286                 self["Cutlist"].setList(new_list)
287
288                 for i in range(min(len(new_list), len(self.last_cuts))):
289                         if new_list[i] != self.last_cuts[i]:
290                                 self["Cutlist"].setSelection(i)
291                                 break
292                 self.last_cuts = new_list
293
294         def getStateForPosition(self, pos):
295                 state = 0 # in
296
297                 # when first point is "in", the beginning is "out"
298                 if len(self.cut_list) and self.cut_list[0][1] == 0:
299                         state = 1
300
301                 for (where, what) in self.cut_list:
302                         if where < pos:
303                                 if what == 0: # in
304                                         state = 0
305                                 elif what == 1: # out
306                                         state = 1
307                 return state
308
309         def showMenu(self):
310                 curpos = self.cueGetCurrentPosition()
311                 if curpos is None:
312                         return
313
314                 self.setSeekState(self.SEEK_STATE_PAUSE)
315
316                 self.context_position = curpos
317
318                 self.context_nearest_mark = self.toggleMark(onlyreturn=True)
319
320                 cur_state = self.getStateForPosition(curpos)
321                 if cur_state == 0:
322                         print "currently in 'IN'"
323                         if self.cut_start is None or self.context_position < self.cut_start:
324                                 state = CutListContextMenu.SHOW_STARTCUT
325                         else:
326                                 state = CutListContextMenu.SHOW_ENDCUT
327                 else:
328                         print "currently in 'OUT'"
329                         state = CutListContextMenu.SHOW_DELETECUT
330
331                 if self.context_nearest_mark is None:
332                         nearmark = False
333                 else:
334                         nearmark = True
335
336                 self.session.openWithCallback(self.menuCallback, CutListContextMenu, state, nearmark)
337
338         def menuCallback(self, *result):
339                 if not len(result):
340                         return
341                 result = result[0]
342
343                 if result == CutListContextMenu.RET_STARTCUT:
344                         self.cut_start = self.context_position
345                 elif result == CutListContextMenu.RET_ENDCUT:
346                         # remove in/out marks between the new cut
347                         for (where, what) in self.cut_list[:]:
348                                 if self.cut_start <= where <= self.context_position and what in [0,1]:
349                                         self.cut_list.remove((where, what))
350
351                         bisect.insort(self.cut_list, (self.cut_start, 1))
352                         bisect.insort(self.cut_list, (self.context_position, 0))
353                         self.uploadCuesheet()
354                         self.cut_start = None
355                 elif result == CutListContextMenu.RET_DELETECUT:
356                         out_before = None
357                         in_after = None
358
359                         for (where, what) in self.cut_list:
360                                 if what == 1 and where < self.context_position: # out
361                                         out_before = (where, what)
362                                 elif what == 0 and where < self.context_position: # in, before out
363                                         out_before = None
364                                 elif what == 0 and where > self.context_position and in_after is None:
365                                         in_after = (where, what)
366
367                         if out_before is not None:
368                                 self.cut_list.remove(out_before)
369
370                         if in_after is not None:
371                                 self.cut_list.remove(in_after)
372                         self.uploadCuesheet()
373                 elif result == CutListContextMenu.RET_MARK:
374                         self.__addMark()
375                 elif result == CutListContextMenu.RET_DELETEMARK:
376                         self.cut_list.remove(self.context_nearest_mark)
377                         self.uploadCuesheet()
378                 elif result == CutListContextMenu.RET_REMOVEBEFORE:
379                         # remove in/out marks before current position
380                         for (where, what) in self.cut_list[:]:
381                                 if where <= self.context_position and what in [0,1]:
382                                         self.cut_list.remove((where, what))
383                         # add 'in' point
384                         bisect.insort(self.cut_list, (self.context_position, 0))
385                         self.uploadCuesheet()
386                 elif result == CutListContextMenu.RET_REMOVEAFTER:
387                         # remove in/out marks after current position
388                         for (where, what) in self.cut_list[:]:
389                                 if where >= self.context_position and what in [0,1]:
390                                         self.cut_list.remove((where, what))
391                         # add 'out' point
392                         bisect.insort(self.cut_list, (self.context_position, 1))
393                         self.uploadCuesheet()
394
395         # we modify the "play" behavior a bit:
396         # if we press pause while being in slowmotion, we will pause (and not play)
397         def playpauseService(self):
398                 if self.seekstate != self.SEEK_STATE_PLAY and not self.isStateSlowMotion(self.seekstate):
399                         self.unPauseService()
400                 else:
401                         self.pauseService()
402
403 def main(session, service, **kwargs):
404         session.open(CutListEditor, service)
405
406 def Plugins(**kwargs):
407         return PluginDescriptor(name="Cutlist Editor", description=_("Cutlist editor..."), where = PluginDescriptor.WHERE_MOVIELIST, fnc=main)