1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386
|
#!/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-2020 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
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)))
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)
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)
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)
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)
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)
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)
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
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
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()
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)
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
|