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