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