Source code for psychopy.visual.movie

#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""A stimulus class for playing movies (mpeg, avi, etc...) in PsychoPy.
"""

# Part of the PsychoPy library
# Copyright (C) 2002-2018 Jonathan Peirce (C) 2019 Open Science Tools Ltd.
# Distributed under the terms of the GNU General Public License (GPL).

from __future__ import absolute_import, division, print_function

from builtins import next
from builtins import str
import sys
import os

# Ensure setting pyglet.options['debug_gl'] to False is done prior to any
# other calls to pyglet or pyglet submodules, otherwise it may not get picked
# up by the pyglet GL engine and have no effect.
# Shaders will work but require OpenGL2.0 drivers AND PyOpenGL3.0+
import pyglet
pyglet.options['debug_gl'] = False
GL = pyglet.gl

# on windows try to load avbin now (other libs can interfere)
if sys.platform == 'win32':
    # make sure we also check in SysWOW64 if on 64-bit windows
    if 'C:\\Windows\\SysWOW64' not in os.environ['PATH']:
        os.environ['PATH'] += ';C:\\Windows\\SysWOW64'

    try:
        from pyglet.media import avbin
        haveAvbin = True
    except ImportError:
        # either avbin isn't installed or scipy.stats has been imported
        # (prevents avbin loading)
        haveAvbin = False
    except Exception as e:
        # WindowsError on some systems
        # AttributeError if using avbin5 from pyglet 1.2?
        haveAvbin = False


import psychopy  # so we can get the __path__
from psychopy import core, logging, event
import psychopy.event

# tools must only be imported *after* event or MovieStim breaks on win32
# (JWP has no idea why!)
from psychopy.tools.arraytools import val2array
from psychopy.tools.attributetools import logAttrib, setAttribute
from psychopy.visual.basevisual import BaseVisualStim, ContainerMixin
from psychopy.tools.filetools import pathToString

if sys.platform == 'win32' and not haveAvbin:
    logging.warning("avbin.dll failed to load. "
                    "Try importing psychopy.visual as the first library "
                    "(before anything that uses scipy) or use a different"
                    "movie backend (e.g. moviepy).")

import numpy
try:
    from pyglet import media
    havePygletMedia = True
except Exception:
    havePygletMedia = False

from psychopy.constants import FINISHED, NOT_STARTED, PAUSED, PLAYING, STOPPED


[docs]class MovieStim(BaseVisualStim, ContainerMixin): """A stimulus class for playing movies (mpeg, avi, etc...) in PsychoPy. **Example**:: mov = visual.MovieStim(myWin, 'testMovie.mp4', flipVert=False) print(mov.duration) # give the original size of the movie in pixels: print(mov.format.width, mov.format.height) mov.draw() # draw the current frame (automagically determined) See MovieStim.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=None, loop=False, autoLog=None, depth=0.0,): """ :Parameters: filename : a string giving the relative or absolute path to the movie. Can be any movie that AVbin can read (e.g. mpeg, DivX) 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 1.0, and 0.0 is silence, see pyglet.media.Player 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(MovieStim, self).__init__( win, units=units, name=name, autoLog=False) self._verticesBase *= numpy.array([[-1, 1]]) # unflip if not havePygletMedia: msg = ("pyglet.media is needed for MovieStim and could not be" " imported.\nThis can occur for various reasons;" " - psychopy.visual was imported too late (after a lib" " that uses scipy)" " - no audio output is enabled (no audio card or no " "speakers attached)" " - avbin is not installed") raise ImportError(msg) self._movie = None # the actual pyglet media object self._player = pyglet.media.ManagedSoundPlayer() self._player.volume = volume try: self._player_default_on_eos = self._player.on_eos except Exception: # pyglet 1.1.4? self._player_default_on_eos = self._player._on_eos self.filename = pathToString(filename) self.duration = None self.loop = loop if loop and pyglet.version >= '1.2': logging.error("looping of movies is not currently supported " "for pyglet >= 1.2 (only for version 1.1.4)") self.loadMovie(self.filename) self.format = self._movie.video_format self.pos = numpy.asarray(pos, float) self.depth = depth self.flipVert = flipVert self.flipHoriz = flipHoriz self.opacity = float(opacity) self.status = NOT_STARTED # size if size is None: self.size = numpy.array([self.format.width, self.format.height], float) else: self.size = val2array(size) self.ori = ori self._updateVertices() if win.winType != 'pyglet': logging.error('Movie stimuli can only be used with a ' 'pyglet window') core.quit() # set autoLog now that params have been initialised wantLog = autoLog is None and self.win.autoLog self.__dict__['autoLog'] = autoLog or wantLog if self.autoLog: logging.exp("Created %s = %s" % (self.name, str(self)))
[docs] def setMovie(self, filename, log=None): """See `~MovieStim.loadMovie` (the functions are identical). This form is provided for syntactic consistency with other visual stimuli. """ self.loadMovie(filename, log=log)
[docs] def loadMovie(self, filename, log=None): """Load a movie from file :Parameters: filename: string The name of the file, including path if necessary Brings up a warning if avbin is not found on the computer. After the file is loaded MovieStim.duration is updated with the movie duration (in seconds). """ filename = pathToString(filename) try: self._movie = pyglet.media.load(filename, streaming=True) except Exception as e: # pyglet.media.riff is N/A if avbin is available, and then # actual exception would get masked with a new one for unknown # (sub)module riff, thus catching any exception and tuning msg # up if it has to do anything with avbin estr = str(e) msg = '' if "avbin" in estr.lower(): msg = ("\nIt seems that avbin was not installed correctly." "\nPlease fetch/install it from " "http://code.google.com/p/avbin/.") emsg = "Caught exception '%s' while loading file '%s'.%s" raise IOError(emsg % (estr, filename, msg)) self._player.queue(self._movie) self.duration = self._movie.duration while self._player.source != self._movie: next(self._player) self.status = NOT_STARTED self._player.pause() # start 'playing' on the next draw command self.filename = filename logAttrib(self, log, 'movie', filename)
[docs] def pause(self, log=None): """Pause the current point in the movie (sound will stop, current frame will not advance). If play() is called again both will restart. """ self._player.pause() self._player._on_eos = self._player_default_on_eos self.status = PAUSED if log or log is None and self.autoLog: self.win.logOnFlip("Set %s paused" % self.name, level=logging.EXP, obj=self)
[docs] def stop(self, log=None): """Stop the current point in the movie. The 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. """ self._player.stop() self._player._on_eos = self._player_default_on_eos self.status = STOPPED if log or log is None and self.autoLog: self.win.logOnFlip("Set %s stopped" % self.name, level=logging.EXP, obj=self)
[docs] def play(self, log=None): """Continue a paused movie from current position. """ self._player.play() self._player._on_eos = self._onEos self.status = PLAYING if log or log is None and self.autoLog: self.win.logOnFlip("Set %s playing" % self.name, level=logging.EXP, obj=self)
[docs] def seek(self, timestamp, log=None): """Seek to a particular timestamp in the movie. NB this does not seem very robust as at version 1.62, may crash! """ self._player.seek(float(timestamp)) logAttrib(self, log, 'seek', timestamp)
[docs] def setFlipHoriz(self, newVal=True, log=None): """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') self._needVertexUpdate = True
[docs] def setFlipVert(self, newVal=True, log=None): """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 = newVal logAttrib(self, log, 'flipVert') self._needVertexUpdate = True
[docs] def draw(self, win=None): """Draw the current frame to a particular visual.Window. Draw 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 and not self._player.playing: self.status = FINISHED _done = bool(self.status == FINISHED) if self.status == NOT_STARTED or (_done and self.loop): self.play() elif _done and not self.loop: return if win is None: win = self.win self._selectWindow(win) # make sure that textures are on and GL_TEXTURE0 is active GL.glActiveTexture(GL.GL_TEXTURE0) GL.glEnable(GL.GL_TEXTURE_2D) if pyglet.version >= '1.2': # for pyglet 1.1.4 this was done via media.dispatch_events self._player.update_texture() frameTexture = self._player.get_texture() if frameTexture is None: return # sets opacity (1,1,1 = RGB placeholder) GL.glColor4f(1, 1, 1, self.opacity) GL.glPushMatrix() self.win.setScale('pix') # move to centre of stimulus and rotate vertsPix = self.verticesPix t = frameTexture.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(frameTexture.target) GL.glBindTexture(frameTexture.target, frameTexture.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()
[docs] def setContrast(self): """Not yet implemented for MovieStim. """ pass
def _onEos(self): if self.loop: self.loadMovie(self.filename) self.play() self.status = PLAYING else: self.status = FINISHED self._player._on_eos = self._player_default_on_eos if self.autoLog: self.win.logOnFlip("Set %s finished" % self.name, level=logging.EXP, obj=self)
[docs] 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)
def __del__(self): try: next(self._player) except Exception: pass

Back to top