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