def loadMovie(self, filename, log=True): """Load a movie from file :Parameters: filename: string The name of the file, including path if necessary After the file is loaded MovieStim.duration is updated with the movie duration (in seconds). """ self._unload() self._reset() if self._no_audio is False: self._createAudioStream() # Create Video Stream stuff self._video_stream = cv2.VideoCapture() self._video_stream.open(filename) if not self._video_stream.isOpened(): raise RuntimeError("Error when reading image file") self._total_frame_count = self._video_stream.get( cv2.cv.CV_CAP_PROP_FRAME_COUNT) self._video_width = self._video_stream.get( cv2.cv.CV_CAP_PROP_FRAME_WIDTH) self._video_height = self._video_stream.get( cv2.cv.CV_CAP_PROP_FRAME_HEIGHT) self._format = self._video_stream.get(cv2.cv.CV_CAP_PROP_FORMAT) # TODO: Read depth from video source self._video_frame_depth = 3 if self._no_audio: self._video_frame_rate = self._requested_fps else: self._video_frame_rate = self._video_stream.get( cv2.cv.CV_CAP_PROP_FPS) self._inter_frame_interval = 1.0 / self._video_frame_rate # Create a numpy array that can hold one video frame, as returned by cv2. self._numpy_frame = numpy.zeros( (self._video_height, self._video_width, self._video_frame_depth), dtype=numpy.uint8) # Uses a preallocated numpy array as the pyglet ImageData data self._frame_data_interface = ArrayInterfaceImage(self._numpy_frame, allow_copy=False, rectangle=True, force_rectangle=True) #frame texture; transformed so it looks right in psychopy self._frame_texture = self._frame_data_interface.texture.get_transform( flip_x=not self.flipHoriz, flip_y=not self.flipVert) self.duration = self._total_frame_count * self._inter_frame_interval self.status = NOT_STARTED self.filename = filename logAttrib(self, log, 'movie', filename)
def loadMovie(self, filename, log=True): """Load a movie from file :Parameters: filename: string The name of the file, including path if necessary After the file is loaded MovieStim.duration is updated with the movie duration (in seconds). """ self._unload() self._reset() if self._no_audio is False: self._createAudioStream() # Create Video Stream stuff self._video_stream = cv2.VideoCapture() self._video_stream.open(filename) if not self._video_stream.isOpened(): raise RuntimeError( "Error when reading image file") self._total_frame_count = self._video_stream.get(cv2.cv.CV_CAP_PROP_FRAME_COUNT) self._video_width = self._video_stream.get(cv2.cv.CV_CAP_PROP_FRAME_WIDTH) self._video_height = self._video_stream.get(cv2.cv.CV_CAP_PROP_FRAME_HEIGHT) self._format = self._video_stream.get(cv2.cv.CV_CAP_PROP_FORMAT) # TODO: Read depth from video source self._video_frame_depth = 3 if self._no_audio: self._video_frame_rate = self._requested_fps else: self._video_frame_rate = self._video_stream.get(cv2.cv.CV_CAP_PROP_FPS) self._inter_frame_interval = 1.0/self._video_frame_rate # Create a numpy array that can hold one video frame, as returned by cv2. self._numpy_frame = numpy.zeros((self._video_height, self._video_width, self._video_frame_depth), dtype=numpy.uint8) # Uses a preallocated numpy array as the pyglet ImageData data self._frame_data_interface = ArrayInterfaceImage(self._numpy_frame, allow_copy=False, rectangle=True, force_rectangle=True) #frame texture; transformed so it looks right in psychopy self._frame_texture = self._frame_data_interface.texture.get_transform(flip_x=not self.flipHoriz, flip_y=not self.flipVert) self.duration = self._total_frame_count * self._inter_frame_interval self.status = NOT_STARTED self.filename = filename logAttrib(self, log, 'movie', filename)
class MovieStim2(BaseVisualStim, ContainerMixin): """A stimulus class for playing movies (mpeg, avi, etc...) in PsychoPy that does not require avbin. Instead it requires the cv2 python package for OpenCV. The VLC media player also needs to be installed on the psychopy computer. **Example**:: See Movie2Stim.py for demo. """ def __init__(self, win, filename="", units='pix', size=None, pos=(0.0,0.0), ori=0.0, flipVert=False, flipHoriz=False, color=(1.0,1.0,1.0), colorSpace='rgb', opacity=1.0, volume=1.0, name='', loop=False, autoLog=True, depth=0.0, noAudio=False, vframe_callback=None, fps=None ): """ :Parameters: filename : a string giving the relative or absolute path to the movie. flipVert : True or *False* If True then the movie will be top-bottom flipped flipHoriz : True or *False* If True then the movie will be right-left flipped volume : The nominal level is 100, and 0 is silence. loop : bool, optional Whether to start the movie over from the beginning if draw is called and the movie is done. """ # what local vars are defined (these are the init params) for use # by __repr__ self._initParams = dir() self._initParams.remove('self') super(MovieStim2, self).__init__(win, units=units, name=name, autoLog=False) #check for pyglet if win.winType != 'pyglet': logging.error('Movie stimuli can only be used with a pyglet window') core.quit() self._retracerate = win._monitorFrameRate if self._retracerate is None: self._retracerate = win.getActualFrameRate() if self._retracerate is None: logging.warning("FrameRate could not be supplied by psychopy; defaulting to 60.0") self._retracerate = 60.0 self.filename = filename self.loop = loop self.flipVert = flipVert self.flipHoriz = flipHoriz self.pos = numpy.asarray(pos, float) self.depth = depth self.opacity = float(opacity) self.volume = volume self._av_stream_time_offset = 0.145 self._no_audio = noAudio self._requested_fps = fps self._vframe_callback = vframe_callback self._reset() self.loadMovie(self.filename) self.setVolume(volume) self.nDroppedFrames = 0 self.aspectRatio = self._video_width/float(self._video_height) #size if size is None: self.size = numpy.array([self._video_width, self._video_height], float) elif isinstance(size, (int, float, long)): # treat size as desired width, and calc a height # that maintains the aspect ratio of the video. self.size = numpy.array([size, size/self.aspectRatio], float) else: self.size = val2array(size) self.ori = ori self._updateVertices() #set autoLog (now that params have been initialised) self.autoLog = autoLog if autoLog: logging.exp("Created %s = %s" %(self.name, str(self))) def _reset(self): self.duration = None self.status = NOT_STARTED self._numpy_frame = None self._frame_texture = None self._frame_data_interface = None self._video_stream = None self._total_frame_count = None self._video_width = None self._video_height = None # TODO: Read depth from video source self._video_frame_depth = 3 self._video_frame_rate = None self._inter_frame_interval = None self._prev_frame_sec = None self._next_frame_sec = None self._next_frame_index = None self._prev_frame_index = None self._video_perc_done = None self._last_video_flip_time = None self._next_frame_displayed = False self._video_track_clock = Clock() self._audio_stream_clock=Clock() self._vlc_instance = None self._audio_stream = None self._audio_stream_player = None self._audio_stream_started = False self._audio_stream_event_manager=None def setMovie(self, filename, log=True): """See `~MovieStim.loadMovie` (the functions are identical). This form is provided for syntactic consistency with other visual stimuli. """ self.loadMovie(filename, log=log) def loadMovie(self, filename, log=True): """Load a movie from file :Parameters: filename: string The name of the file, including path if necessary After the file is loaded MovieStim.duration is updated with the movie duration (in seconds). """ self._unload() self._reset() if self._no_audio is False: self._createAudioStream() # Create Video Stream stuff self._video_stream = cv2.VideoCapture() self._video_stream.open(filename) if not self._video_stream.isOpened(): raise RuntimeError( "Error when reading image file") self._total_frame_count = self._video_stream.get(cv2.cv.CV_CAP_PROP_FRAME_COUNT) self._video_width = self._video_stream.get(cv2.cv.CV_CAP_PROP_FRAME_WIDTH) self._video_height = self._video_stream.get(cv2.cv.CV_CAP_PROP_FRAME_HEIGHT) self._format = self._video_stream.get(cv2.cv.CV_CAP_PROP_FORMAT) # TODO: Read depth from video source self._video_frame_depth = 3 cv_fps = self._video_stream.get(cv2.cv.CV_CAP_PROP_FPS) if self._requested_fps: if self._no_audio is False and cv_fps != self._requested_fps: self._no_audio = True logging.error("MovieStim2 video fps != requested fps. Disabling Audio Stream.") logging.flush() if self._no_audio and self._requested_fps: self._video_frame_rate = self._requested_fps else: self._video_frame_rate = self._video_stream.get(cv2.cv.CV_CAP_PROP_FPS) self._inter_frame_interval = 1.0/self._video_frame_rate # Create a numpy array that can hold one video frame, as returned by cv2. self._numpy_frame = numpy.zeros((self._video_height, self._video_width, self._video_frame_depth), dtype=numpy.uint8) # Uses a preallocated numpy array as the pyglet ImageData data self._frame_data_interface = ArrayInterfaceImage(self._numpy_frame, allow_copy=False, rectangle=True, force_rectangle=True) #frame texture; transformed so it looks right in psychopy self._frame_texture = self._frame_data_interface.texture.get_transform(flip_x=not self.flipHoriz, flip_y=not self.flipVert) self.duration = self._total_frame_count * self._inter_frame_interval self.status = NOT_STARTED self.filename = filename logAttrib(self, log, 'movie', filename) def _createAudioStream(self): """ Create the audio stream player for the video using pyvlc. """ if not os.access(self.filename, os.R_OK): raise RuntimeError('Error: %s file not readable' % self.filename) self._vlc_instance = vlc.Instance('--novideo') try: self._audio_stream = self._vlc_instance.media_new(self.filename) except NameError: raise ImportError('NameError: %s vs LibVLC %s' % (vlc.__version__, vlc.libvlc_get_version())) self._audio_stream_player = self._vlc_instance.media_player_new() self._audio_stream_player.set_media(self._audio_stream) self._audio_stream_event_manager = self._audio_stream_player.event_manager() self._audio_stream_event_manager.event_attach(vlc.EventType.MediaPlayerTimeChanged, self._audio_time_callback, self._audio_stream_player) self._audio_stream_event_manager.event_attach(vlc.EventType.MediaPlayerEndReached, self._audio_end_callback) def _releaseeAudioStream(self): if self._audio_stream_player: self._audio_stream_player.stop() if self._audio_stream_event_manager: self._audio_stream_event_manager.event_detach(vlc.EventType.MediaPlayerTimeChanged) self._audio_stream_event_manager.event_detach(vlc.EventType.MediaPlayerEndReached) if self._audio_stream: self._audio_stream.release() if self._vlc_instance: self._vlc_instance.vlm_release() self._vlc_instance.release() self._audio_stream = None self._audio_stream_event_manager = None self._audio_stream_player = None self._vlc_instance = None def _flipCallback(self): import inspect flip_time = inspect.currentframe().f_back.f_locals.get('now') if PRINT_FRAME_FLIP_TIMES: if self._last_video_flip_time is None: self._last_video_flip_time=flip_time print 'Frame %d\t%.4f\t%.4f'%(self.getCurrentFrameIndex(), flip_time, flip_time-self._last_video_flip_time) if flip_time is None: raise RuntimeError("Movie2._flipCallback: Can not access the currect flip time.") self._last_video_flip_time = flip_time self._next_frame_displayed = True def play(self, log=True): """Continue a paused movie from current position. """ cstat = self.status if cstat != PLAYING: self.status = PLAYING if self._next_frame_sec is None: # movie has no current position, need to reset the clock # to zero in order to have the timing logic work # otherwise the video stream would skip frames until the # time since creating the movie object has passed self._video_track_clock.reset() if cstat == PAUSED: # toggle audio pause if self._audio_stream_player: self._audio_stream_player.pause() self._audio_stream_clock.reset(-self._audio_stream_player.get_time()/1000.0) if self._next_frame_sec: self._video_track_clock.reset(-self._next_frame_sec) else: nt = self._getNextFrame() self._video_track_clock.reset(-nt) if log and self.autoLog: self.win.logOnFlip("Set %s playing" %(self.name), level=logging.EXP, obj=self) self._updateFrameTexture() self.win.callOnFlip(self._flipCallback) return self._next_frame_index def pause(self, log=True): """ Pause the current point in the movie (sound will stop, current frame will not advance). If play() is called again both will restart. """ if self.status == PLAYING: self.status = PAUSED if self._audio_stream_player and self._audio_stream_player.can_pause(): self._audio_stream_player.pause() if log and self.autoLog: self.win.logOnFlip("Set %s paused" %(self.name), level=logging.EXP, obj=self) return True if log and self.autoLog: self.win.logOnFlip("Failed Set %s paused" %(self.name), level=logging.EXP, obj=self) return False def stop(self, log=True): """ Stop the current point in the movie (sound will stop, current frame will not advance). Once stopped the movie cannot be restarted - it must be loaded again. Use pause() if you may need to restart the movie. """ if self.status != STOPPED: self.status = STOPPED self._unload() self._reset() if log and self.autoLog: self.win.logOnFlip("Set %s stopped" %(self.name), level=logging.EXP,obj=self) def seek(self, timestamp, log=True): """ Seek to a particular timestamp in the movie. """ if self.status in [PLAYING, PAUSED]: if timestamp > 0.0: if self.status == PLAYING: self.pause() if self._audio_stream_player and self._audio_stream_player.is_seekable(): self._audio_stream_player.set_time(int(timestamp*1000.0)) self._audio_stream_clock.reset(-timestamp) self._video_stream.set(cv2.cv.CV_CAP_PROP_POS_MSEC, timestamp*1000.0) self._video_track_clock.reset(-timestamp) self._next_frame_index = self._video_stream.get(cv2.cv.CV_CAP_PROP_POS_FRAMES) self._next_frame_sec = self._video_stream.get(cv2.cv.CV_CAP_PROP_POS_MSEC)/1000.0 else: self.stop() self.loadMovie(self.filename) if log: logAttrib(self, log, 'seek', timestamp) self.play() def setFlipHoriz(self, newVal=True, log=True): """If set to True then the movie will be flipped horizontally (left-to-right). Note that this is relative to the original, not relative to the current state. """ self.flipHoriz = newVal logAttrib(self, log, 'flipHoriz') def setFlipVert(self, newVal=True, log=True): """If set to True then the movie will be flipped vertically (top-to-bottom). Note that this is relative to the original, not relative to the current state. """ self.flipVert = not newVal logAttrib(self, log, 'flipVert') def setVolume(self, v): """ Set the audio track volume. 0 = mute, 100 = 0 dB. float values between 0.0 and 1.0 are also accepted, and scaled to an int between 0 and 100. """ if self._audio_stream_player: if 0.0 <= v <= 1.0 and isinstance(v, (float,)): v = int(v*100) else: v = int(v) self.volume = v self._audio_stream_player.audio_set_volume(v) def getVolume(self): """ Returns the current movie audio volume. 0 is no audio, 100 is max audio volume. """ if self._audio_stream_player: self.volume = self._audio_stream_player.audio_get_volume() return self.volume def getFPS(self): """ Returns the movie frames per second playback speed. """ return self._video_frame_rate def setFPS(self, fps): """ If the movie was created with noAudio = True kwarg, then the movie playback speed can be changed from the original frame rate. For example, if the movie being played has 30 fps and you would like to play it at 2x normal speed, setFPS(60) will do that. """ if self._no_audio: self._requested_fps = fps self._video_frame_rate = fps self._inter_frame_interval = 1.0/self._video_frame_rate return raise ValueError("Error calling movie.setFPS(): MovieStim must be created with kwarg noAudio=True.") def getTimeToNextFrameDraw(self): """ Get the number of sec.msec remaining until the next movie video frame should be drawn. """ # rt = (self._next_frame_sec - 1.0/self._retracerate) - self._video_track_clock.getTime() try: rt = (self._next_frame_sec - 1.0/self._retracerate) - self._video_track_clock.getTime() #print "getTimeToNextFrameDraw: ",self.getCurrentFrameNumber(), rt return rt except: #import traceback #traceback.print_exc() logging.WARNING("MovieStim2.getTimeToNextFrameDraw failed.") return 0.0 def shouldDrawVideoFrame(self): """ True if the next movie frame should be drawn, False if it is not yet time. See getTimeToNextFrameDraw(). """ return self.getTimeToNextFrameDraw() <= 0.0 def getCurrentFrameNumber(self): """ Get the current movie frame number. The first frame number in a file is 1. """ return self._next_frame_index def getCurrentFrameTime(self): """ Get the time that the movie file specified the current video frame as having. """ return self._next_frame_sec def getPercentageComplete(self): """ Provides a value between 0.0 and 100.0, indicating the amount of the movie that has been already played. """ return self._video_perc_done def isCurrentFrameVisible(self): """ The current video frame goes through two stages; the first being when the movie frame is being loaded, but is not visible on the display. The second is when the frame has actually been presented on the display. Returns False if the frame is in the first stage, True when in stage 2. """ return self._next_frame_displayed def _getNextFrame(self): # get next frame info ( do not decode frame yet) while self.status == PLAYING: if self._video_stream.grab(): self._prev_frame_index = self._next_frame_index self._prev_frame_sec = self._next_frame_sec self._next_frame_index = self._video_stream.get(cv2.cv.CV_CAP_PROP_POS_FRAMES) if self._requested_fps and self._no_audio: self._next_frame_sec = self._next_frame_index/self._requested_fps#*self._video_stream.get(cv2.cv.CV_CAP_PROP_POS_MSEC)/1000.0 else: self._next_frame_sec = self._video_stream.get(cv2.cv.CV_CAP_PROP_POS_MSEC)/1000.0 self._video_perc_done = self._video_stream.get(cv2.cv.CV_CAP_PROP_POS_AVI_RATIO) self._next_frame_displayed = False if self.getTimeToNextFrameDraw() > -self._inter_frame_interval/2.0: return self._next_frame_sec else: self.nDroppedFrames += 1 if self.nDroppedFrames < reportNDroppedFrames: logging.warning("MovieStim2 dropping video frame index: %d"%(self._next_frame_index)) elif self.nDroppedFrames == reportNDroppedFrames: logging.warning("Multiple Movie frames have " "occurred - I'll stop bothering you " "about them!") else: self._onEos() break def _updateFrameTexture(self): # decode frame into np array and move to opengl tex ret, f = self._video_stream.retrieve() if ret: frame_array = cv2.cvtColor(f, cv2.COLOR_BGR2RGB) if callable(self._vframe_callback): try: frame_array = self._vframe_callback(self._next_frame_index, frame_array) except: print "MovieStim2 Error: vframe_callback raised an exception. Using original frame data." import traceback traceback.print_exc() #self._numpy_frame[:] = f[...,::-1] numpy.copyto(self._numpy_frame, frame_array) self._frame_data_interface.dirty() else: raise RuntimeError("Could not load video frame data.") def _getVideoAudioTimeDiff(self): if self._audio_stream_started is False: return 0 return self.getCurrentFrameTime()-self._getAudioStreamTime() def draw(self, win=None): """ Draw the current frame to a particular visual.Window (or to the default win for this object if not specified). The current position in the movie will be determined automatically. This method should be called on every frame that the movie is meant to appear""" if self.status==NOT_STARTED or (self.status==FINISHED and self.loop): self.play() elif self.status == FINISHED and not self.loop: return return_next_frame_index = False if win is None: win = self.win self._selectWindow(win) if self._no_audio is False and not self._audio_stream_started and self._video_track_clock.getTime() >= self._av_stream_time_offset: self._startAudio() if self._next_frame_displayed: if self._getVideoAudioTimeDiff() > self._inter_frame_interval: self._video_track_clock.reset(-self._next_frame_sec) else: self._getNextFrame() if self.shouldDrawVideoFrame() and not self._next_frame_displayed: self._updateFrameTexture() return_next_frame_index = True #make sure that textures are on and GL_TEXTURE0 is active GL.glActiveTexture(GL.GL_TEXTURE0) GL.glEnable(GL.GL_TEXTURE_2D) GL.glColor4f(1, 1, 1, self.opacity) # sets opacity (1,1,1 = RGB placeholder) GL.glPushMatrix() self.win.setScale('pix') #move to centre of stimulus and rotate vertsPix = self.verticesPix t=self._frame_texture.tex_coords array = (GL.GLfloat * 32)( t[0], t[1], vertsPix[0,0], vertsPix[0,1], 0., #vertex t[3], t[4], vertsPix[1,0], vertsPix[1,1], 0., t[6], t[7], vertsPix[2,0], vertsPix[2,1], 0., t[9], t[10], vertsPix[3,0], vertsPix[3,1], 0., ) GL.glPushAttrib(GL.GL_ENABLE_BIT) GL.glEnable(self._frame_texture.target) GL.glBindTexture(self._frame_texture.target, self._frame_texture.id) GL.glPushClientAttrib(GL.GL_CLIENT_VERTEX_ARRAY_BIT) #2D texture array, 3D vertex array GL.glInterleavedArrays(GL.GL_T2F_V3F, 0, array) GL.glDrawArrays(GL.GL_QUADS, 0, 4) GL.glPopClientAttrib() GL.glPopAttrib() GL.glPopMatrix() if return_next_frame_index: self.win.callOnFlip(self._flipCallback) return self._next_frame_index def setContrast(self): """Not yet implemented for MovieStim""" pass def _startAudio(self): """ Start the audio playback stream. """ if self._audio_stream_player: self._audio_stream_started = True self._audio_stream_player.play() self._audio_stream_clock.reset(-self._audio_stream_player.get_time()/1000.0) def _getAudioStreamTime(self): return self._audio_stream_clock.getTime() def _audio_time_callback(self, event, player): """ Called by VLC every few hundred msec providing the current audio track time. This info is used to pace the display of video frames read using cv2. """ self._audio_stream_clock.reset(-event.u.new_time/1000.0) def _audio_end_callback(self, event): """ Called by VLC when the audio track ends. Right now, when this is called the video is stopped. """ self._onEos() def _unload(self): if self._video_stream: self._video_stream.release() self._video_stream = None self._frame_data_interface = None self._numpy_frame = None self._releaseeAudioStream() self.status = FINISHED def _onEos(self): if self.loop: self.seek(0.0) else: self.status = FINISHED self.stop() if self.autoLog: self.win.logOnFlip("Set %s finished" %(self.name), level=logging.EXP,obj=self) def __del__(self): self._unload() def setAutoDraw(self, val, log=None): """Add or remove a stimulus from the list of stimuli that will be automatically drawn on each flip :parameters: - val: True/False True to add the stimulus to the draw list, False to remove it """ if val: self.play(log=False) # set to play in case stopped else: self.pause(log=False) #add to drawing list and update status setAttribute(self, 'autoDraw', val, log)
class MovieStim2(BaseVisualStim, ContainerMixin): """A stimulus class for playing movies (mpeg, avi, etc...) in PsychoPy that does not require avbin. Instead it requires the cv2 python package for OpenCV. The VLC media player also needs to be installed on the psychopy computer. **Example**:: See Movie2Stim.py for demo. """ def __init__(self, win, filename="", units='pix', size=None, pos=(0.0,0.0), ori=0.0, flipVert=False, flipHoriz=False, color=(1.0,1.0,1.0), colorSpace='rgb', opacity=1.0, volume=1.0, name='', loop=False, autoLog=True, depth=0.0, noAudio=False, vframe_callback=None, fps=None ): """ :Parameters: filename : a string giving the relative or absolute path to the movie. flipVert : True or *False* If True then the movie will be top-bottom flipped flipHoriz : True or *False* If True then the movie will be right-left flipped volume : The nominal level is 100, and 0 is silence. loop : bool, optional Whether to start the movie over from the beginning if draw is called and the movie is done. """ # what local vars are defined (these are the init params) for use # by __repr__ self._initParams = dir() self._initParams.remove('self') super(MovieStim2, self).__init__(win, units=units, name=name, autoLog=False) #check for pyglet if win.winType != 'pyglet': logging.error('Movie stimuli can only be used with a pyglet window') core.quit() self._retracerate = win._monitorFrameRate if self._retracerate is None: self._retracerate = win.getActualFrameRate() if self._retracerate is None: logging.warning("FrameRate could not be supplied by psychopy; defaulting to 60.0") self._retracerate = 60.0 self.filename = filename self.loop = loop self.flipVert = flipVert self.flipHoriz = flipHoriz self.pos = numpy.asarray(pos, float) self.depth = depth self.opacity = float(opacity) self.volume = volume self._av_stream_time_offset = 0.145 self._no_audio = noAudio if self._no_audio: self._requested_fps = fps else: self._requested_fps = None self._vframe_callback = vframe_callback self._reset() self.loadMovie(self.filename) self.setVolume(volume) self.aspectRatio = self._video_width/float(self._video_height) #size if size is None: self.size = numpy.array([self._video_width, self._video_height], float) elif isinstance(size, (int, float, long)): # treat size as desired width, and calc a height # that maintains the aspect ratio of the video. self.size = numpy.array([size, size/self.aspectRatio], float) else: self.size = val2array(size) self.ori = ori self._updateVertices() #set autoLog (now that params have been initialised) self.autoLog = autoLog if autoLog: logging.exp("Created %s = %s" %(self.name, str(self))) def _reset(self): self.duration = None self.status = NOT_STARTED self._numpy_frame = None self._frame_texture = None self._frame_data_interface = None self._video_stream = None self._total_frame_count = None self._video_width = None self._video_height = None # TODO: Read depth from video source self._video_frame_depth = 3 self._video_frame_rate = None self._inter_frame_interval = None self._prev_frame_sec = None self._next_frame_sec = None self._next_frame_index = None self._prev_frame_index = None self._video_perc_done = None self._last_video_flip_time = None self._next_frame_displayed = False self._video_track_clock = Clock() self._audio_stream_clock=Clock() self._vlc_instance = None self._audio_stream = None self._audio_stream_player = None self._audio_stream_started = False self._audio_stream_event_manager=None def setMovie(self, filename, log=True): """See `~MovieStim.loadMovie` (the functions are identical). This form is provided for syntactic consistency with other visual stimuli. """ self.loadMovie(filename, log=log) def loadMovie(self, filename, log=True): """Load a movie from file :Parameters: filename: string The name of the file, including path if necessary After the file is loaded MovieStim.duration is updated with the movie duration (in seconds). """ self._unload() self._reset() if self._no_audio is False: self._createAudioStream() # Create Video Stream stuff self._video_stream = cv2.VideoCapture() self._video_stream.open(filename) if not self._video_stream.isOpened(): raise RuntimeError( "Error when reading image file") self._total_frame_count = self._video_stream.get(cv2.cv.CV_CAP_PROP_FRAME_COUNT) self._video_width = self._video_stream.get(cv2.cv.CV_CAP_PROP_FRAME_WIDTH) self._video_height = self._video_stream.get(cv2.cv.CV_CAP_PROP_FRAME_HEIGHT) self._format = self._video_stream.get(cv2.cv.CV_CAP_PROP_FORMAT) # TODO: Read depth from video source self._video_frame_depth = 3 if self._no_audio: self._video_frame_rate = self._requested_fps else: self._video_frame_rate = self._video_stream.get(cv2.cv.CV_CAP_PROP_FPS) self._inter_frame_interval = 1.0/self._video_frame_rate # Create a numpy array that can hold one video frame, as returned by cv2. self._numpy_frame = numpy.zeros((self._video_height, self._video_width, self._video_frame_depth), dtype=numpy.uint8) # Uses a preallocated numpy array as the pyglet ImageData data self._frame_data_interface = ArrayInterfaceImage(self._numpy_frame, allow_copy=False, rectangle=True, force_rectangle=True) #frame texture; transformed so it looks right in psychopy self._frame_texture = self._frame_data_interface.texture.get_transform(flip_x=not self.flipHoriz, flip_y=not self.flipVert) self.duration = self._total_frame_count * self._inter_frame_interval self.status = NOT_STARTED self.filename = filename logAttrib(self, log, 'movie', filename) def _createAudioStream(self): """ Create the audio stream player for the video using pyvlc. """ if not os.access(self.filename, os.R_OK): raise RuntimeError('Error: %s file not readable' % self.filename) self._vlc_instance = vlc.Instance('--novideo') try: self._audio_stream = self._vlc_instance.media_new(self.filename) except NameError: raise ImportError('NameError: %s vs LibVLC %s' % (vlc.__version__, vlc.libvlc_get_version())) self._audio_stream_player = self._vlc_instance.media_player_new() self._audio_stream_player.set_media(self._audio_stream) self._audio_stream_event_manager = self._audio_stream_player.event_manager() self._audio_stream_event_manager.event_attach(vlc.EventType.MediaPlayerTimeChanged, self._audio_time_callback, self._audio_stream_player) self._audio_stream_event_manager.event_attach(vlc.EventType.MediaPlayerEndReached, self._audio_end_callback) def _releaseeAudioStream(self): if self._audio_stream_player: self._audio_stream_player.stop() if self._audio_stream_event_manager: self._audio_stream_event_manager.event_detach(vlc.EventType.MediaPlayerTimeChanged) self._audio_stream_event_manager.event_detach(vlc.EventType.MediaPlayerEndReached) if self._audio_stream: self._audio_stream.release() if self._vlc_instance: self._vlc_instance.vlm_release() self._vlc_instance.release() self._audio_stream = None self._audio_stream_event_manager = None self._audio_stream_player = None self._vlc_instance = None def _flipCallback(self): import inspect flip_time = inspect.currentframe().f_back.f_locals.get('now') if PRINT_FRAME_FLIP_TIMES: if self._last_video_flip_time is None: self._last_video_flip_time=flip_time print 'Frame %d\t%.4f\t%.4f'%(self.getCurrentFrameIndex(), flip_time, flip_time-self._last_video_flip_time) if flip_time is None: raise RuntimeError("Movie2._flipCallback: Can not access the currect flip time.") self._last_video_flip_time = flip_time self._next_frame_displayed = True def play(self, log=True): """Continue a paused movie from current position. """ if self.status != PLAYING: if self.status == PAUSED: # toggle audio pause if self._audio_stream_player: self._audio_stream_player.pause() self._audio_stream_clock.reset(-self._audio_stream_player.get_time()/1000.0) self.status = PLAYING if log and self.autoLog: self.win.logOnFlip("Set %s playing" %(self.name), level=logging.EXP, obj=self) self._video_track_clock.reset(-self._getNextFrame()) self._updateFrameTexture() self.win.callOnFlip(self._flipCallback) return self._next_frame_index def pause(self, log=True): """ Pause the current point in the movie (sound will stop, current frame will not advance). If play() is called again both will restart. """ if self.status == PLAYING: self.status = PAUSED if self._audio_stream_player and self._audio_stream_player.can_pause(): self._audio_stream_player.pause() if log and self.autoLog: self.win.logOnFlip("Set %s paused" %(self.name), level=logging.EXP, obj=self) return True if log and self.autoLog: self.win.logOnFlip("Failed Set %s paused" %(self.name), level=logging.EXP, obj=self) return False def stop(self, log=True): """ Stop the current point in the movie (sound will stop, current frame will not advance). Once stopped the movie cannot be restarted - it must be loaded again. Use pause() if you may need to restart the movie. """ if self.status != STOPPED: self.status = STOPPED self._unload() self._reset() if log and self.autoLog: self.win.logOnFlip("Set %s stopped" %(self.name), level=logging.EXP,obj=self) def seek(self, timestamp, log=True): """ Seek to a particular timestamp in the movie. """ if self.status in [PLAYING, PAUSED]: if self.status == PLAYING: self.pause() if self._audio_stream_player and self._audio_stream_player.is_seekable(): self._audio_stream_player.set_time(int(timestamp*1000.0)) self._video_stream.set(cv2.cv.CV_CAP_PROP_POS_MSEC, timestamp*1000.0) self.play() if log: logAttrib(self, log, 'seek', timestamp) def setFlipHoriz(self, newVal=True, log=True): """If set to True then the movie will be flipped horizontally (left-to-right). Note that this is relative to the original, not relative to the current state. """ self.flipHoriz = newVal logAttrib(self, log, 'flipHoriz') def setFlipVert(self, newVal=True, log=True): """If set to True then the movie will be flipped vertically (top-to-bottom). Note that this is relative to the original, not relative to the current state. """ self.flipVert = not newVal logAttrib(self, log, 'flipVert') def setVolume(self, v): """ Set the audio track volume. 0 = mute, 100 = 0 dB. float values between 0.0 and 1.0 are also accepted, and scaled to an int between 0 and 100. """ if self._audio_stream_player: if 0.0 <= v <= 1.0 and isinstance(v, (float,)): v = int(v*100) else: v = int(v) self.volume = v self._audio_stream_player.audio_set_volume(v) def getVolume(self): """ Returns the current movie audio volume. 0 is no audio, 100 is max audio volume. """ if self._audio_stream_player: self.volume = self._audio_stream_player.audio_get_volume() return self.volume def getFPS(self): """ Returns the movie frames per second playback speed. """ return self._video_frame_rate def setFPS(self, fps): """ If the movie was created with noAudio = True kwarg, then the movie playback speed can be changed from the original frame rate. For example, if the movie being played has 30 fps and you would like to play it at 2x normal speed, setFPS(60) will do that. """ if self._no_audio: self._requested_fps = fps self._video_frame_rate = fps self._inter_frame_interval = 1.0/self._video_frame_rate return raise ValueError("Error calling movie.setFPS(): MovieStim must be created with kwarg noAudio=True.") def getTimeToNextFrameDraw(self): """ Get the number of sec.msec remaining until the next movie video frame should be drawn. """ try: rt = (self._next_frame_sec - 1.0/self._retracerate) - self._video_track_clock.getTime() return rt except: #import traceback #traceback.print_exc() logging.WARNING("MovieStim2.getTimeToNextFrameDraw failed.") return 0.0 def shouldDrawVideoFrame(self): """ True if the next movie frame should be drawn, False if it is not yet time. See getTimeToNextFrameDraw(). """ return self.getTimeToNextFrameDraw() <= 0.0 def getCurrentFrameNumber(self): """ Get the current movie frame number. The first frame number in a file is 1. """ return self._next_frame_index def getCurrentFrameTime(self): """ Get the time that the movie file specified the current video frame as having. """ return self._next_frame_sec def getPercentageComplete(self): """ Provides a value between 0.0 and 100.0, indicating the amount of the movie that has been already played. """ return self._video_perc_done def isCurrentFrameVisible(self): """ The current video frame goes through two stages; the first being when the movie frame is being loaded, but is not visible on the display. The second is when the frame has actually been presented on the display. Returns False if the frame is in the first stage, True when in stage 2. """ return self._next_frame_displayed def _getNextFrame(self): # get next frame info ( do not decode frame yet) if self._video_stream.grab(): self._prev_frame_index = self._next_frame_index self._prev_frame_sec = self._next_frame_sec self._next_frame_sec = self._video_stream.get(cv2.cv.CV_CAP_PROP_POS_MSEC)/1000.0 self._next_frame_index = self._video_stream.get(cv2.cv.CV_CAP_PROP_POS_FRAMES) self._video_perc_done = self._video_stream.get(cv2.cv.CV_CAP_PROP_POS_AVI_RATIO) self._next_frame_displayed = False return self._next_frame_sec else: self._onEos() def _updateFrameTexture(self): # decode frame into np array and move to opengl tex ret, f = self._video_stream.retrieve() if ret: frame_array = cv2.cvtColor(f, cv2.COLOR_BGR2RGB) if callable(self._vframe_callback): try: frame_array = self._vframe_callback(self._next_frame_index, frame_array) except: print "MovieStim2 Error: vframe_callback raised an exception. Using original frame data." import traceback traceback.print_exc() #self._numpy_frame[:] = f[...,::-1] numpy.copyto(self._numpy_frame, frame_array) self._frame_data_interface.dirty() else: raise RuntimeError("Could not load video frame data.") def _getVideoAudioTimeDiff(self): if self._audio_stream_started is False: return 0 return self.getCurrentFrameTime()-self._getAudioStreamTime() def draw(self, win=None): """ Draw the current frame to a particular visual.Window (or to the default win for this object if not specified). The current position in the movie will be determined automatically. This method should be called on every frame that the movie is meant to appear""" if self.status != PLAYING: return return_next_frame_index = False if win is None: win = self.win self._selectWindow(win) if self._no_audio is False and not self._audio_stream_started and self._video_track_clock.getTime() >= self._av_stream_time_offset: self._startAudio() if self._next_frame_displayed: if self._getVideoAudioTimeDiff() > self._inter_frame_interval: self._video_track_clock.reset(-self._next_frame_sec) else: self._getNextFrame() if self.shouldDrawVideoFrame() and not self._next_frame_displayed: self._updateFrameTexture() return_next_frame_index = True #make sure that textures are on and GL_TEXTURE0 is active GL.glActiveTexture(GL.GL_TEXTURE0) GL.glEnable(GL.GL_TEXTURE_2D) GL.glColor4f(1, 1, 1, self.opacity) # sets opacity (1,1,1 = RGB placeholder) GL.glPushMatrix() self.win.setScale('pix') #move to centre of stimulus and rotate vertsPix = self.verticesPix t=self._frame_texture.tex_coords array = (GL.GLfloat * 32)( t[0], t[1], vertsPix[0,0], vertsPix[0,1], 0., #vertex t[3], t[4], vertsPix[1,0], vertsPix[1,1], 0., t[6], t[7], vertsPix[2,0], vertsPix[2,1], 0., t[9], t[10], vertsPix[3,0], vertsPix[3,1], 0., ) GL.glPushAttrib(GL.GL_ENABLE_BIT) GL.glEnable(self._frame_texture.target) GL.glBindTexture(self._frame_texture.target, self._frame_texture.id) GL.glPushClientAttrib(GL.GL_CLIENT_VERTEX_ARRAY_BIT) #2D texture array, 3D vertex array GL.glInterleavedArrays(GL.GL_T2F_V3F, 0, array) GL.glDrawArrays(GL.GL_QUADS, 0, 4) GL.glPopClientAttrib() GL.glPopAttrib() GL.glPopMatrix() if return_next_frame_index: self.win.callOnFlip(self._flipCallback) return self._next_frame_index def setContrast(self): """Not yet implemented for MovieStim""" pass def _startAudio(self): """ Start the audio playback stream. """ if self._audio_stream_player: self._audio_stream_started = True self._audio_stream_player.play() self._audio_stream_clock.reset(-self._audio_stream_player.get_time()/1000.0) def _getAudioStreamTime(self): return self._audio_stream_clock.getTime() def _audio_time_callback(self, event, player): """ Called by VLC every few hundred msec providing the current audio track time. This info is used to pace the display of video frames read using cv2. """ self._audio_stream_clock.reset(-event.u.new_time/1000.0) def _audio_end_callback(self, event): """ Called by VLC when the audio track ends. Right now, when this is called the video is stopped. """ self._onEos() def _unload(self): if self._video_stream: self._video_stream.release() self._video_stream = None self._frame_data_interface = None self._numpy_frame = None self._releaseeAudioStream() self.status = FINISHED def _onEos(self): if self.loop: self.seek(0.0) else: self.status = FINISHED self.stop() if self.autoLog: self.win.logOnFlip("Set %s finished" %(self.name), level=logging.EXP,obj=self) def __del__(self): self._unload()
class MovieStim2(BaseVisualStim, ContainerMixin): """A stimulus class for playing movies (mpeg, avi, etc...) in PsychoPy that does not require avbin. Instead it requires the cv2 python package for OpenCV. The VLC media player also needs to be installed on the psychopy computer. **Example**:: See Movie2Stim.py for demo. """ def __init__(self, win, filename="", units='pix', size=None, pos=(0.0,0.0), ori=0.0, flipVert=False, flipHoriz=False, color=(1.0,1.0,1.0), colorSpace='rgb', opacity=1.0, volume=1.0, name='', loop=False, autoLog=True, depth=0.0,): """ :Parameters: filename : a string giving the relative or absolute path to the movie. flipVert : True or *False* If True then the movie will be top-bottom flipped flipHoriz : True or *False* If True then the movie will be right-left flipped volume : The nominal level is 100, and 0 is silence. loop : bool, optional Whether to start the movie over from the beginning if draw is called and the movie is done. """ # what local vars are defined (these are the init params) for use # by __repr__ self._initParams = dir() self._initParams.remove('self') super(MovieStim2, self).__init__(win, units=units, name=name, autoLog=False) #check for pyglet if win.winType != 'pyglet': logging.error('Movie stimuli can only be used with a pyglet window') core.quit() self._retracerate = win._monitorFrameRate if self._retracerate is None: self._retracerate = win.getActualFrameRate() self.filename = filename self.loop = loop if loop: #and pyglet.version>='1.2': logging.error("looping of movies is not currently supported") self.flipVert = flipVert self.flipHoriz = flipHoriz self.pos = numpy.asarray(pos, float) self.depth = depth self.opacity = float(opacity) self.volume = volume self._av_stream_time_offset = 0.145 self._reset() self.loadMovie(self.filename) self.setVolume(volume) self.aspectRatio = self._video_width/float(self._video_height) #size if size is None: self.size = numpy.array([self._video_width, self._video_height], float) elif isinstance(size, (int, float, long)): # treat size as desired width, and calc a height # that maintains the aspect ratio of the video. self.size = numpy.array([size, size/self.aspectRatio], float) else: self.size = val2array(size) self.ori = ori self._updateVertices() #set autoLog (now that params have been initialised) self.autoLog = autoLog if autoLog: logging.exp("Created %s = %s" %(self.name, str(self))) def _reset(self): self.duration = None self.status = NOT_STARTED self._numpy_frame = None self._frame_texture = None self._frame_data_interface = None self._video_stream = None self._total_frame_count = None self._video_width = None self._video_height = None # TODO: Read depth from video source self._video_frame_depth = 3 self._video_frame_rate = None self._inter_frame_interval = None self._prev_frame_sec = None self._next_frame_sec = None self._next_frame_index = None self._prev_frame_index = None self._video_perc_done = None self._last_video_flip_time = None self._next_frame_displayed = False self._video_track_clock = Clock() self._vlc_instance = None self._vlc_event_manager = None self._audio_stream = None self._audio_stream_player = None self._audio_stream_started = False self._last_audio_callback_time = core.getTime() self._last_audio_stream_time = None self._first_audio_callback_time = None self._audio_computer_time_drift = None def setMovie(self, filename, log=True): """See `~MovieStim.loadMovie` (the functions are identical). This form is provided for syntactic consistency with other visual stimuli. """ self.loadMovie(filename, log=log) def loadMovie(self, filename, log=True): """Load a movie from file :Parameters: filename: string The name of the file, including path if necessary After the file is loaded MovieStim.duration is updated with the movie duration (in seconds). """ self._reset() self._unload() self._createAudioStream() self._video_stream = cv2.VideoCapture() self._video_stream.open(filename) if not self._video_stream.isOpened(): raise RuntimeError( "Error when reading image file") self._total_frame_count = self._video_stream.get(cv2.cv.CV_CAP_PROP_FRAME_COUNT) self._video_width = self._video_stream.get(cv2.cv.CV_CAP_PROP_FRAME_WIDTH) self._video_height = self._video_stream.get(cv2.cv.CV_CAP_PROP_FRAME_HEIGHT) self._format = self._video_stream.get(cv2.cv.CV_CAP_PROP_FORMAT) # TODO: Read depth from video source self._video_frame_depth = 3 self._video_frame_rate = self._video_stream.get(cv2.cv.CV_CAP_PROP_FPS) self._inter_frame_interval = 1.0/self._video_frame_rate # Create a numpy array that can hold one video frame, as returned by cv2. self._numpy_frame = numpy.zeros((self._video_height, self._video_width, self._video_frame_depth), dtype=numpy.uint8) # Uses a preallocated numpy array as the pyglet ImageData data self._frame_data_interface = ArrayInterfaceImage(self._numpy_frame, allow_copy=False, rectangle=True, force_rectangle=True) #frame texture; transformed so it looks right in psychopy self._frame_texture = self._frame_data_interface.texture.get_transform(flip_x=not self.flipHoriz, flip_y=not self.flipVert) self.duration = self._total_frame_count * self._inter_frame_interval self.status = NOT_STARTED self.filename = filename logAttrib(self, log, 'movie', filename) def _createAudioStream(self): """ Create the audio stream player for the video using pyvlc. """ if not os.access(self.filename, os.R_OK): raise RuntimeError('Error: %s file not readable' % self.filename) self._vlc_instance = vlc.Instance('--novideo') try: self._audio_stream = self._vlc_instance.media_new(self.filename) except NameError: raise ImportError('NameError: %s vs LibVLC %s' % (vlc.__version__, vlc.libvlc_get_version())) self._audio_stream_player = self._vlc_instance.media_player_new() self._audio_stream_player.set_media(self._audio_stream) self._vlc_event_manager = self._audio_stream_player.event_manager() self._vlc_event_manager.event_attach(vlc.EventType.MediaPlayerTimeChanged, self._audio_time_callback, self._audio_stream_player) self._vlc_event_manager.event_attach(vlc.EventType.MediaPlayerEndReached, self._audio_end_callback) def _flipCallback(self): import inspect flip_time = inspect.currentframe().f_back.f_locals.get('now') if flip_time is None: raise RuntimeError("Movie2._flipCallback: Can not access the currect flip time.") self._last_video_flip_time = flip_time self._next_frame_displayed = True def play(self, log=True): """Continue a paused movie from current position. """ if self.status != PLAYING: if self.status == PAUSED: # toggle audio pause self._audio_stream_player.pause() self.status = PLAYING if log and self.autoLog: self.win.logOnFlip("Set %s playing" %(self.name), level=logging.EXP, obj=self) #print '### PLAY ###' self._video_track_clock.reset(-self._getNextFrame()) self._updateFrameTexture() self.win.callOnFlip(self._flipCallback) #self._player._on_eos=self._onEos def pause(self, log=True): """Pause the current point in the movie (sound will stop, current frame will not advance). If play() is called again both will restart. Completely untested in all regards. """ if self.status == PLAYING and self._audio_stream_player: if self._audio_stream_player.can_pause(): self.status = PAUSED self._audio_stream_player.pause() #print '### PAUSE ###' if log and self.autoLog: self.win.logOnFlip("Set %s paused" %(self.name), level=logging.EXP, obj=self) return True if log and self.autoLog: self.win.logOnFlip("Failed Set %s paused" %(self.name), level=logging.EXP, obj=self) return False def stop(self, log=True): """Stop the current point in the movie (sound will stop, current frame will not advance). Once stopped the movie cannot be restarted - it must be loaded again. Use pause() if you may need to restart the movie. """ #print '### STOP ###' self.status = STOPPED self._unload() self._reset() if log and self.autoLog: self.win.logOnFlip("Set %s stopped" %(self.name), level=logging.EXP,obj=self) def seek(self, timestamp, log=True): """ Seek to a particular timestamp in the movie. Completely untested in all regards. Does not currently work. """ if self._audio_stream_player: if self.status in [PLAYING, PAUSED] and self._audio_stream_player.is_seekable(): if self.status == PLAYING: self.pause() aresult = self._audio_stream_player.set_time(int(timestamp*1000.0)) vresult = self._video_stream.set(cv2.cv.CV_CAP_PROP_POS_MSEC, timestamp*1000.0) self.play() if log: logAttrib(self, log, 'seek', timestamp) def setFlipHoriz(self, newVal=True, log=True): """If set to True then the movie will be flipped horizontally (left-to-right). Note that this is relative to the original, not relative to the current state. """ self.flipHoriz = newVal logAttrib(self, log, 'flipHoriz') def setFlipVert(self, newVal=True, log=True): """If set to True then the movie will be flipped vertically (top-to-bottom). Note that this is relative to the original, not relative to the current state. """ self.flipVert = not newVal logAttrib(self, log, 'flipVert') def setVolume(self, v): """ Set the audio track volume. 0 = mute, 100 = 0 dB. float values between 0.0 and 1.0 are also accepted, and scaled to an int between 0 and 100. """ if 0.0 <= v <= 1.0 and isinstance(v, (float,)): v = int(v*100) else: v = int(v) #print 'setting volume:',v self.volume = v if self._audio_stream_player: self._audio_stream_player.audio_set_volume(v) def getVolume(self): if self._audio_stream_player: self.volume = self._audio_stream_player.audio_get_volume() return self.volume def getTimeToNextFrameDraw(self): try: #assert self._video_track_clock != None #assert self._next_frame_sec != None #assert self._retracerate != None rt = (self._next_frame_sec - 1.0/self._retracerate) - self._video_track_clock.getTime() #if rt > self._inter_frame_interval or rt <= -1.0/self._retracerate: # print 'getTimeToNextFrameDraw:', rt return rt except: import traceback traceback.print_exc() return 0.0 def shouldDrawVideoFrame(self): return self.getTimeToNextFrameDraw() <= 0.0 def getCurrentFrameIndex(self): return self._next_frame_index def getCurrentFrameTime(self): return self._next_frame_sec def getPercentageComplete(self): return self._video_perc_done def getCurrentFrameDisplayed(self): return self._next_frame_displayed def _getNextFrame(self): # get next frame info ( do not decode frame yet) # TODO: Implement frame skipping (multiple grabs) if _next_frame_sec < video_track_clock - framerate if self._video_stream.grab(): self._prev_frame_index = self._next_frame_index self._prev_frame_sec = self._next_frame_sec self._next_frame_sec = self._video_stream.get(cv2.cv.CV_CAP_PROP_POS_MSEC)/1000.0 self._next_frame_index = self._video_stream.get(cv2.cv.CV_CAP_PROP_POS_FRAMES) self._video_perc_done = self._video_stream.get(cv2.cv.CV_CAP_PROP_POS_AVI_RATIO) self._next_frame_displayed = False return self._next_frame_sec else: self.status = FINISHED if self._audio_stream_player: self._audio_stream_player.stop() self._onEos() def _updateFrameTexture(self): # decode frame into np array and move to opengl tex ret, f = self._video_stream.retrieve() if ret: #self._numpy_frame[:] = f[...,::-1] numpy.copyto(self._numpy_frame, cv2.cvtColor(f, cv2.COLOR_BGR2RGB)) self._frame_data_interface.dirty() else: raise RuntimeError("Could not load video frame data.") def draw(self, win=None): """Draw the current frame to a particular visual.Window (or to the default win for this object if not specified). The current position in the movie will be determined automatically. This method should be called on every frame that the movie is meant to appear""" if self.status != PLAYING: return return_next_frame_index = False if win is None: win = self.win self._selectWindow(win) if not self._audio_stream_started and self._video_track_clock.getTime() >= self._av_stream_time_offset: self._startAudio() if self._next_frame_displayed: self._getNextFrame() if self.shouldDrawVideoFrame() and not self._next_frame_displayed: self._updateFrameTexture() return_next_frame_index = True #make sure that textures are on and GL_TEXTURE0 is active GL.glActiveTexture(GL.GL_TEXTURE0) GL.glEnable(GL.GL_TEXTURE_2D) GL.glColor4f(1, 1, 1, self.opacity) # sets opacity (1,1,1 = RGB placeholder) GL.glPushMatrix() self.win.setScale('pix') #move to centre of stimulus and rotate vertsPix = self.verticesPix t=self._frame_texture.tex_coords array = (GL.GLfloat * 32)( t[0], t[1], vertsPix[0,0], vertsPix[0,1], 0., #vertex t[3], t[4], vertsPix[1,0], vertsPix[1,1], 0., t[6], t[7], vertsPix[2,0], vertsPix[2,1], 0., t[9], t[10], vertsPix[3,0], vertsPix[3,1], 0., ) GL.glPushAttrib(GL.GL_ENABLE_BIT) GL.glEnable(self._frame_texture.target) GL.glBindTexture(self._frame_texture.target, self._frame_texture.id) GL.glPushClientAttrib(GL.GL_CLIENT_VERTEX_ARRAY_BIT) #2D texture array, 3D vertex array GL.glInterleavedArrays(GL.GL_T2F_V3F, 0, array) GL.glDrawArrays(GL.GL_QUADS, 0, 4) GL.glPopClientAttrib() GL.glPopAttrib() GL.glPopMatrix() if return_next_frame_index: self.win.callOnFlip(self._flipCallback) return self._next_frame_index def setContrast(self): """Not yet implemented for MovieStim""" pass def _startAudio(self): """ Start the audio playback stream. """ self._audio_stream_started = True self._last_audio_callback_time = core.getTime() self._audio_stream_player.play() def getAudioStreamTime(self): """ Get the current sec.msec audio track time, by taking the last reported audio stream time and adding the time since the _audio_time_callback was last called. """ #TODO: This will not be correct is video is paused. Fix. return self._last_audio_stream_time + (core.getTime() - self._last_audio_callback_time) def _audio_time_callback(self, event, player): """ Called by VLC every few hundred msec providing the current audio track time. This info is used to pace the display of video frames read using cv2. """ self._last_audio_callback_time = core.getTime() self._last_audio_stream_time = player.get_time()/1000.0 if self._first_audio_callback_time is None: self._first_audio_callback_time = self._last_audio_callback_time-self._last_audio_stream_time self._audio_computer_time_drift = self._last_audio_stream_time-( self._last_audio_callback_time-self._first_audio_callback_time) def _audio_end_callback(self, event): """ Called by VLC when the audio track ends. Right now, when this is called the video is stopped. """ # print('End of media stream (event %s)' % event.type) self.status = FINISHED self._onEos() def _unload(self): if self._video_stream: self._video_stream.release() if self._audio_stream_player: self._audio_stream_player.stop() self._video_stream = None self._audio_stream_player = None self._frame_data_interface = None self._numpy_frame = None self.status = FINISHED def __del__(self): self._unload() def _onEos(self): if self.loop: self.loadMovie(self.filename) self.play() self.status = PLAYING else: self.status = FINISHED if self.autoLog: self.win.logOnFlip("Set %s finished" %(self.name), level=logging.EXP,obj=self)