Source code for VideoRecorder

"""
:filename:     SamplePlugin.py
:author:       roar@tordivel.no
:requirements: Scorpion 10.1.1.569 or higher

VideoRecorder - Scorpion plugin for creating video from Scorpion GUI

::

    1.0.0.15, 11nov2015, RL: modified for autodoc
    1.0.0.14, 25jan2014, roar@tordivel.no
      format combobox accepts any text (FOURCC)
    1.0.0.13, 24jan2014, roar@tordivel.no
      added video format
    1.0.0.12, 17jan2014, RL:
      added hints and adjusted control position in config dialog
    1.0.0.11, 16jan2014, RL:
      added start recording on start option
    1.0.0.10, 18apr2013, RL:
      new filename <prefix>_<YYYYMMDD>_<nnnnnn>.avi
    1.0.0.9, 10oct2012, RL:
      using icon stripes
      added allowOnlineRecording
    1.0.0.8, 08oct2012, TV:
      new metro icons
    1.0.0.7, 06sep2012, RL:
      fixed video source mapping when creating video
      fixed button hints
      new icons
    1.0.0.6, 04sep2012, RL:
      uses <Storage>\Video folder
      added filename prefix
      changed button order/captions/hint
    1.0.0.5, 30aug2012, RL:
      added simulation button
    1.0.0.4, 30aug2012, RL:
      using SpeedButtons with graphis
      added configuration dialog
    1.0.0.3, 21aug2012, RL:
      layout improvements
    1.0.0.2, 10aug2012, RL:
      resizes controls
      added autoStart flag
      added start/stopRecording methods
      added shortcut
      added configure method
    1.0.0.1, 02jul2012, RL:
      created
"""

__version__ = '1.0.0.15'

import os
import datetime
from Scorpion import RegisterCallback,UnregisterCallback,GetCameraImages,FindFiles,CreateVideoWriter,PluginChanged
from Scorpion import GetStringValue,GetBoolValue,GetControlByHandle,CreateControlVideoWriter,ExecuteCmd,CreateOKCancelDialog

VIDEOSOURCES    = ['Image pane','Left pane','Right pane','Application']  #GUI names
INTERNALSOURCES = ['ImageView' ,'LeftPane' ,'RightPane' ,'']             #Scorpion internal names
VIDEOFORMATS    = ['DIB','CVID','MSVC','IYUV']                           #known available formats

#-----------------------------------------------------------------------------
# VideoRecorderPlugin configuration dilaog
#-----------------------------------------------------------------------------
[docs]def ConfigureVideoRecorderPlugin(videoRecorderPlugin): ''' show up the setup dialog ''' dlg=CreateOKCancelDialog('dlg') dlg.caption='Video setup' dlg.clientWidth=350 gbSetup=dlg.addControl('GroupBox',2,0) gbSetup.caption='Video generation' gbSetup.width=dlg.clientWidth-75-16 #buttons are 75 wide gbSetup.height=dlg.clientHeight-4 lblSource=gbSetup.addControl('Label',6,16) lblSource.caption='Source' cbSource=gbSetup.addControl('ComboBox',100,lblSource.top-4) cbSource.style=2 cbSource.width=gbSetup.clientWidth-cbSource.left-6 cbSource.items=tuple(list(GetCameraImages().names)+VIDEOSOURCES) cbSource.selItem=videoRecorderPlugin.source lblPrefix=gbSetup.addControl('Label',lblSource.left,cbSource.bottom+7) lblPrefix.caption='Filename prefix' ePrefix=gbSetup.addControl('Edit',cbSource.left,lblPrefix.top-4) ePrefix.width=gbSetup.clientWidth-cbSource.left-6 ePrefix.text=videoRecorderPlugin.prefix lblFps=gbSetup.addControl('Label',lblPrefix.left,ePrefix.bottom+7) lblFps.caption='Framerate [fps]' eFps=gbSetup.addControl('Edit',cbSource.left,lblFps.top-4) eFps.width=cbSource.width eFps.text=str(videoRecorderPlugin.fps) lblFmt=gbSetup.addControl('Label',lblSource.left,eFps.bottom+7) lblFmt.caption='Format [FOURCC]' cbFmt=gbSetup.addControl('ComboBox',cbSource.left,lblFmt.top-4) cbFmt.style=0 cbFmt.width=cbSource.width cbFmt.items=tuple(VIDEOFORMATS) cbFmt.hint='video codec - must be supported on computer' cbFmt.showHint=True cbFmt.text=videoRecorderPlugin.format lblShortcut=gbSetup.addControl('Label',lblSource.left,cbFmt.bottom+7) lblShortcut.caption='Shortcut' cbShortcut=gbSetup.addControl('ComboBox',cbFmt.left,lblShortcut.top-4) cbShortcut.style=2 cbShortcut.width=cbSource.width cbShortcut.items=('None','F1','F2','F3','F4','F5','F6','F7','F8','F9','F10','F11','F12') cbShortcut.hint='start/stop recording shortcut' cbShortcut.showHint=True if videoRecorderPlugin.shortcut=='': cbShortcut.itemIndex=0 else: cbShortcut.selItem=videoRecorderPlugin.shortcut cbAllowOnlineRecording=gbSetup.addControl('CheckBox',lblSource.left,cbShortcut.bottom+7) cbAllowOnlineRecording.caption='Allow online recording' cbAllowOnlineRecording.hint='allows recording when offline only' cbAllowOnlineRecording.showHint=True cbAllowOnlineRecording.right=cbShortcut.right cbAllowOnlineRecording.checked=videoRecorderPlugin.allowOnlineRecording cbAutostart=gbSetup.addControl('CheckBox',lblSource.left,cbAllowOnlineRecording.bottom) cbAutostart.caption='Autostart on start recording' cbAutostart.hint='forces start when recording is pressed' cbAutostart.showHint=True cbAutostart.right=cbShortcut.right cbAutostart.checked=videoRecorderPlugin.autostart cbAutorecord=gbSetup.addControl('CheckBox',lblSource.left,cbAutostart.bottom) cbAutorecord.caption='Start recording on start' cbAutorecord.hint='starts recording when system starts' cbAutorecord.showHint=True cbAutorecord.right=cbShortcut.right cbAutorecord.checked=videoRecorderPlugin.autorecord gbSetup.clientHeight=cbAutorecord.bottom+6 dlg.clientHeight=gbSetup.height+2 if dlg.showModal()==1: videoRecorderPlugin.source=cbSource.selItem videoRecorderPlugin.prefix=ePrefix.text videoRecorderPlugin.fps=float(eFps.text) videoRecorderPlugin.format=cbFmt.text if cbShortcut.itemIndex==0: videoRecorderPlugin.shortcut='' else: videoRecorderPlugin.shortcut=cbShortcut.selItem videoRecorderPlugin.allowOnlineRecording=cbAllowOnlineRecording.checked videoRecorderPlugin.autostart=cbAutostart.checked videoRecorderPlugin.autorecord=cbAutorecord.checked return True return False #----------------------------------------------------------------------------- # VideoRecorderPlugin implementation #-----------------------------------------------------------------------------
[docs]class VideoRecorderPlugin(object): def __init__(self,cntr,name): def addbttn(caption,hint,filename,onClick): bttn=self.cntr.addControl('SpeedButton',10,6) bttn.caption=caption bttn.font.size=self.cntr.font.size bttn.layout=2 bttn.height=self.bttnsize bttn.width=self.bttnsize bttn.flat=True bttn.glyphName=os.path.join(os.path.split(__file__)[0],filename) bttn.numGlyphs=4 bttn.hint=hint bttn.showHint=True bttn.onClick=onClick self.bttns.append(bttn) return bttn cntr.deleteControls() #delete previous added controls if any src=list(GetCameraImages().names)+VIDEOSOURCES #get available sources self.iname=cntr.iname #full path name of control self.name=name #this plugins name self.cntr=cntr #keep reference to container self.cntr.onResize=self.cntrResize #hook up on size events self.bttnsize=60 #default button size self.source=src[0] #defult source self.fps=1 #default framerate self.format=VIDEOFORMATS[0] #default video format self.autostart=True #flag to start/stop on record bttn self.autorecord=False #flag to start/stop record on start/stop self.shortcut='' #may contain F1..F12 self.prefix='Video' #filename prefix self.filename='' #current filename self.frameCount=0 #current frames self.allowOnlineRecording=True #enables/disables recording due to self.writer=None #the videowriter object self.bttns=[] #list of all buttons self.folder=os.path.join(GetStringValue('System.Storage'),'Video') if not os.path.exists(self.folder):os.makedirs(self.folder) self.bRecord = addbttn('Record', 'start/stop recording', 'RecordImageStrip.bmp', self.bRecordClick) self.bPlay = addbttn('Play Video', 'play last recorded video', 'PlayImageStrip.bmp', self.bPlayClick) self.bExplore = addbttn('Video Folder', 'open video folder', 'FolderImageStrip.bmp', self.bExploreClick) self.bSequence = addbttn('Buffer', 'select file buffer', 'SelectImageStrip.bmp', self.bSequenceClick) self.bReset = addbttn('Reset', 'reset file buffer', 'SkipBackwardImageStrip.bmp', self.bResetClick) self.bSimulation = addbttn('Offline', 'toggle online/offline ', 'SnapshotImageStrip.bmp', self.bSimulationClick) self.bRecord.groupIndex=1 #to enable up/down self.bRecord.allowAllUp=True #to enable toggling of single button self.bSimulation.groupIndex=2 #to enable up/down self.bSimulation.allowAllUp=True #to enable toggling of single button self.miConfig=self.cntr.addMenuItem('Configure') #add configuration popup menu self.miConfig.onClick=self.miConfigClick self.lblState=self.cntr.addControl('Label',0,self.bRecord.bottom+6) self.lblState.caption='' self.lblState.autosize=False self.lblState.alignment=2 RegisterCallback('Actions.BeforeStart',self.actionsBeforeStart) RegisterCallback('Actions.AfterStop',self.actionsAfterStop) RegisterCallback('Actions.AfterInspect',self.actionsAfterInspect) RegisterCallback('System.SimulationChanged',self.systemSimulationChanged) RegisterCallback('System.AccessControlChanged',self.systemAccessControlChanged) self.registerShortcut() self.cntrResize(None,None) self.enableControls()
[docs] def getConfig(self): ''' returns plugin configuration as string ''' from SPB import CreateSpb spb=CreateSpb() #create a spb instance, populate with the plugin configuration #and return the xml string spb.setText('type',self.__class__.__name__) #configuration type spb.setInt('version',6) #current config version spb.setText('source',self.source) spb.setText('prefix',self.prefix) spb.setFloat('fps',self.fps) spb.setText('format',self.format) spb.setBool('autostart',self.autostart) spb.setBool('autorecord',self.autorecord) spb.setText('shortcut',self.shortcut) spb.setBool('allowOnlineRec',self.allowOnlineRecording) return spb.xml
[docs] def setConfig(self,value): ''' set plugin configuration from the string 'value' ''' from SPB import CreateSpb spb=CreateSpb(value) #extract plugin configuration from the spb object if spb.getText('type')==self.__class__.__name__: if spb.getInt('version')>0: self.source=spb.getText('source') self.fps=spb.getFloat('fps') if spb.getInt('version')>1: self.autostart=spb.getBool('autostart') self.shortcut=spb.getText('shortcut') if spb.getInt('version')>2: self.format=spb.getText('format') if self.format=='RAW':self.format='DIB' #for compatibilty if spb.getInt('version')>3: self.prefix=spb.getText('prefix') if spb.getInt('version')>4: self.allowOnlineRecording=spb.getBool('allowOnlineRec') if spb.getInt('version')>5: self.autorecord=spb.getBool('autorecord') self.registerShortcut() self.enableControls()
[docs] def configure(self): ''' launch an input dialog for editing tool ''' if ConfigureVideoRecorderPlugin(self): self.registerShortcut() PluginChanged(self)
[docs] def enableControls(self): ''' enable controls due to operation state ''' running=GetBoolValue('System.Running') service=GetBoolValue('System.Service') or GetBoolValue('System.Settings') simulation=GetBoolValue('System.CameraSimulation') self.bRecord.enabled=self.allowOnlineRecording or simulation self.bRecord.down=self.writer<>None self.bPlay.enabled=not running and os.path.exists(self.filename) self.bReset.enabled=simulation self.bSequence.enabled=not running self.bSimulation.enabled=not running self.bSimulation.down=simulation self.bExplore.enabled=not running and os.path.exists(self.folder) self.miConfig.enabled=service self.miConfig.visible=service
[docs] def registerShortcut(self): ''' register for shortcut events ''' for i in range(1,13): UnregisterCallback('Actions.F%i'%i,self.actionsFn) if self.shortcut=='F%i'%i: RegisterCallback('Actions.F%i'%i,self.actionsFn)
[docs] def getNextFileno(self,path,prefix,cnt): ''' return unique filename by adding trailing integer ''' lst=FindFiles(os.path.join(path,'%s%s.avi'%(prefix,'?'*cnt))) if len(lst): s=os.path.splitext(lst[-1])[0] #get filename without ext return int(s[len(prefix):len(s)])+1 #remove prefix and return trailing no+1 return 1
[docs] def getNextFilename(self): ''' return next unique filename ''' path=os.path.join(GetStringValue('System.Storage'),'Video') prefix='%s_%s_'%(self.prefix,datetime.date.today().strftime('%Y%m%d')) return os.path.join(path,'%s%06i.avi'%(prefix,self.getNextFileno(path,prefix,6)))
[docs] def startRecording(self): ''' start recording if not recording ''' if self.writer==None: if self.source==VIDEOSOURCES[0]: src=INTERNALSOURCES[0] elif self.source==VIDEOSOURCES[1]: src=INTERNALSOURCES[1] elif self.source==VIDEOSOURCES[2]: src=INTERNALSOURCES[2] elif self.source==VIDEOSOURCES[3]: src=INTERNALSOURCES[3] else: src=self.source self.writer=CreateControlVideoWriter(src) self.filename=self.getNextFilename() print 'filename',self.filename,self.format period=int(round(1000/self.fps)) if os.path.exists(self.filename):os.remove(self.filename) if self.writer.open(self.filename,period,self.format): self.lblState.caption='%s - %i'%(os.path.relpath(self.filename,self.folder),self.writer.frameCount) self.bRecord.down=True else: self.stopRecording() self.lblState.caption='Error opening %s'%(os.path.relpath(self.filename,self.folder)) self.enableControls() return self.writer<>None
[docs] def stopRecording(self): ''' stop recording if active ''' if self.writer: self.bRecord.down=False self.lblState.caption='%s - %i frames recorded'%(os.path.relpath(self.filename,self.folder),self.writer.frameCount) self.writer.close() self.writer=None self.enableControls() return self.writer==None
[docs] def playVideo(self,filename): ''' play video ''' os.startfile(filename,'open')
[docs] def playLastVideo(self): ''' play last video ''' self.playVideo(self.filename)
[docs] def selectSequence(self): ''' select sequence ''' GetCameraImages().configure(2) self.enableControls()
[docs] def reset(self): ''' reset sequence ''' GetCameraImages().resetFilenames()
[docs] def explore(self): ''' launch explorer in video folder ''' os.startfile(self.folder,'open')
[docs] def cntrResize(self,sender,args): ''' center buttons in panel ''' l=int(self.cntr.clientWidth/2-((len(self.bttns)*(self.bttns[0].width)+(len(self.bttns)-1)*4))/2) for b in self.bttns: b.left=l l=b.right+4 self.lblState.width=self.cntr.clientWidth
[docs] def bSequenceClick(self,sender,args): ''' select guard tagname ''' self.selectSequence()
[docs] def bResetClick(self,sender,args): ''' reset sequence ''' self.reset()
[docs] def bRecordClick(self,sender,args): ''' start/stop recording ''' if self.writer==None: if self.startRecording() and self.autostart: ExecuteCmd('start','') elif self.writer: if self.stopRecording() and self.autostart: ExecuteCmd('stop','')
[docs] def bPlayClick(self,sender,args): ''' view last video ''' self.playLastVideo()
[docs] def bSimulationClick(self,sender,args): ''' toggle simulation ''' GetCameraImages().simulation=not GetCameraImages().simulation self.enableControls()
[docs] def bExploreClick(self,sender,args): ''' launch explorer in video folder ''' self.explore()
[docs] def miConfigClick(self,sender,args): ''' launch configuration dialog ''' if GetBoolValue('System.Service') or GetBoolValue('System.Settings'): self.configure()
[docs] def systemAccessControlChanged(self,settings,service): ''' update rights ''' self.enableControls()
[docs] def actionsBeforeStart(self): ''' start ''' if self.autorecord: self.startRecording() self.enableControls()
[docs] def actionsAfterStop(self): ''' end of video if recording ''' self.stopRecording()
[docs] def actionsAfterInspect(self): ''' add image to video ''' if GetBoolValue('System.Running') and self.writer: if self.writer.addFrame(): self.lblState.caption='%s - %d'%(os.path.relpath(self.filename,self.folder),self.writer.frameCount) else: self.lblState.caption='addFrame failed'
[docs] def systemSimulationChanged(self,simulation,folder): ''' ''' self.enableControls()
[docs] def actionsFn(self): ''' start/stop on shortcut ''' if self.writer==None: self.startRecording() else: self.stopRecording()
[docs]def CreatePlugin(hWnd, name=''): ''' Scorpion Plugin Stub - Required ''' cntr=GetControlByHandle(hWnd) return VideoRecorderPlugin(cntr,name)