e2bf7235d606ba71bc0d050d783f9f3a6cd0f71a
[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, InfoBarServiceName
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, InfoBarServiceName, 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/position_pointer.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                 InfoBarServiceName.__init__(self)
174                 HelpableScreen.__init__(self)
175                 self.old_service = session.nav.getCurrentlyPlayingServiceReference()
176                 session.nav.playService(service)
177
178                 service = session.nav.getCurrentService()
179                 cue = service and service.cueSheet()
180                 if cue is not None:
181                         # disable cutlists. we want to freely browse around in the movie
182                         print "cut lists disabled!"
183                         cue.setCutListEnable(0)
184
185                 self.downloadCuesheet()
186
187                 self["Timeline"] = ServicePositionGauge(self.session.nav)
188                 self["Cutlist"] = CutList(self.getCutlist())
189                 self["Cutlist"].onSelectionChanged.append(self.selectionChanged)
190
191                 self["Video"] = VideoWindow(decoder = 0)
192
193                 self["actions"] = HelpableActionMap(self, "CutListEditorActions",
194                         {
195                                 "setIn": (self.setIn, _("Make this mark an 'in' point")),
196                                 "setOut": (self.setOut, _("Make this mark an 'out' point")),
197                                 "setMark": (self.setMark, _("Make this mark just a mark")),
198                                 "addMark": (self.__addMark, _("Add a mark")),
199                                 "removeMark": (self.__removeMark, _("Remove a mark")),
200                                 "leave": (self.exit, _("Exit editor")),
201                                 "showMenu": (self.showMenu, _("menu")),
202                         }, prio=-4)
203
204                 self.tutorial_seen = False
205
206                 self.onExecBegin.append(self.showTutorial)
207                 self.__event_tracker = ServiceEventTracker(screen=self, eventmap=
208                         {
209                                 iPlayableService.evCuesheetChanged: self.refillList
210                         })
211
212                 # to track new entries we save the last version of the cutlist
213                 self.last_cuts = [ ]
214                 self.cut_start = None
215
216         def showTutorial(self):
217                 if not self.tutorial_seen:
218                         self.tutorial_seen = True
219                         self.session.open(MessageBox, 
220                                 """Welcome to the Cutlist editor. 
221
222 Seek to the start of the stuff you want to cut away. Press OK, select 'start cut'.
223
224 Then seek to the end, press OK, select 'end cut'. That's it.
225                                 """, MessageBox.TYPE_INFO)
226
227         def checkSkipShowHideLock(self):
228                 pass
229
230         def setType(self, index, type):
231                 if len(self.cut_list):
232                         self.cut_list[index] = (self.cut_list[index][0], type)
233                         self["Cutlist"].setIndex(index, CutListEntry(*self.cut_list[index]))
234
235         def setIn(self):
236                 m = self["Cutlist"].getCurrentIndex()
237                 self.setType(m, 0)
238                 self.uploadCuesheet()
239
240         def setOut(self):
241                 m = self["Cutlist"].getCurrentIndex()
242                 self.setType(m, 1)
243                 self.uploadCuesheet()
244
245         def setMark(self):
246                 m = self["Cutlist"].getCurrentIndex()
247                 self.setType(m, 2)
248                 self.uploadCuesheet()
249
250         def __addMark(self):
251                 self.toggleMark(onlyadd=True, tolerance=90000) # do not allow two marks in <1s
252
253         def __removeMark(self):
254                 m = self["Cutlist"].getCurrent()
255                 m = m and m[0]
256                 if m is not None:
257                         self.removeMark(m)
258
259         def exit(self):
260                 self.session.nav.playService(self.old_service)
261                 self.close()
262
263         def getCutlist(self):
264                 r = [ ]
265                 for e in self.cut_list:
266                         r.append(CutListEntry(*e))
267                 return r
268
269         def selectionChanged(self):
270                 where = self["Cutlist"].getCurrent()
271                 if where is None:
272                         print "no selection"
273                         return
274                 pts = where[0][0]
275                 seek = self.getSeek()
276                 if seek is None:
277                         print "no seek"
278                         return
279                 seek.seekTo(pts)
280
281         def refillList(self):
282                 print "cue sheet changed, refilling"
283                 self.downloadCuesheet()
284
285                 # get the first changed entry, and select it
286                 new_list = self.getCutlist()
287                 self["Cutlist"].setList(new_list)
288
289                 for i in range(min(len(new_list), len(self.last_cuts))):
290                         if new_list[i] != self.last_cuts[i]:
291                                 self["Cutlist"].setSelection(i)
292                                 break
293                 self.last_cuts = new_list
294
295         def getStateForPosition(self, pos):
296                 state = 0 # in
297
298                 # when first point is "in", the beginning is "out"
299                 if len(self.cut_list) and self.cut_list[0][1] == 0:
300                         state = 1
301
302                 for (where, what) in self.cut_list:
303                         if where < pos:
304                                 if what == 0: # in
305                                         state = 0
306                                 elif what == 1: # out
307                                         state = 1
308                 return state
309
310         def showMenu(self):
311                 curpos = self.cueGetCurrentPosition()
312                 if curpos is None:
313                         return
314
315                 self.setSeekState(self.SEEK_STATE_PAUSE)
316
317                 self.context_position = curpos
318
319                 self.context_nearest_mark = self.toggleMark(onlyreturn=True)
320
321                 cur_state = self.getStateForPosition(curpos)
322                 if cur_state == 0:
323                         print "currently in 'IN'"
324                         if self.cut_start is None or self.context_position < self.cut_start:
325                                 state = CutListContextMenu.SHOW_STARTCUT
326                         else:
327                                 state = CutListContextMenu.SHOW_ENDCUT
328                 else:
329                         print "currently in 'OUT'"
330                         state = CutListContextMenu.SHOW_DELETECUT
331
332                 if self.context_nearest_mark is None:
333                         nearmark = False
334                 else:
335                         nearmark = True
336
337                 self.session.openWithCallback(self.menuCallback, CutListContextMenu, state, nearmark)
338
339         def menuCallback(self, *result):
340                 if not len(result):
341                         return
342                 result = result[0]
343
344                 if result == CutListContextMenu.RET_STARTCUT:
345                         self.cut_start = self.context_position
346                 elif result == CutListContextMenu.RET_ENDCUT:
347                         # remove in/out marks between the new cut
348                         for (where, what) in self.cut_list[:]:
349                                 if self.cut_start <= where <= self.context_position and what in [0,1]:
350                                         self.cut_list.remove((where, what))
351
352                         bisect.insort(self.cut_list, (self.cut_start, 1))
353                         bisect.insort(self.cut_list, (self.context_position, 0))
354                         self.uploadCuesheet()
355                         self.cut_start = None
356                 elif result == CutListContextMenu.RET_DELETECUT:
357                         out_before = None
358                         in_after = None
359
360                         for (where, what) in self.cut_list:
361                                 if what == 1 and where < self.context_position: # out
362                                         out_before = (where, what)
363                                 elif what == 0 and where < self.context_position: # in, before out
364                                         out_before = None
365                                 elif what == 0 and where > self.context_position and in_after is None:
366                                         in_after = (where, what)
367
368                         if out_before is not None:
369                                 self.cut_list.remove(out_before)
370
371                         if in_after is not None:
372                                 self.cut_list.remove(in_after)
373                         self.uploadCuesheet()
374                 elif result == CutListContextMenu.RET_MARK:
375                         self.__addMark()
376                 elif result == CutListContextMenu.RET_DELETEMARK:
377                         self.cut_list.remove(self.context_nearest_mark)
378                         self.uploadCuesheet()
379                 elif result == CutListContextMenu.RET_REMOVEBEFORE:
380                         # remove in/out marks before current position
381                         for (where, what) in self.cut_list[:]:
382                                 if where <= self.context_position and what in [0,1]:
383                                         self.cut_list.remove((where, what))
384                         # add 'in' point
385                         bisect.insort(self.cut_list, (self.context_position, 0))
386                         self.uploadCuesheet()
387                 elif result == CutListContextMenu.RET_REMOVEAFTER:
388                         # remove in/out marks after current position
389                         for (where, what) in self.cut_list[:]:
390                                 if where >= self.context_position and what in [0,1]:
391                                         self.cut_list.remove((where, what))
392                         # add 'out' point
393                         bisect.insort(self.cut_list, (self.context_position, 1))
394                         self.uploadCuesheet()
395
396         # we modify the "play" behavior a bit:
397         # if we press pause while being in slowmotion, we will pause (and not play)
398         def playpauseService(self):
399                 if self.seekstate != self.SEEK_STATE_PLAY and not self.isStateSlowMotion(self.seekstate):
400                         self.unPauseService()
401                 else:
402                         self.pauseService()
403
404 def main(session, service, **kwargs):
405         session.open(CutListEditor, service)
406
407 def Plugins(**kwargs):
408         return PluginDescriptor(name="Cutlist Editor", description=_("Cutlist editor..."), where = PluginDescriptor.WHERE_MOVIELIST, fnc=main)