remove comments from Makefile.am to make automatic parsing easier
[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, InfoBarBase
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 from ServiceReference import ServiceReference
16 import bisect
17
18 def CutListEntry(where, what):
19         res = [ (where, what) ]
20         w = where / 90
21         ms = w % 1000
22         s = (w / 1000) % 60
23         m = (w / 60000) % 60
24         h = w / 3600000
25         if what == 0:
26                 type = "IN"
27         elif what == 1:
28                 type = "OUT"
29         elif what == 2:
30                 type = "MARK"
31         elif what == 3:
32                 type = "LAST"
33         res.append(MultiContentEntryText(size=(400, 20), text = "%dh:%02dm:%02ds:%03d" % (h, m, s, ms)))
34         res.append(MultiContentEntryText(pos=(400,0), size=(130, 20), text = type, flags = RT_HALIGN_RIGHT))
35
36         return res
37
38 class CutListContextMenu(FixedMenu):
39         RET_STARTCUT = 0
40         RET_ENDCUT = 1
41         RET_DELETECUT = 2
42         RET_MARK = 3
43         RET_DELETEMARK = 4
44         RET_REMOVEBEFORE = 5
45         RET_REMOVEAFTER = 6
46         RET_GRABFRAME = 7
47
48         SHOW_STARTCUT = 0
49         SHOW_ENDCUT = 1
50         SHOW_DELETECUT = 2
51
52         def __init__(self, session, state, nearmark):
53                 menu = [(_("back"), self.close)] #, (None, )]
54
55                 if state == self.SHOW_STARTCUT:
56                         menu.append((_("start cut here"), self.startCut))
57                 else:
58                         menu.append((_("start cut here"), ))
59
60                 if state == self.SHOW_ENDCUT:
61                         menu.append((_("end cut here"), self.endCut))
62                 else:
63                         menu.append((_("end cut here"), ))
64
65                 if state == self.SHOW_DELETECUT:
66                         menu.append((_("delete cut"), self.deleteCut))
67                 else:
68                         menu.append((_("delete cut"), ))
69
70                 menu.append((_("remove before this position"), self.removeBefore))
71                 menu.append((_("remove after this position"), self.removeAfter))
72
73 #               menu.append((None, ))
74
75                 if not nearmark:
76                         menu.append((_("insert mark here"), self.insertMark))
77                 else:
78                         menu.append((_("remove this mark"), self.removeMark))
79
80                 menu.append((("grab this frame as bitmap"), self.grabFrame))
81                 FixedMenu.__init__(self, session, _("Cut"), menu)
82                 self.skinName = "Menu"
83
84         def startCut(self):
85                 self.close(self.RET_STARTCUT)
86
87         def endCut(self):
88                 self.close(self.RET_ENDCUT)
89
90         def deleteCut(self):
91                 self.close(self.RET_DELETECUT)
92
93         def insertMark(self):
94                 self.close(self.RET_MARK)
95
96         def removeMark(self):
97                 self.close(self.RET_DELETEMARK)
98
99         def removeBefore(self):
100                 self.close(self.RET_REMOVEBEFORE)
101
102         def removeAfter(self):
103                 self.close(self.RET_REMOVEAFTER)
104
105         def grabFrame(self):
106                 self.close(self.RET_GRABFRAME)
107
108 class CutList(GUIComponent):
109         def __init__(self, list):
110                 GUIComponent.__init__(self)
111                 self.l = eListboxPythonMultiContent()
112                 self.setList(list)
113                 self.l.setFont(0, gFont("Regular", 20))
114                 self.onSelectionChanged = [ ]
115
116         def getCurrent(self):
117                 return self.l.getCurrentSelection()
118
119         def getCurrentIndex(self):
120                 return self.l.getCurrentSelectionIndex()
121
122         GUI_WIDGET = eListbox
123
124         def postWidgetCreate(self, instance):
125                 instance.setContent(self.l)
126                 instance.setItemHeight(30)
127                 instance.selectionChanged.get().append(self.selectionChanged)
128
129         def preWidgetRemove(self, instance):
130                 instance.setContent(None)
131                 instance.selectionChanged.get().remove(self.selectionChanged)
132
133         def selectionChanged(self):
134                 for x in self.onSelectionChanged:
135                         x()
136
137         def invalidateEntry(self, index):
138                 self.l.invalidateEntry(index)
139
140         def setIndex(self, index, data):
141                 self.list[index] = data
142                 self.invalidateEntry(index)
143
144         def setList(self, list):
145                 self.list = list
146                 self.l.setList(self.list)
147
148         def setSelection(self, index):
149                 if self.instance is not None:
150                         self.instance.moveSelectionTo(index)
151
152 class CutListEditor(Screen, InfoBarBase, InfoBarSeek, InfoBarCueSheetSupport, HelpableScreen):
153         skin = """
154         <screen position="0,0" size="720,576" title="Cutlist editor" flags="wfNoBorder">
155                 <eLabel text="Cutlist editor" position="65,60" size="300,25" font="Regular;20" />
156                 <widget source="global.CurrentTime" render="Label" position="268,60" size="394,20" font="Regular;20" halign="right">
157                         <convert type="ClockToText">Format:%A %B %d, %H:%M</convert>
158                 </widget>
159                 <eLabel position="268,98" size="394,304" backgroundColor="#505555" />
160                 <widget name="Video" position="270,100" zPosition="1" size="390,300" backgroundColor="transparent" />
161                 <widget source="session.CurrentService" render="Label" position="135,405" size="450,50" font="Regular;22" halign="center" valign="center">
162                         <convert type="ServiceName">Name</convert>
163                 </widget>
164                 <widget source="session.CurrentService" render="Label" position="50,450" zPosition="1" size="620,25" font="Regular;20" halign="center" valign="center">
165                         <convert type="ServicePosition">Position,Detailed</convert>
166                 </widget>
167                 <eLabel position="62,98" size="179,274" backgroundColor="#505555" />
168                 <eLabel position="64,100" size="175,270" backgroundColor="#000000" />
169                 <widget name="Cutlist" position="64,100" zPosition="1" size="175,270" scrollbarMode="showOnDemand" transparent="1" />
170                 <widget name="Timeline" position="50,485" size="615,20" backgroundColor="#505555" pointer="skin_default/position_arrow.png:3,5" foregroundColor="black" />
171                 <ePixmap pixmap="skin_default/icons/mp_buttons.png" position="305,515" size="109,13" alphatest="on" />
172         </screen>"""
173
174         def __init__(self, session, service):
175                 self.skin = CutListEditor.skin
176                 Screen.__init__(self, session)
177                 InfoBarSeek.__init__(self, actionmap = "CutlistSeekActions")
178                 InfoBarCueSheetSupport.__init__(self)
179                 InfoBarBase.__init__(self, steal_current_service = True)
180                 HelpableScreen.__init__(self)
181                 self.old_service = session.nav.getCurrentlyPlayingServiceReference()
182                 session.nav.playService(service)
183
184                 service = session.nav.getCurrentService()
185                 cue = service and service.cueSheet()
186                 if cue is not None:
187                         # disable cutlists. we want to freely browse around in the movie
188                         print "cut lists disabled!"
189                         cue.setCutListEnable(0)
190
191                 self.downloadCuesheet()
192
193                 self["Timeline"] = ServicePositionGauge(self.session.nav)
194                 self["Cutlist"] = CutList(self.getCutlist())
195                 self["Cutlist"].onSelectionChanged.append(self.selectionChanged)
196
197                 self["Video"] = VideoWindow(decoder = 0)
198
199                 self["actions"] = HelpableActionMap(self, "CutListEditorActions",
200                         {
201                                 "setIn": (self.setIn, _("Make this mark an 'in' point")),
202                                 "setOut": (self.setOut, _("Make this mark an 'out' point")),
203                                 "setMark": (self.setMark, _("Make this mark just a mark")),
204                                 "addMark": (self.__addMark, _("Add a mark")),
205                                 "removeMark": (self.__removeMark, _("Remove a mark")),
206                                 "leave": (self.exit, _("Exit editor")),
207                                 "showMenu": (self.showMenu, _("menu")),
208                         }, prio=-4)
209
210                 self.tutorial_seen = False
211
212                 self.onExecBegin.append(self.showTutorial)
213                 self.__event_tracker = ServiceEventTracker(screen=self, eventmap=
214                         {
215                                 iPlayableService.evCuesheetChanged: self.refillList
216                         })
217
218                 # to track new entries we save the last version of the cutlist
219                 self.last_cuts = [ ]
220                 self.cut_start = None
221                 self.onClose.append(self.__onClose)
222
223         def __onClose(self):
224                 self.session.nav.playService(self.old_service)
225
226         def showTutorial(self):
227                 if not self.tutorial_seen:
228                         self.tutorial_seen = True
229                         self.session.open(MessageBox,_("Welcome to the Cutlist editor.\n\nSeek to the start of the stuff you want to cut away. Press OK, select 'start cut'.\n\nThen seek to the end, press OK, select 'end cut'. That's it."), MessageBox.TYPE_INFO)
230
231         def checkSkipShowHideLock(self):
232                 pass
233
234         def setType(self, index, type):
235                 if len(self.cut_list):
236                         self.cut_list[index] = (self.cut_list[index][0], type)
237                         self["Cutlist"].setIndex(index, CutListEntry(*self.cut_list[index]))
238
239         def setIn(self):
240                 m = self["Cutlist"].getCurrentIndex()
241                 self.setType(m, 0)
242                 self.uploadCuesheet()
243
244         def setOut(self):
245                 m = self["Cutlist"].getCurrentIndex()
246                 self.setType(m, 1)
247                 self.uploadCuesheet()
248
249         def setMark(self):
250                 m = self["Cutlist"].getCurrentIndex()
251                 self.setType(m, 2)
252                 self.uploadCuesheet()
253
254         def __addMark(self):
255                 self.toggleMark(onlyadd=True, tolerance=90000) # do not allow two marks in <1s
256
257         def __removeMark(self):
258                 m = self["Cutlist"].getCurrent()
259                 m = m and m[0]
260                 if m is not None:
261                         self.removeMark(m)
262
263         def exit(self):
264                 self.close()
265
266         def getCutlist(self):
267                 r = [ ]
268                 for e in self.cut_list:
269                         r.append(CutListEntry(*e))
270                 return r
271
272         def selectionChanged(self):
273                 where = self["Cutlist"].getCurrent()
274                 if where is None:
275                         print "no selection"
276                         return
277                 pts = where[0][0]
278                 seek = self.getSeek()
279                 if seek is None:
280                         print "no seek"
281                         return
282                 seek.seekTo(pts)
283
284         def refillList(self):
285                 print "cue sheet changed, refilling"
286                 self.downloadCuesheet()
287
288                 # get the first changed entry, and select it
289                 new_list = self.getCutlist()
290                 self["Cutlist"].setList(new_list)
291
292                 for i in range(min(len(new_list), len(self.last_cuts))):
293                         if new_list[i] != self.last_cuts[i]:
294                                 self["Cutlist"].setSelection(i)
295                                 break
296                 self.last_cuts = new_list
297
298         def getStateForPosition(self, pos):
299                 state = 0 # in
300
301                 # when first point is "in", the beginning is "out"
302                 if len(self.cut_list) and self.cut_list[0][1] == 0:
303                         state = 1
304
305                 for (where, what) in self.cut_list:
306                         if where < pos:
307                                 if what == 0: # in
308                                         state = 0
309                                 elif what == 1: # out
310                                         state = 1
311                 return state
312
313         def showMenu(self):
314                 curpos = self.cueGetCurrentPosition()
315                 if curpos is None:
316                         return
317
318                 self.setSeekState(self.SEEK_STATE_PAUSE)
319
320                 self.context_position = curpos
321
322                 self.context_nearest_mark = self.toggleMark(onlyreturn=True)
323
324                 cur_state = self.getStateForPosition(curpos)
325                 if cur_state == 0:
326                         print "currently in 'IN'"
327                         if self.cut_start is None or self.context_position < self.cut_start:
328                                 state = CutListContextMenu.SHOW_STARTCUT
329                         else:
330                                 state = CutListContextMenu.SHOW_ENDCUT
331                 else:
332                         print "currently in 'OUT'"
333                         state = CutListContextMenu.SHOW_DELETECUT
334
335                 if self.context_nearest_mark is None:
336                         nearmark = False
337                 else:
338                         nearmark = True
339
340                 self.session.openWithCallback(self.menuCallback, CutListContextMenu, state, nearmark)
341
342         def menuCallback(self, *result):
343                 if not len(result):
344                         return
345                 result = result[0]
346
347                 if result == CutListContextMenu.RET_STARTCUT:
348                         self.cut_start = self.context_position
349                 elif result == CutListContextMenu.RET_ENDCUT:
350                         # remove in/out marks between the new cut
351                         for (where, what) in self.cut_list[:]:
352                                 if self.cut_start <= where <= self.context_position and what in [0,1]:
353                                         self.cut_list.remove((where, what))
354
355                         bisect.insort(self.cut_list, (self.cut_start, 1))
356                         bisect.insort(self.cut_list, (self.context_position, 0))
357                         self.uploadCuesheet()
358                         self.cut_start = None
359                 elif result == CutListContextMenu.RET_DELETECUT:
360                         out_before = None
361                         in_after = None
362
363                         for (where, what) in self.cut_list:
364                                 if what == 1 and where < self.context_position: # out
365                                         out_before = (where, what)
366                                 elif what == 0 and where < self.context_position: # in, before out
367                                         out_before = None
368                                 elif what == 0 and where > self.context_position and in_after is None:
369                                         in_after = (where, what)
370
371                         if out_before is not None:
372                                 self.cut_list.remove(out_before)
373
374                         if in_after is not None:
375                                 self.cut_list.remove(in_after)
376                         self.uploadCuesheet()
377                 elif result == CutListContextMenu.RET_MARK:
378                         self.__addMark()
379                 elif result == CutListContextMenu.RET_DELETEMARK:
380                         self.cut_list.remove(self.context_nearest_mark)
381                         self.uploadCuesheet()
382                 elif result == CutListContextMenu.RET_REMOVEBEFORE:
383                         # remove in/out marks before current position
384                         for (where, what) in self.cut_list[:]:
385                                 if where <= self.context_position and what in [0,1]:
386                                         self.cut_list.remove((where, what))
387                         # add 'in' point
388                         bisect.insort(self.cut_list, (self.context_position, 0))
389                         self.uploadCuesheet()
390                 elif result == CutListContextMenu.RET_REMOVEAFTER:
391                         # remove in/out marks after current position
392                         for (where, what) in self.cut_list[:]:
393                                 if where >= self.context_position and what in [0,1]:
394                                         self.cut_list.remove((where, what))
395                         # add 'out' point
396                         bisect.insort(self.cut_list, (self.context_position, 1))
397                         self.uploadCuesheet()
398                 elif result == CutListContextMenu.RET_GRABFRAME:
399                         self.grabFrame()
400
401         # we modify the "play" behavior a bit:
402         # if we press pause while being in slowmotion, we will pause (and not play)
403         def playpauseService(self):
404                 if self.seekstate != self.SEEK_STATE_PLAY and not self.isStateSlowMotion(self.seekstate):
405                         self.unPauseService()
406                 else:
407                         self.pauseService()
408
409         def grabFrame(self):
410                 path = self.session.nav.getCurrentlyPlayingServiceReference().getPath()
411                 from Components.Console import Console
412                 grabConsole = Console()
413                 cmd = 'grab -vblpr%d "%s"' % (180, path.rsplit('.',1)[0] + ".png")
414                 grabConsole.ePopen(cmd)
415                 self.playpauseService()
416
417 def main(session, service, **kwargs):
418         session.open(CutListEditor, service)
419
420 def Plugins(**kwargs):
421         return PluginDescriptor(name="Cutlist Editor", description=_("Cutlist editor..."), where = PluginDescriptor.WHERE_MOVIELIST, fnc=main)