25a5e7422a505dc3d7fb8363529a1955411d5cf9
[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.MenuList import MenuList
8 from Components.MultiContent import MultiContentEntryText, RT_HALIGN_RIGHT
9 from Components.ServiceEventTracker import ServiceEventTracker
10 from Screens.InfoBarGenerics import InfoBarSeek, InfoBarCueSheetSupport
11 from Components.GUIComponent import GUIComponent
12 from enigma import eListboxPythonMultiContent, eListbox, gFont, iPlayableService
13 from Screens.FixedMenu import FixedMenu
14 import bisect
15
16 def CutListEntry(where, what):
17         res = [ (where, what) ]
18         w = where / 90
19         ms = w % 1000
20         s = (w / 1000) % 60
21         m = (w / 60000) % 60
22         h = w / 3600000
23         if what == 0:
24                 type = "IN"
25         elif what == 1:
26                 type = "OUT"
27         elif what == 2:
28                 type = "MARK"
29         res.append(MultiContentEntryText(size=(400, 20), text = "%dh:%02dm:%02ds:%03d" % (h, m, s, ms)))
30         res.append(MultiContentEntryText(pos=(400,0), size=(130, 20), text = type, flags = RT_HALIGN_RIGHT))
31
32         return res
33
34 class CutListContextMenu(FixedMenu):
35         RET_STARTCUT = 0
36         RET_ENDCUT = 1
37         RET_DELETECUT = 2
38         RET_MARK = 3
39         RET_DELETEMARK = 4
40         
41         SHOW_STARTCUT = 0
42         SHOW_ENDCUT = 1
43         SHOW_DELETECUT = 2
44         
45         def __init__(self, session, state, nearmark):
46                 menu = [(_("back"), self.close), (None, )]
47
48                 if state == self.SHOW_STARTCUT:
49                         menu.append((_("start cut here"), self.startCut))
50                 else:
51                         menu.append((_("start cut here"), ))
52
53                 if state == self.SHOW_ENDCUT:
54                         menu.append((_("end cut here"), self.endCut))
55                 else:
56                         menu.append((_("end cut here"), ))
57
58                 if state == self.SHOW_DELETECUT:
59                         menu.append((_("delete cut"), self.deleteCut))
60                 else:
61                         menu.append((_("delete cut"), ))
62
63                 menu.append((None, ))
64
65                 if not nearmark:
66                         menu.append((_("insert mark here"), self.insertMark))
67                 else:
68                         menu.append((_("remove this mark"), self.removeMark))
69
70                 FixedMenu.__init__(self, session, _("Cut"), menu)
71                 self.skinName = "Menu"
72
73         def startCut(self):
74                 self.close(self.RET_STARTCUT)
75
76         def endCut(self):
77                 self.close(self.RET_ENDCUT)
78
79         def deleteCut(self):
80                 self.close(self.RET_DELETECUT)
81
82         def insertMark(self):
83                 self.close(self.RET_MARK)
84
85         def removeMark(self):
86                 self.close(self.RET_DELETEMARK)
87
88 class CutList(GUIComponent):
89         def __init__(self, list):
90                 GUIComponent.__init__(self)
91                 self.l = eListboxPythonMultiContent()
92                 self.setList(list)
93                 self.l.setFont(0, gFont("Regular", 20))
94                 self.onSelectionChanged = [ ]
95         
96         def getCurrent(self):
97                 return self.l.getCurrentSelection()
98         
99         def getCurrentIndex(self):
100                 return self.l.getCurrentSelectionIndex()
101         
102         def GUIcreate(self, parent):
103                 self.instance = eListbox(parent)
104                 self.instance.setContent(self.l)
105                 self.instance.setItemHeight(30)
106                 self.instance.selectionChanged.get().append(self.selectionChanged)
107
108         def selectionChanged(self):
109                 for x in self.onSelectionChanged:
110                         x()
111         
112         def GUIdelete(self):
113                 self.instance.selectionChanged.get().remove(self.selectionChanged)
114                 self.instance.setContent(None)
115                 self.instance = None
116         
117         def invalidateEntry(self, index):
118                 self.l.invalidateEntry(index)
119         
120         def setIndex(self, index, data):
121                 self.list[index] = data
122                 self.invalidateEntry(index)
123
124         def setList(self, list):
125                 self.list = list
126                 self.l.setList(self.list)
127         
128         def setSelection(self, index):
129                 if self.instance is not None:
130                         self.instance.moveSelectionTo(index)
131
132 class CutListEditor(Screen, InfoBarSeek, InfoBarCueSheetSupport):
133         skin = """
134                 <screen position="100,100" size="550,400" title="Test" >
135                         <widget name="Timeline" position="10,0" size="530,40" 
136                                 pointer="/usr/share/enigma2/position_pointer.png:3,5" />
137                         <widget name="Cutlist" position="10,50" size="530,300" scrollbarMode="showOnDemand" />
138                 </screen>"""
139         def __init__(self, session, service):
140                 self.skin = CutListEditor.skin
141                 Screen.__init__(self, session)
142                 InfoBarSeek.__init__(self)
143                 InfoBarCueSheetSupport.__init__(self)
144                 session.nav.playService(service)
145                 
146                 service = session.nav.getCurrentService()
147                 cue = service and service.cueSheet()
148                 if cue is not None:
149                         # disable cutlists. we want to freely browse around in the movie
150                         print "cut lists disabled!"
151                         cue.setCutListEnable(0)
152                 
153                 self.downloadCuesheet()
154         
155                 self["Timeline"] = ServicePositionGauge(self.session.nav)
156                 self["Cutlist"] = CutList(self.getCutlist())
157                 self["Cutlist"].onSelectionChanged.append(self.selectionChanged)
158                 
159                 self["actions"] = HelpableActionMap(self, "CutListEditorActions",
160                         {
161                                 "setIn": (self.setIn, _("Make this mark an 'in' point")),
162                                 "setOut": (self.setOut, _("Make this mark an 'out' point")),
163                                 "setMark": (self.setMark, _("Make this mark just a mark")),
164                                 "addMark": (self.__addMark, _("Add a mark")),
165                                 "removeMark": (self.__removeMark, _("Remove a mark")),
166                                 "leave": (self.exit, _("Exit editor")),
167                                 "showMenu": self.showMenu,
168                         })
169                 
170                 self.tutorial_seen = False
171                 
172                 self.onExecBegin.append(self.showTutorial)
173                 self.__event_tracker = ServiceEventTracker(screen=self, eventmap=
174                         {
175                                 iPlayableService.evCuesheetChanged: self.refillList
176                         })
177
178                 # to track new entries we save the last version of the cutlist
179                 self.last_cuts = [ ]
180                 
181                 self.cut_start = None
182                 
183         def showTutorial(self):
184                 if not self.tutorial_seen:
185                         self.tutorial_seen = True
186                         self.session.open(MessageBox, 
187                                 """Welcome to the Cutlist editor. It's still a bit strange to use, but anyway:
188
189 Seek to the start of the stuff you want to cut away. Press OK, select 'start cut'.
190
191 Then seek to the end, press OK, select 'end cut'. That's it.
192                                 """, MessageBox.TYPE_INFO)
193         
194         def checkSkipShowHideLock(self):
195                 pass
196         
197         def setType(self, index, type):
198                 self.cut_list[index] = (self.cut_list[index][0], type)
199                 self["Cutlist"].setIndex(index, CutListEntry(*self.cut_list[index]))
200         
201         def setIn(self):
202                 m = self["Cutlist"].getCurrentIndex()
203                 self.setType(m, 0)
204                 self.uploadCuesheet()
205         
206         def setOut(self):
207                 m = self["Cutlist"].getCurrentIndex()
208                 self.setType(m, 1)
209                 self.uploadCuesheet()
210
211         def setMark(self):
212                 m = self["Cutlist"].getCurrentIndex()
213                 self.setType(m, 2)
214                 self.uploadCuesheet()
215         
216         def __addMark(self):
217                 self.toggleMark(onlyadd=True, tolerance=90000) # do not allow two marks in <1s
218         
219         def __removeMark(self):
220                 m = self["Cutlist"].getCurrent()
221                 m = m and m[0]
222                 if m is not None:
223                         self.removeMark(m)
224         
225         def exit(self):
226                 self.close()
227
228         def getCutlist(self):
229                 r = [ ]
230                 for e in self.cut_list:
231                         r.append(CutListEntry(*e))
232                 return r
233
234         def selectionChanged(self):
235                 where = self["Cutlist"].getCurrent()
236                 if where is None:
237                         print "no selection"
238                         return
239                 pts = where[0][0]
240                 seek = self.getSeek()
241                 if seek is None:
242                         print "no seek"
243                         return
244                 seek.seekTo(pts)
245
246         def refillList(self):
247                 print "cue sheet changed, refilling"
248                 self.downloadCuesheet()
249                 
250                 # get the first changed entry, and select it
251                 new_list = self.getCutlist()
252                 self["Cutlist"].setList(new_list)
253                 
254                 for i in range(min(len(new_list), len(self.last_cuts))):
255                         if new_list[i] != self.last_cuts[i]:
256                                 self["Cutlist"].setSelection(i)
257                                 break
258                 self.last_cuts = new_list
259
260         def getStateForPosition(self, pos):
261                 state = 0 # in
262                 for (where, what) in self.cut_list:
263                         if where < pos:
264                                 if what == 0: # in
265                                         state = 0
266                                 elif what == 1: # out
267                                         state = 1
268                 return state
269
270         def showMenu(self):
271                 curpos = self.cueGetCurrentPosition()
272                 if curpos is None:
273                         return
274                 
275                 self.setSeekState(self.SEEK_STATE_PAUSE)
276                 
277                 self.context_position = curpos
278                 
279                 self.context_nearest_mark = self.toggleMark(onlyreturn=True)
280
281                 cur_state = self.getStateForPosition(curpos)
282                 if cur_state == 0:
283                         print "currently in 'IN'"
284                         if self.cut_start is None or self.context_position < self.cut_start:
285                                 state = CutListContextMenu.SHOW_STARTCUT
286                         else:
287                                 state = CutListContextMenu.SHOW_ENDCUT
288                 else:
289                         print "currently in 'OUT'"
290                         state = CutListContextMenu.SHOW_DELETECUT
291                 
292                 if self.context_nearest_mark is None:
293                         nearmark = False
294                 else:
295                         nearmark = True
296                 
297                 self.session.openWithCallback(self.menuCallback, CutListContextMenu, state, nearmark)
298         
299         def menuCallback(self, *result):
300                 self.setSeekState(self.SEEK_STATE_PLAY)
301                 if not len(result):
302                         return
303                 result = result[0]
304                 
305                 if result == CutListContextMenu.RET_STARTCUT:
306                         self.cut_start = self.context_position
307                 elif result == CutListContextMenu.RET_ENDCUT:
308                         # remove in/out marks between the new cut
309                         for (where, what) in self.cut_list[:]:
310                                 if self.cut_start <= where <= self.context_position and what in [0,1]:
311                                         self.cut_list.remove((where, what))
312                         
313                         bisect.insort(self.cut_list, (self.cut_start, 1))
314                         bisect.insort(self.cut_list, (self.context_position, 0))
315                         self.uploadCuesheet()
316                         self.cut_start = None
317                 elif result == CutListContextMenu.RET_DELETECUT:
318                         out_before = None
319                         in_after = None
320                         
321                         for (where, what) in self.cut_list:
322                                 if what == 1 and where < self.context_position: # out
323                                         out_before = (where, what)
324                                 elif what == 0 and where < self.context_position: # in, before out
325                                         out_before = None
326                                 elif what == 0 and where > self.context_position and in_after is None:
327                                         in_after = (where, what)
328                         
329                         if out_before is not None:
330                                 self.cut_list.remove(out_before)
331                         
332                         if in_after is not None:
333                                 self.cut_list.remove(in_after)
334                         self.uploadCuesheet()
335                 elif result == CutListContextMenu.RET_MARK:
336                         self.__addMark()
337                 elif result == CutListContextMenu.RET_DELETEMARK:
338                         self.cut_list.remove(self.context_nearest_mark)
339                         self.uploadCuesheet()
340
341 def main(session, service):
342         session.open(CutListEditor, service)
343
344 def Plugins():
345         return PluginDescriptor(name="Cutlist Editor", description=_("Cutlist editor..."), where = PluginDescriptor.WHERE_MOVIELIST, fnc=main)