Merge branch 'master' into experimental
[enigma2.git] / lib / python / Plugins / Extensions / CutListEditor / plugin.py
index 76a7bdc80cad11430863306d3126c397a4ca06be..efe9f761e3d9e2a769e359161499f2d78608fcaa 100644 (file)
@@ -5,17 +5,20 @@ from Screens.MessageBox import MessageBox
 from Components.ServicePosition import ServicePositionGauge
 from Components.ActionMap import HelpableActionMap
 from Components.MultiContent import MultiContentEntryText
-from Components.ServiceEventTracker import ServiceEventTracker
+from Components.ServiceEventTracker import ServiceEventTracker, InfoBarBase
 from Components.VideoWindow import VideoWindow
-from Screens.InfoBarGenerics import InfoBarSeek, InfoBarCueSheetSupport, InfoBarServiceName
+from Components.Label import Label
+from Screens.InfoBarGenerics import InfoBarSeek, InfoBarCueSheetSupport
 from Components.GUIComponent import GUIComponent
 from enigma import eListboxPythonMultiContent, eListbox, gFont, iPlayableService, RT_HALIGN_RIGHT
 from Screens.FixedMenu import FixedMenu
 from Screens.HelpMenu import HelpableScreen
+from ServiceReference import ServiceReference
+from Components.Sources.List import List
+
 import bisect
 
 def CutListEntry(where, what):
-       res = [ (where, what) ]
        w = where / 90
        ms = w % 1000
        s = (w / 1000) % 60
@@ -23,16 +26,17 @@ def CutListEntry(where, what):
        h = w / 3600000
        if what == 0:
                type = "IN"
+               type_col = 0x004000
        elif what == 1:
                type = "OUT"
+               type_col = 0x400000
        elif what == 2:
                type = "MARK"
+               type_col = 0x000040
        elif what == 3:
                type = "LAST"
-       res.append(MultiContentEntryText(size=(400, 20), text = "%dh:%02dm:%02ds:%03d" % (h, m, s, ms)))
-       res.append(MultiContentEntryText(pos=(400,0), size=(130, 20), text = type, flags = RT_HALIGN_RIGHT))
-
-       return res
+               type_col = 0x000000
+       return ((where, what), "%dh:%02dm:%02ds:%03d" % (h, m, s, ms), type, type_col)
 
 class CutListContextMenu(FixedMenu):
        RET_STARTCUT = 0
@@ -42,6 +46,7 @@ class CutListContextMenu(FixedMenu):
        RET_DELETEMARK = 4
        RET_REMOVEBEFORE = 5
        RET_REMOVEAFTER = 6
+       RET_GRABFRAME = 7
 
        SHOW_STARTCUT = 0
        SHOW_ENDCUT = 1
@@ -75,6 +80,7 @@ class CutListContextMenu(FixedMenu):
                else:
                        menu.append((_("remove this mark"), self.removeMark))
 
+               menu.append((_("grab this frame as bitmap"), self.grabFrame))
                FixedMenu.__init__(self, session, _("Cut"), menu)
                self.skinName = "Menu"
 
@@ -99,78 +105,48 @@ class CutListContextMenu(FixedMenu):
        def removeAfter(self):
                self.close(self.RET_REMOVEAFTER)
 
+       def grabFrame(self):
+               self.close(self.RET_GRABFRAME)
 
-class CutList(GUIComponent):
-       def __init__(self, list):
-               GUIComponent.__init__(self)
-               self.l = eListboxPythonMultiContent()
-               self.setList(list)
-               self.l.setFont(0, gFont("Regular", 20))
-               self.onSelectionChanged = [ ]
-
-       def getCurrent(self):
-               return self.l.getCurrentSelection()
-
-       def getCurrentIndex(self):
-               return self.l.getCurrentSelectionIndex()
-
-       GUI_WIDGET = eListbox
-
-       def postWidgetCreate(self, instance):
-               instance.setContent(self.l)
-               instance.setItemHeight(30)
-               instance.selectionChanged.get().append(self.selectionChanged)
-
-       def preWidgetRemove(self, instance):
-               instance.setContent(None)
-               instance.selectionChanged.get().remove(self.selectionChanged)
-
-       def selectionChanged(self):
-               for x in self.onSelectionChanged:
-                       x()
-
-       def invalidateEntry(self, index):
-               self.l.invalidateEntry(index)
-
-       def setIndex(self, index, data):
-               self.list[index] = data
-               self.invalidateEntry(index)
-
-       def setList(self, list):
-               self.list = list
-               self.l.setList(self.list)
-
-       def setSelection(self, index):
-               if self.instance is not None:
-                       self.instance.moveSelectionTo(index)
-
-class CutListEditor(Screen, InfoBarSeek, InfoBarCueSheetSupport, InfoBarServiceName, HelpableScreen):
+class CutListEditor(Screen, InfoBarBase, InfoBarSeek, InfoBarCueSheetSupport, HelpableScreen):
        skin = """
-               <screen position="0,0" size="720,576" flags="wfNoBorder" backgroundColor="#444444">
-                       <eLabel position="360,0" size="360,313" backgroundColor="#ffffff" />
-                       <widget name="Video" position="370,10" size="340,268" backgroundColor="transparent" zPosition="1" />
+       <screen position="0,0" size="720,576" title="Cutlist editor" flags="wfNoBorder">
+               <eLabel text="Cutlist editor" position="65,60" size="300,25" font="Regular;20" />
+               <widget source="global.CurrentTime" render="Label" position="268,60" size="394,20" font="Regular;20" halign="right">
+                       <convert type="ClockToText">Format:%A %B %d, %H:%M</convert>
+               </widget>
+               <eLabel position="268,98" size="394,304" backgroundColor="#505555" />
+               <widget name="Video" position="270,100" zPosition="1" size="390,300" backgroundColor="transparent" />
+               <widget source="session.CurrentService" render="Label" position="135,405" size="450,50" font="Regular;22" halign="center" valign="center">
+                       <convert type="ServiceName">Name</convert>
+               </widget>
+               <widget source="session.CurrentService" render="Label" position="320,450" zPosition="1" size="420,25" font="Regular;20" halign="left" valign="center">
+                       <convert type="ServicePosition">Position,Detailed</convert>
+               </widget>
+               <widget name="SeekState" position="210,450" zPosition="1" size="100,25" halign="right" font="Regular;20" valign="center" />
+               <eLabel position="48,98" size="204,274" backgroundColor="#505555" />
+               <eLabel position="50,100" size="200,270" backgroundColor="#000000" />
+               <widget source="cutlist" position="50,100" zPosition="1" size="200,270" scrollbarMode="showOnDemand" transparent="1" render="Listbox" >
+                       <convert type="TemplatedMultiContent">
+                               {"template": [
+                                               MultiContentEntryText(size=(125, 20), text = 1, backcolor = MultiContentTemplateColor(3)),
+                                               MultiContentEntryText(pos=(125,0), size=(50, 20), text = 2, flags = RT_HALIGN_RIGHT, backcolor = MultiContentTemplateColor(3))
+                                       ],
+                                "fonts": [gFont("Regular", 18)],
+                                "itemHeight": 20
+                               }
+                       </convert>
+               </widget>
+               <widget name="Timeline" position="50,485" size="615,20" backgroundColor="#505555" pointer="skin_default/position_arrow.png:3,5" foregroundColor="black" />
+               <ePixmap pixmap="skin_default/icons/mp_buttons.png" position="305,515" size="109,13" alphatest="on" />
+       </screen>"""
 
-                       <eLabel position="50,80" size="300,24" text="Name:" font="Regular;20" foregroundColor="#cccccc" transparent="1" />
-
-                       <widget source="CurrentService" render="Label" position="50,110" size="300,60" font="Regular;22" >
-                               <convert type="ServiceName">Name</convert>
-                       </widget>
-
-                       <widget source="CurrentService" render="Label" position="370,278" size="340,25" 
-                               backgroundColor="#000000" foregroundColor="#ffffff" font="Regular;19" zPosition="1" >
-                               <convert type="ServicePosition">PositionDetailed</convert>
-                       </widget>
-
-                       <widget name="Timeline" position="50,500" size="620,40" backgroundColor="#000000"
-                               pointer="/usr/share/enigma2/position_pointer.png:3,5" foregroundColor="#ffffff" />
-                       <widget name="Cutlist" position="50,325" size="620,175" scrollbarMode="showOnDemand" transparent="1" />
-               </screen>"""
        def __init__(self, session, service):
                self.skin = CutListEditor.skin
                Screen.__init__(self, session)
                InfoBarSeek.__init__(self, actionmap = "CutlistSeekActions")
                InfoBarCueSheetSupport.__init__(self)
-               InfoBarServiceName.__init__(self)
+               InfoBarBase.__init__(self, steal_current_service = True)
                HelpableScreen.__init__(self)
                self.old_service = session.nav.getCurrentlyPlayingServiceReference()
                session.nav.playService(service)
@@ -185,8 +161,11 @@ class CutListEditor(Screen, InfoBarSeek, InfoBarCueSheetSupport, InfoBarServiceN
                self.downloadCuesheet()
 
                self["Timeline"] = ServicePositionGauge(self.session.nav)
-               self["Cutlist"] = CutList(self.getCutlist())
-               self["Cutlist"].onSelectionChanged.append(self.selectionChanged)
+               self["cutlist"] = List(self.getCutlist())
+               self["cutlist"].onSelectionChanged.append(self.selectionChanged)
+               self["SeekState"] = Label()
+               self.onPlayStateChanged.append(self.updateStateLabel)
+               self.updateStateLabel(self.seekstate)
 
                self["Video"] = VideoWindow(decoder = 0)
 
@@ -210,19 +189,21 @@ class CutListEditor(Screen, InfoBarSeek, InfoBarCueSheetSupport, InfoBarServiceN
                        })
 
                # to track new entries we save the last version of the cutlist
-               self.last_cuts = [ ]
+               self.last_cuts = self.getCutlist()
                self.cut_start = None
+               self.inhibit_seek = False
+               self.onClose.append(self.__onClose)
+
+       def __onClose(self):
+               self.session.nav.playService(self.old_service)
+
+       def updateStateLabel(self, state):
+               self["SeekState"].setText(state[3].strip())
 
        def showTutorial(self):
                if not self.tutorial_seen:
                        self.tutorial_seen = True
-                       self.session.open(MessageBox, 
-                               """Welcome to the Cutlist editor. 
-
-Seek to the start of the stuff you want to cut away. Press OK, select 'start cut'.
-
-Then seek to the end, press OK, select 'end cut'. That's it.
-                               """, MessageBox.TYPE_INFO)
+                       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)
 
        def checkSkipShowHideLock(self):
                pass
@@ -230,20 +211,20 @@ Then seek to the end, press OK, select 'end cut'. That's it.
        def setType(self, index, type):
                if len(self.cut_list):
                        self.cut_list[index] = (self.cut_list[index][0], type)
-                       self["Cutlist"].setIndex(index, CutListEntry(*self.cut_list[index]))
+                       self["cutlist"].modifyEntry(index, CutListEntry(*self.cut_list[index]))
 
        def setIn(self):
-               m = self["Cutlist"].getCurrentIndex()
+               m = self["cutlist"].getIndex()
                self.setType(m, 0)
                self.uploadCuesheet()
 
        def setOut(self):
-               m = self["Cutlist"].getCurrentIndex()
+               m = self["cutlist"].getIndex()
                self.setType(m, 1)
                self.uploadCuesheet()
 
        def setMark(self):
-               m = self["Cutlist"].getCurrentIndex()
+               m = self["cutlist"].getIndex()
                self.setType(m, 2)
                self.uploadCuesheet()
 
@@ -251,13 +232,12 @@ Then seek to the end, press OK, select 'end cut'. That's it.
                self.toggleMark(onlyadd=True, tolerance=90000) # do not allow two marks in <1s
 
        def __removeMark(self):
-               m = self["Cutlist"].getCurrent()
+               m = self["cutlist"].getCurrent()
                m = m and m[0]
                if m is not None:
                        self.removeMark(m)
 
        def exit(self):
-               self.session.nav.playService(self.old_service)
                self.close()
 
        def getCutlist(self):
@@ -267,44 +247,46 @@ Then seek to the end, press OK, select 'end cut'. That's it.
                return r
 
        def selectionChanged(self):
-               where = self["Cutlist"].getCurrent()
-               if where is None:
-                       print "no selection"
-                       return
-               pts = where[0][0]
-               seek = self.getSeek()
-               if seek is None:
-                       print "no seek"
-                       return
-               seek.seekTo(pts)
+               if not self.inhibit_seek:
+                       where = self["cutlist"].getCurrent()
+                       if where is None:
+                               print "no selection"
+                               return
+                       pts = where[0][0]
+                       seek = self.getSeek()
+                       if seek is None:
+                               print "no seek"
+                               return
+                       seek.seekTo(pts)
 
        def refillList(self):
                print "cue sheet changed, refilling"
                self.downloadCuesheet()
 
-               # get the first changed entry, and select it
+               # get the first changed entry, counted from the end, and select it
                new_list = self.getCutlist()
-               self["Cutlist"].setList(new_list)
+               self["cutlist"].list = new_list
 
-               for i in range(min(len(new_list), len(self.last_cuts))):
-                       if new_list[i] != self.last_cuts[i]:
-                               self["Cutlist"].setSelection(i)
+               l1 = len(new_list)
+               l2 = len(self.last_cuts)
+               for i in range(min(l1, l2)):
+                       if new_list[l1-i-1] != self.last_cuts[l2-i-1]:
+                               self["cutlist"].setIndex(l1-i-1)
                                break
                self.last_cuts = new_list
 
        def getStateForPosition(self, pos):
-               state = 0 # in
-
-               # when first point is "in", the beginning is "out"
-               if len(self.cut_list) and self.cut_list[0][1] == 0:
-                       state = 1
-
+               state = -1
                for (where, what) in self.cut_list:
-                       if where < pos:
-                               if what == 0: # in
-                                       state = 0
-                               elif what == 1: # out
+                       if what in [0, 1]:
+                               if where < pos:
+                                       state = what
+                               elif where == pos:
                                        state = 1
+                               elif state == -1:
+                                       state = 1 - what
+               if state == -1:
+                       state = 0
                return state
 
        def showMenu(self):
@@ -346,7 +328,7 @@ Then seek to the end, press OK, select 'end cut'. That's it.
                elif result == CutListContextMenu.RET_ENDCUT:
                        # remove in/out marks between the new cut
                        for (where, what) in self.cut_list[:]:
-                               if self.cut_start <= where <= self.context_position and what in [0,1]:
+                               if self.cut_start <= where <= self.context_position and what in (0,1):
                                        self.cut_list.remove((where, what))
 
                        bisect.insort(self.cut_list, (self.cut_start, 1))
@@ -358,11 +340,11 @@ Then seek to the end, press OK, select 'end cut'. That's it.
                        in_after = None
 
                        for (where, what) in self.cut_list:
-                               if what == 1 and where < self.context_position: # out
+                               if what == 1 and where <= self.context_position: # out
                                        out_before = (where, what)
                                elif what == 0 and where < self.context_position: # in, before out
                                        out_before = None
-                               elif what == 0 and where > self.context_position and in_after is None:
+                               elif what == 0 and where >= self.context_position and in_after is None:
                                        in_after = (where, what)
 
                        if out_before is not None:
@@ -370,30 +352,40 @@ Then seek to the end, press OK, select 'end cut'. That's it.
 
                        if in_after is not None:
                                self.cut_list.remove(in_after)
+                       self.inhibit_seek = True
                        self.uploadCuesheet()
+                       self.inhibit_seek = False
                elif result == CutListContextMenu.RET_MARK:
                        self.__addMark()
                elif result == CutListContextMenu.RET_DELETEMARK:
                        self.cut_list.remove(self.context_nearest_mark)
+                       self.inhibit_seek = True
                        self.uploadCuesheet()
+                       self.inhibit_seek = False
                elif result == CutListContextMenu.RET_REMOVEBEFORE:
                        # remove in/out marks before current position
                        for (where, what) in self.cut_list[:]:
-                               if where <= self.context_position and what in [0,1]:
+                               if where <= self.context_position and what in (0,1):
                                        self.cut_list.remove((where, what))
                        # add 'in' point
                        bisect.insort(self.cut_list, (self.context_position, 0))
+                       self.inhibit_seek = True
                        self.uploadCuesheet()
+                       self.inhibit_seek = False
                elif result == CutListContextMenu.RET_REMOVEAFTER:
                        # remove in/out marks after current position
                        for (where, what) in self.cut_list[:]:
-                               if where >= self.context_position and what in [0,1]:
+                               if where >= self.context_position and what in (0,1):
                                        self.cut_list.remove((where, what))
                        # add 'out' point
                        bisect.insort(self.cut_list, (self.context_position, 1))
+                       self.inhibit_seek = True
                        self.uploadCuesheet()
+                       self.inhibit_seek = False
+               elif result == CutListContextMenu.RET_GRABFRAME:
+                       self.grabFrame()
 
-       # we modify the "play" behaviour a bit:
+       # we modify the "play" behavior a bit:
        # if we press pause while being in slowmotion, we will pause (and not play)
        def playpauseService(self):
                if self.seekstate != self.SEEK_STATE_PLAY and not self.isStateSlowMotion(self.seekstate):
@@ -401,6 +393,14 @@ Then seek to the end, press OK, select 'end cut'. That's it.
                else:
                        self.pauseService()
 
+       def grabFrame(self):
+               path = self.session.nav.getCurrentlyPlayingServiceReference().getPath()
+               from Components.Console import Console
+               grabConsole = Console()
+               cmd = 'grab -vblpr%d "%s"' % (180, path.rsplit('.',1)[0] + ".png")
+               grabConsole.ePopen(cmd)
+               self.playpauseService()
+
 def main(session, service, **kwargs):
        session.open(CutListEditor, service)