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