psychopy 2024.1.3__py3-none-any.whl → 2024.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of psychopy might be problematic. Click here for more details.
- psychopy/.DS_Store +0 -0
- psychopy/CHANGELOG.txt +206 -0
- psychopy/GIT_SHA +1 -0
- psychopy/VERSION +1 -0
- psychopy/__init__.py +77 -15
- psychopy/app/Resources/classic/plugin16.png +0 -0
- psychopy/app/Resources/classic/plugin16@2x.png +0 -0
- psychopy/app/Resources/dark/plugin16.png +0 -0
- psychopy/app/Resources/dark/plugin16@2x.png +0 -0
- psychopy/app/Resources/light/plugin16.png +0 -0
- psychopy/app/Resources/light/plugin16@2x.png +0 -0
- psychopy/app/__init__.py +76 -2
- psychopy/app/_psychopyApp.py +126 -101
- psychopy/app/builder/builder.py +14 -10
- psychopy/app/builder/dialogs/__init__.py +8 -8
- psychopy/app/builder/dialogs/dlgsConditions.py +12 -13
- psychopy/app/builder/dialogs/paramCtrls.py +24 -57
- psychopy/app/builder/localizedStrings.py +11 -9
- psychopy/app/builder/validators.py +2 -2
- psychopy/app/coder/codeEditorBase.py +8 -8
- psychopy/app/coder/coder.py +4 -4
- psychopy/app/connections/sendusage.py +2 -2
- psychopy/app/connections/updates.py +9 -9
- psychopy/app/dialogs.py +34 -2
- psychopy/app/idle.py +31 -0
- psychopy/app/jobs.py +21 -3
- psychopy/app/linuxconfig/__init__.py +9 -0
- psychopy/app/locale/ar_001/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/ar_001/LC_MESSAGE/messages.po +4602 -2540
- psychopy/app/locale/es_CO/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/es_CO/LC_MESSAGE/messages.po +56 -54
- psychopy/app/locale/es_ES/LC_MESSAGE/messages.po +53 -43
- psychopy/app/locale/es_US/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/es_US/LC_MESSAGE/messages.po +56 -54
- psychopy/app/locale/ja_JP/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/ja_JP/LC_MESSAGE/messages.po +1258 -1176
- psychopy/app/locale/pt_PT/LC_MESSAGE/messages.po +9415 -5
- psychopy/app/pavlovia_ui/_base.py +33 -3
- psychopy/app/pavlovia_ui/search.py +0 -1
- psychopy/app/plugin_manager/dialog.py +104 -51
- psychopy/app/plugin_manager/packages.py +5 -0
- psychopy/app/plugin_manager/plugins.py +152 -67
- psychopy/app/preferencesDlg.py +8 -8
- psychopy/app/psychopyApp.py +11 -5
- psychopy/app/ribbon.py +124 -14
- psychopy/app/runner/runner.py +6 -1
- psychopy/app/stdout/stdOutRich.py +27 -11
- psychopy/app/themes/icons.py +52 -2
- psychopy/assets/__init__.py +0 -0
- psychopy/assets/click.png +0 -0
- psychopy/assets/clicknext.png +0 -0
- psychopy/assets/next.png +0 -0
- psychopy/assets/psychopy.ico +0 -0
- psychopy/assets/psychopy.png +0 -0
- psychopy/assets/templates/__init__.py +0 -0
- psychopy/assets/touch.png +0 -0
- psychopy/assets/touchnext.png +0 -0
- psychopy/assets/window.ico +0 -0
- psychopy/changes/2023.1.0.md +9 -0
- psychopy/changes/2024.1.0.md +16 -0
- psychopy/changes/__init__.py +0 -0
- psychopy/clock.py +2 -2
- psychopy/colors.py +2 -1
- psychopy/compatibility.py +53 -1
- psychopy/contrib/.DS_Store +0 -0
- psychopy/contrib/configobj/__init__.py +10 -8
- psychopy/data/__init__.py +3 -2
- psychopy/data/base.py +5 -5
- psychopy/data/experiment.py +130 -4
- psychopy/data/routine.py +56 -0
- psychopy/data/staircase.py +2 -2
- psychopy/data/trial.py +559 -97
- psychopy/data/utils.py +56 -21
- psychopy/demos/.DS_Store +0 -0
- psychopy/demos/builder/.DS_Store +0 -0
- psychopy/demos/builder/Design Templates/.DS_Store +0 -0
- psychopy/demos/builder/Experiments/.DS_Store +0 -0
- psychopy/demos/builder/Feature Demos/.DS_Store +0 -0
- psychopy/demos/builder/Feature Demos/buttonBox/buttonBoxDemo.psyexp +375 -0
- psychopy/demos/builder/Feature Demos/buttonBox/readme.md +5 -0
- psychopy/demos/builder/Feature Demos/pilotMode/pilotMode.psyexp +433 -0
- psychopy/demos/builder/Feature Demos/pilotMode/readme.md +7 -0
- psychopy/demos/builder/Feature Demos/progress/progressBar.psyexp +4 -4
- psychopy/demos/builder/Hardware/.DS_Store +0 -0
- psychopy/demos/builder/Helper Tools/.DS_Store +0 -0
- psychopy/demos/coder/.DS_Store +0 -0
- psychopy/demos/coder/hardware/testSoundLatency.py +2 -2
- psychopy/demos/coder/iohub/.DS_Store +0 -0
- psychopy/demos/coder/misc/hdf5_2_csv +33 -0
- psychopy/event.py +30 -29
- psychopy/experiment/.DS_Store +0 -0
- psychopy/experiment/_experiment.py +6 -6
- psychopy/experiment/components/.DS_Store +0 -0
- psychopy/experiment/components/__init__.py +6 -3
- psychopy/experiment/components/_base.py +286 -131
- psychopy/experiment/components/aperture/.DS_Store +0 -0
- psychopy/experiment/components/brush/.DS_Store +0 -0
- psychopy/experiment/components/button/.DS_Store +0 -0
- psychopy/experiment/components/button/__init__.py +5 -1
- psychopy/experiment/components/buttonBox/.DS_Store +0 -0
- psychopy/experiment/components/buttonBox/__init__.py +21 -12
- psychopy/experiment/components/camera/.DS_Store +0 -0
- psychopy/experiment/components/code/.DS_Store +0 -0
- psychopy/experiment/components/dots/.DS_Store +0 -0
- psychopy/experiment/components/eyetracker_record/.DS_Store +0 -0
- psychopy/experiment/components/eyetracker_record/__init__.py +92 -30
- psychopy/experiment/components/form/.DS_Store +0 -0
- psychopy/experiment/components/form/__init__.py +6 -2
- psychopy/experiment/components/grating/.DS_Store +0 -0
- psychopy/experiment/components/grating/__init__.py +14 -3
- psychopy/experiment/components/image/.DS_Store +0 -0
- psychopy/experiment/components/image/__init__.py +14 -3
- psychopy/experiment/components/joyButtons/.DS_Store +0 -0
- psychopy/experiment/components/joystick/.DS_Store +0 -0
- psychopy/experiment/components/keyboard/.DS_Store +0 -0
- psychopy/experiment/components/keyboard/__init__.py +22 -10
- psychopy/experiment/components/microphone/.DS_Store +0 -0
- psychopy/experiment/components/microphone/__init__.py +59 -39
- psychopy/experiment/components/mouse/.DS_Store +0 -0
- psychopy/experiment/components/mouse/__init__.py +44 -29
- psychopy/experiment/components/movie/.DS_Store +0 -0
- psychopy/experiment/components/movie/__init__.py +1 -1
- psychopy/experiment/components/panorama/.DS_Store +0 -0
- psychopy/experiment/components/parallelOut/.DS_Store +0 -0
- psychopy/experiment/components/patch/.DS_Store +0 -0
- psychopy/experiment/components/polygon/.DS_Store +0 -0
- psychopy/experiment/components/polygon/__init__.py +26 -6
- psychopy/experiment/components/progress/.DS_Store +0 -0
- psychopy/experiment/components/progress/__init__.py +1 -1
- psychopy/experiment/components/ratingScale/.DS_Store +0 -0
- psychopy/experiment/components/resourceManager/.DS_Store +0 -0
- psychopy/experiment/components/roi/.DS_Store +0 -0
- psychopy/experiment/components/roi/__init__.py +5 -0
- psychopy/experiment/components/routineSettings/.DS_Store +0 -0
- psychopy/experiment/components/routineSettings/__init__.py +57 -10
- psychopy/experiment/components/serialOut/.DS_Store +0 -0
- psychopy/experiment/components/settings/.DS_Store +0 -0
- psychopy/experiment/components/settings/__init__.py +117 -42
- psychopy/experiment/components/slider/.DS_Store +0 -0
- psychopy/experiment/components/sound/.DS_Store +0 -0
- psychopy/experiment/components/sound/__init__.py +54 -19
- psychopy/experiment/components/static/.DS_Store +0 -0
- psychopy/experiment/components/static/__init__.py +1 -1
- psychopy/experiment/components/text/.DS_Store +0 -0
- psychopy/experiment/components/text/__init__.py +28 -3
- psychopy/experiment/components/textbox/.DS_Store +0 -0
- psychopy/experiment/components/textbox/__init__.py +12 -2
- psychopy/experiment/components/unknown/.DS_Store +0 -0
- psychopy/experiment/components/unknown/__init__.py +1 -2
- psychopy/experiment/components/unknownPlugin/.DS_Store +0 -0
- psychopy/experiment/components/unknownPlugin/__init__.py +2 -2
- psychopy/experiment/components/variable/.DS_Store +0 -0
- psychopy/experiment/flow.py +11 -4
- psychopy/experiment/loops.py +85 -37
- psychopy/experiment/params.py +74 -32
- psychopy/experiment/py2js_transpiler.py +8 -1
- psychopy/experiment/routines/.DS_Store +0 -0
- psychopy/experiment/routines/_base.py +102 -22
- psychopy/experiment/routines/counterbalance/.DS_Store +0 -0
- psychopy/experiment/routines/counterbalance/__init__.py +5 -1
- psychopy/experiment/routines/eyetracker_calibrate/.DS_Store +0 -0
- psychopy/experiment/routines/eyetracker_validate/.DS_Store +0 -0
- psychopy/experiment/routines/pavlovia_survey/.DS_Store +0 -0
- psychopy/experiment/routines/photodiodeValidator/.DS_Store +0 -0
- psychopy/experiment/routines/photodiodeValidator/__init__.py +7 -6
- psychopy/experiment/routines/unknown/.DS_Store +0 -0
- psychopy/gui/wxgui.py +4 -4
- psychopy/hardware/.DS_Store +0 -0
- psychopy/hardware/__init__.py +1 -1
- psychopy/hardware/base.py +12 -0
- psychopy/hardware/camera/__init__.py +1 -15
- psychopy/hardware/cedrus.py +10 -11
- psychopy/hardware/crs/colorcal.py +13 -22
- psychopy/hardware/crs/optical.py +10 -20
- psychopy/hardware/emulator.py +17 -14
- psychopy/hardware/eyetracker.py +42 -118
- psychopy/hardware/gammasci.py +4 -15
- psychopy/hardware/keyboard.py +102 -11
- psychopy/hardware/listener.py +3 -0
- psychopy/hardware/microphone.py +148 -18
- psychopy/hardware/minolta.py +8 -15
- psychopy/hardware/photodiode.py +191 -16
- psychopy/hardware/photometer/__init__.py +11 -19
- psychopy/hardware/pr.py +8 -15
- psychopy/hardware/speaker.py +39 -4
- psychopy/info.py +0 -71
- psychopy/iohub/.DS_Store +0 -0
- psychopy/iohub/__init__.py +1 -1
- psychopy/iohub/client/__init__.py +30 -20
- psychopy/iohub/client/keyboard.py +24 -24
- psychopy/iohub/datastore/__init__.py +2 -2
- psychopy/iohub/datastore/util.py +2 -2
- psychopy/iohub/default_config.yaml +1 -1
- psychopy/iohub/devices/.DS_Store +0 -0
- psychopy/iohub/devices/__init__.py +112 -25
- psychopy/iohub/devices/deviceConfigValidation.py +2 -1
- psychopy/iohub/devices/experiment/default_experiment.yaml +12 -1
- psychopy/iohub/devices/experiment/supported_config_settings.yaml +5 -1
- psychopy/iohub/devices/eyetracker/.DS_Store +0 -0
- psychopy/iohub/devices/eyetracker/__init__.py +46 -0
- psychopy/iohub/devices/eyetracker/calibration/procedure.py +2 -2
- psychopy/iohub/devices/eyetracker/hw/gazepoint/__init__.py +14 -2
- psychopy/iohub/devices/eyetracker/hw/mouse/eyetracker.py +3 -4
- psychopy/iohub/server.py +2 -2
- psychopy/iohub/start_iohub_process.py +3 -0
- psychopy/iohub/util/__init__.py +62 -70
- psychopy/layout.py +5 -5
- psychopy/logging.py +8 -1
- psychopy/microphone.py +10 -37
- psychopy/platform_specific/__init__.py +0 -2
- psychopy/platform_specific/darwin.py +1 -3
- psychopy/platform_specific/linux.py +31 -33
- psychopy/platform_specific/win32.py +38 -13
- psychopy/plugins/__init__.py +148 -116
- psychopy/plugins/util.py +39 -0
- psychopy/preferences/Darwin.spec +4 -2
- psychopy/preferences/FreeBSD.spec +4 -2
- psychopy/preferences/Linux.spec +4 -2
- psychopy/preferences/Windows.spec +4 -2
- psychopy/preferences/baseNoArch.spec +4 -2
- psychopy/preferences/preferences.py +47 -24
- psychopy/projects/pavlovia.py +47 -4
- psychopy/scripts/psyexpCompile.py +0 -4
- psychopy/session.py +153 -21
- psychopy/sound/__init__.py +31 -21
- psychopy/sound/_base.py +20 -3
- psychopy/sound/audioclip.py +320 -33
- psychopy/sound/backend_ptb.py +47 -58
- psychopy/sound/backend_pygame.py +1 -1
- psychopy/sound/backend_pysound.py +6 -15
- psychopy/sound/transcribe.py +53 -0
- psychopy/tests/.DS_Store +0 -0
- psychopy/tests/data/.DS_Store +0 -0
- psychopy/tests/data/TestUnknownPluginComponent_load_resave.psyexp +135 -0
- psychopy/tests/data/Test_textbox/test_ori_0_bottom right.png +0 -0
- psychopy/tests/data/Test_textbox/test_ori_0_center.png +0 -0
- psychopy/tests/data/Test_textbox/test_ori_0_top left.png +0 -0
- psychopy/tests/data/Test_textbox/test_ori_120_bottom right.png +0 -0
- psychopy/tests/data/Test_textbox/test_ori_120_center.png +0 -0
- psychopy/tests/data/Test_textbox/test_ori_120_top left.png +0 -0
- psychopy/tests/data/Test_textbox/test_ori_180_bottom right.png +0 -0
- psychopy/tests/data/Test_textbox/test_ori_180_center.png +0 -0
- psychopy/tests/data/Test_textbox/test_ori_180_top left.png +0 -0
- psychopy/tests/data/Test_textbox/test_ori_240_bottom right.png +0 -0
- psychopy/tests/data/Test_textbox/test_ori_240_center.png +0 -0
- psychopy/tests/data/Test_textbox/test_ori_240_top left.png +0 -0
- psychopy/tests/data/correctScript/.DS_Store +0 -0
- psychopy/tests/data/test_components/testClearKeyboard/testClearKeyboard.psyexp +200 -0
- psychopy/tests/data/test_session/.DS_Store +0 -0
- psychopy/tests/data/test_session/root/testFutureTrials/testFutureTrials.psyexp +155 -0
- psychopy/tests/data/test_session/root/testTrialNav/trialNav.psyexp +158 -0
- psychopy/tests/test_app/.DS_Store +0 -0
- psychopy/tests/test_app/conftest.py +2 -2
- psychopy/tests/test_app/test_speed.py +4 -1
- psychopy/tests/test_data/test_TrialHandler2.py +146 -1
- psychopy/tests/test_experiment/.DS_Store +0 -0
- psychopy/tests/test_experiment/needs_wx/genComponsTemplate.py +3 -3
- psychopy/tests/test_experiment/needs_wx/test_components.py +2 -2
- psychopy/tests/test_experiment/test_components/test_KeyboardComponent.py +28 -0
- psychopy/tests/test_experiment/test_components/test_UnknownPluginComponent.py +27 -0
- psychopy/tests/test_experiment/test_components/test_base_components.py +58 -0
- psychopy/tests/test_experiment/test_py2js.py +1 -1
- psychopy/tests/test_hardware/test_keyboard.py +184 -16
- psychopy/tests/test_hardware/test_ports.py +1 -11
- psychopy/tests/test_liaison/test_Liaison.py +47 -0
- psychopy/tests/test_misc/test_core.py +5 -0
- psychopy/tests/test_session/test_Session.py +5 -1
- psychopy/tests/test_tools/test_versionchooser.py +39 -8
- psychopy/tests/test_visual/test_all_stimuli.py +0 -97
- psychopy/tests/test_visual/test_image.py +6 -5
- psychopy/tests/test_visual/test_textbox.py +36 -0
- psychopy/tests/utils.py +4 -0
- psychopy/tools/filetools.py +1 -1
- psychopy/tools/pkgtools.py +160 -137
- psychopy/tools/versionchooser.py +10 -10
- psychopy/tools/wizard.py +3 -3
- psychopy/visual/.DS_Store +0 -0
- psychopy/visual/backends/pygletbackend.py +24 -13
- psychopy/visual/basevisual.py +5 -11
- psychopy/visual/button.py +2 -14
- psychopy/visual/helpers.py +5 -5
- psychopy/visual/line.py +1 -2
- psychopy/visual/movie2.py +7 -816
- psychopy/visual/movie3.py +7 -589
- psychopy/visual/movies/__init__.py +8 -11
- psychopy/visual/movies/frame.py +5 -2
- psychopy/visual/movies/players/ffpyplayer_player.py +5 -2
- psychopy/visual/noise.py +8 -7
- psychopy/visual/patch.py +7 -16
- psychopy/visual/progress.py +1 -1
- psychopy/visual/radial.py +9 -7
- psychopy/visual/ratingscale.py +8 -1415
- psychopy/visual/secondorder.py +10 -9
- psychopy/visual/shape.py +7 -2
- psychopy/visual/text.py +1 -1
- psychopy/visual/textbox2/textbox2.py +28 -5
- psychopy/web.py +5 -2
- {psychopy-2024.1.3.dist-info → psychopy-2024.2.0.dist-info}/METADATA +8 -13
- {psychopy-2024.1.3.dist-info → psychopy-2024.2.0.dist-info}/RECORD +313 -219
- {psychopy-2024.1.3.dist-info → psychopy-2024.2.0.dist-info}/WHEEL +1 -1
- psychopy/app/Resources/click.png +0 -0
- psychopy/app/Resources/next.png +0 -0
- psychopy/experiment/components/patch/__init__.py +0 -121
- psychopy/experiment/components/patch/classic/patch.png +0 -0
- psychopy/experiment/components/patch/dark/patch.png +0 -0
- psychopy/experiment/components/patch/dark/patch@2x.png +0 -0
- psychopy/experiment/components/patch/light/patch.png +0 -0
- psychopy/experiment/components/patch/light/patch@2x.png +0 -0
- psychopy/experiment/components/ratingScale/__init__.py +0 -337
- psychopy/experiment/components/ratingScale/classic/ratingscale.png +0 -0
- psychopy/experiment/components/ratingScale/classic/ratingscale@2x.png +0 -0
- psychopy/experiment/components/ratingScale/dark/ratingScale@2x.png +0 -0
- psychopy/experiment/components/ratingScale/dark/ratingscale.png +0 -0
- psychopy/experiment/components/ratingScale/light/ratingScale@2x.png +0 -0
- psychopy/experiment/components/ratingScale/light/ratingscale.png +0 -0
- psychopy/platform_specific/posix.py +0 -16
- psychopy/tests/test_sound/test_microphone.py +0 -217
- psychopy/tests/test_visual/test_ratingScale.py +0 -299
- /psychopy/{app/Resources → assets}/Psychopy Window Favicon@16w.png +0 -0
- /psychopy/{app/Resources → assets}/Psychopy Window Favicon@32w.png +0 -0
- /psychopy/{app/Resources → assets}/USB-C.png +0 -0
- /psychopy/{app/Resources → assets}/USB.png +0 -0
- /psychopy/{app/Resources → assets}/creditCard.png +0 -0
- /psychopy/{app/Resources → assets}/default.mp3 +0 -0
- /psychopy/{app/Resources → assets}/default.mp4 +0 -0
- /psychopy/{app/Resources → assets}/default.png +0 -0
- /psychopy/{app/Resources → assets/templates}/instruct1.png +0 -0
- /psychopy/{app/Resources → assets/templates}/instruct2.png +0 -0
- {psychopy-2024.1.3.dist-info → psychopy-2024.2.0.dist-info}/entry_points.txt +0 -0
- {psychopy-2024.1.3.dist-info → psychopy-2024.2.0.dist-info}/licenses/AUTHORS.md +0 -0
- {psychopy-2024.1.3.dist-info → psychopy-2024.2.0.dist-info}/licenses/LICENSE +0 -0
psychopy/visual/movie3.py
CHANGED
|
@@ -1,599 +1,17 @@
|
|
|
1
1
|
#!/usr/bin/env python
|
|
2
2
|
# -*- coding: utf-8 -*-
|
|
3
3
|
|
|
4
|
-
"""
|
|
5
|
-
A stimulus class for playing movies (mp4, divx, avi etc...) in PsychoPy.
|
|
6
|
-
Demo using the experimental movie3 stim to play a video file. Path of video
|
|
7
|
-
needs to updated to point to a video you have. movie2 does /not/ require
|
|
8
|
-
avbin to be installed.
|
|
9
|
-
|
|
10
|
-
Movie3 does require:
|
|
11
|
-
~~~~~~~~~~~~~~~~~~~~~
|
|
12
|
-
|
|
13
|
-
moviepy (which requires imageio, Decorator). These can be installed
|
|
14
|
-
(including dependencies) on a standard Python install using
|
|
15
|
-
`pip install moviepy`
|
|
16
|
-
imageio will download further compiled libs (ffmpeg) as needed
|
|
17
|
-
|
|
18
|
-
Current known issues:
|
|
19
|
-
~~~~~~~~~~~~~~~~~~~~~~
|
|
20
|
-
|
|
21
|
-
volume control not implemented
|
|
22
|
-
movie is long then audio will be huge and currently the whole thing gets
|
|
23
|
-
loaded in one go. We should provide streaming audio from disk.
|
|
24
|
-
|
|
25
|
-
"""
|
|
26
|
-
|
|
27
4
|
# Part of the PsychoPy library
|
|
28
5
|
# Copyright (C) 2002-2018 Jonathan Peirce (C) 2019-2024 Open Science Tools Ltd.
|
|
29
6
|
# Distributed under the terms of the GNU General Public License (GPL).
|
|
30
|
-
from pathlib import Path
|
|
31
|
-
|
|
32
|
-
reportNDroppedFrames = 10
|
|
33
|
-
|
|
34
|
-
import os
|
|
35
|
-
|
|
36
|
-
from psychopy import logging, prefs # adding prefs to be able to check sound lib -JK
|
|
37
|
-
from psychopy.tools.arraytools import val2array
|
|
38
|
-
from psychopy.tools.attributetools import logAttrib, setAttribute
|
|
39
|
-
from psychopy.tools.filetools import pathToString
|
|
40
|
-
from psychopy.visual.basevisual import BaseVisualStim, ContainerMixin, TextureMixin
|
|
41
|
-
from moviepy.video.io.VideoFileClip import VideoFileClip
|
|
42
|
-
|
|
43
|
-
import ctypes
|
|
44
|
-
import numpy
|
|
45
|
-
from psychopy.clock import Clock
|
|
46
|
-
from psychopy.constants import FINISHED, NOT_STARTED, PAUSED, PLAYING, STOPPED
|
|
47
|
-
|
|
48
|
-
import pyglet.gl as GL
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
class MovieStim3(BaseVisualStim, ContainerMixin, TextureMixin):
|
|
52
|
-
"""A stimulus class for playing movies. This is a lazy-imported class,
|
|
53
|
-
therefore import using full path
|
|
54
|
-
`from psychopy.visual.movie3 import MovieStim3` when inheriting from it.
|
|
55
|
-
|
|
56
|
-
This class uses MoviePy and FFMPEG as a backend for loading and decoding
|
|
57
|
-
video data from files.
|
|
58
|
-
|
|
59
|
-
Parameters
|
|
60
|
-
----------
|
|
61
|
-
filename : str
|
|
62
|
-
A string giving the relative or absolute path to the movie.
|
|
63
|
-
flipVert : True or *False*
|
|
64
|
-
If True then the movie will be top-bottom flipped
|
|
65
|
-
flipHoriz : True or *False*
|
|
66
|
-
If True then the movie will be right-left flipped
|
|
67
|
-
volume :
|
|
68
|
-
The nominal level is 100, and 0 is silence.
|
|
69
|
-
loop : bool, optional
|
|
70
|
-
Whether to start the movie over from the beginning if draw is called and
|
|
71
|
-
the movie is done.
|
|
72
|
-
|
|
73
|
-
Examples
|
|
74
|
-
--------
|
|
75
|
-
See Movie2Stim.py for demo.
|
|
76
|
-
|
|
77
|
-
"""
|
|
78
|
-
def __init__(self, win,
|
|
79
|
-
filename="",
|
|
80
|
-
units='pix',
|
|
81
|
-
size=None,
|
|
82
|
-
pos=(0.0, 0.0),
|
|
83
|
-
anchor="center",
|
|
84
|
-
ori=0.0,
|
|
85
|
-
flipVert=False,
|
|
86
|
-
flipHoriz=False,
|
|
87
|
-
color=(1.0, 1.0, 1.0),
|
|
88
|
-
colorSpace='rgb',
|
|
89
|
-
opacity=1.0,
|
|
90
|
-
volume=1.0,
|
|
91
|
-
name='',
|
|
92
|
-
loop=False,
|
|
93
|
-
autoLog=True,
|
|
94
|
-
depth=0.0,
|
|
95
|
-
noAudio=False,
|
|
96
|
-
vframe_callback=None,
|
|
97
|
-
fps=None,
|
|
98
|
-
interpolate=True):
|
|
99
|
-
# what local vars are defined (these are the init params) for use
|
|
100
|
-
# by __repr__
|
|
101
|
-
self._initParams = dir()
|
|
102
|
-
self._initParams.remove('self')
|
|
103
|
-
super(MovieStim3, self).__init__(win, units=units, name=name,
|
|
104
|
-
autoLog=False)
|
|
105
|
-
|
|
106
|
-
retraceRate = win._monitorFrameRate
|
|
107
|
-
# if retraceRate is None:
|
|
108
|
-
# retraceRate = win.getActualFrameRate()
|
|
109
|
-
if retraceRate is None:
|
|
110
|
-
logging.warning("FrameRate could not be supplied by psychopy; "
|
|
111
|
-
"defaulting to 60.0")
|
|
112
|
-
retraceRate = 60.0
|
|
113
|
-
self._retraceInterval = 1.0/retraceRate
|
|
114
|
-
self.filename = pathToString(filename)
|
|
115
|
-
self.loop = loop
|
|
116
|
-
self.flipVert = flipVert
|
|
117
|
-
self.flipHoriz = flipHoriz
|
|
118
|
-
self.pos = numpy.asarray(pos, float)
|
|
119
|
-
self.anchor = anchor
|
|
120
|
-
self.depth = depth
|
|
121
|
-
self.opacity = opacity
|
|
122
|
-
self.interpolate = interpolate
|
|
123
|
-
self.noAudio = noAudio
|
|
124
|
-
self._audioStream = None
|
|
125
|
-
self.useTexSubImage2D = True
|
|
126
|
-
|
|
127
|
-
if noAudio: # to avoid dependency problems in silent movies
|
|
128
|
-
self.sound = None
|
|
129
|
-
else:
|
|
130
|
-
from psychopy import sound
|
|
131
|
-
self.sound = sound
|
|
132
|
-
|
|
133
|
-
# set autoLog (now that params have been initialised)
|
|
134
|
-
self.autoLog = autoLog
|
|
135
|
-
if autoLog:
|
|
136
|
-
logging.exp("Created %s = %s" % (self.name, str(self)))
|
|
137
|
-
|
|
138
|
-
self._videoClock = Clock()
|
|
139
|
-
self.loadMovie(self.filename)
|
|
140
|
-
self.setVolume(volume)
|
|
141
|
-
self.nDroppedFrames = 0
|
|
142
|
-
|
|
143
|
-
# size
|
|
144
|
-
if size is None:
|
|
145
|
-
self.size = numpy.array([self._mov.w, self._mov.h],
|
|
146
|
-
float)
|
|
147
|
-
else:
|
|
148
|
-
self.size = val2array(size)
|
|
149
|
-
self.ori = ori
|
|
150
|
-
self._updateVertices()
|
|
151
|
-
|
|
152
|
-
@property
|
|
153
|
-
def interpolate(self):
|
|
154
|
-
"""Enable linear interpolation (`bool').
|
|
155
|
-
|
|
156
|
-
If `True` linear filtering will be applied to the video making the image
|
|
157
|
-
less pixelated if scaled.
|
|
158
|
-
"""
|
|
159
|
-
return self._interpolate
|
|
160
|
-
|
|
161
|
-
@interpolate.setter
|
|
162
|
-
def interpolate(self, value):
|
|
163
|
-
self._interpolate = value
|
|
164
|
-
self._texFilterNeedsUpdate = True
|
|
165
|
-
|
|
166
|
-
@property
|
|
167
|
-
def duration(self):
|
|
168
|
-
"""Duration of the video clip in seconds (`float`). Only valid after
|
|
169
|
-
loading a clip, always returning `0.0` if not.
|
|
170
|
-
"""
|
|
171
|
-
if self._mov is None:
|
|
172
|
-
return 0.0
|
|
173
|
-
|
|
174
|
-
return self._mov.duration
|
|
175
|
-
|
|
176
|
-
@property
|
|
177
|
-
def frameInterval(self):
|
|
178
|
-
"""Time in seconds each frame is to be presented on screen (`float`).
|
|
179
|
-
Value is `0.0` if no movie is loaded.
|
|
180
|
-
"""
|
|
181
|
-
if self._mov is None:
|
|
182
|
-
return 0.0
|
|
183
|
-
|
|
184
|
-
return 1. / self._mov.fps
|
|
185
|
-
|
|
186
|
-
def reset(self):
|
|
187
|
-
self._numpyFrame = None
|
|
188
|
-
self._nextFrameT = 0.0
|
|
189
|
-
self._texID = None
|
|
190
|
-
self.status = NOT_STARTED
|
|
191
|
-
self.nDroppedFrames = 0
|
|
192
|
-
|
|
193
|
-
def setMovie(self, filename, log=True):
|
|
194
|
-
"""See `~MovieStim.loadMovie` (the functions are identical).
|
|
195
|
-
|
|
196
|
-
This form is provided for syntactic consistency with other visual
|
|
197
|
-
stimuli.
|
|
198
|
-
|
|
199
|
-
Parameters
|
|
200
|
-
----------
|
|
201
|
-
filename : str
|
|
202
|
-
The name of the file, including path if necessary.
|
|
203
|
-
log : bool
|
|
204
|
-
Log this event.
|
|
205
|
-
|
|
206
|
-
"""
|
|
207
|
-
self.loadMovie(filename, log=log)
|
|
208
|
-
|
|
209
|
-
def loadMovie(self, filename, log=True):
|
|
210
|
-
"""Load a movie from file.
|
|
211
|
-
|
|
212
|
-
After the file is loaded `MovieStim.duration` is updated with the movie
|
|
213
|
-
duration (in seconds).
|
|
214
|
-
|
|
215
|
-
Parameters
|
|
216
|
-
----------
|
|
217
|
-
filename : str
|
|
218
|
-
The name of the file, including path if necessary.
|
|
219
|
-
log : bool
|
|
220
|
-
Log this event.
|
|
221
|
-
|
|
222
|
-
"""
|
|
223
|
-
filename = pathToString(filename)
|
|
224
|
-
self.reset() # set status and timestamps etc
|
|
225
|
-
|
|
226
|
-
self._mov = None
|
|
227
|
-
# Create Video Stream stuff
|
|
228
|
-
if os.path.isfile(filename):
|
|
229
|
-
self._mov = VideoFileClip(filename, audio=(1 - self.noAudio))
|
|
230
|
-
if (not self.noAudio) and (self._mov.audio is not None):
|
|
231
|
-
sound = self.sound
|
|
232
|
-
try:
|
|
233
|
-
self._audioStream = sound.Sound(
|
|
234
|
-
self._mov.audio.to_soundarray(),
|
|
235
|
-
sampleRate=self._mov.audio.fps)
|
|
236
|
-
except:
|
|
237
|
-
# JWE added this as a patch for a moviepy oddity where the
|
|
238
|
-
# duration is inflated in the saved file causes the
|
|
239
|
-
# audioclip to be the wrong length, so round down and it
|
|
240
|
-
# should work
|
|
241
|
-
jwe_tmp = self._mov.subclip(0, round(self._mov.duration))
|
|
242
|
-
self._audioStream = sound.Sound(
|
|
243
|
-
jwe_tmp.audio.to_soundarray(),
|
|
244
|
-
sampleRate=self._mov.audio.fps)
|
|
245
|
-
del(jwe_tmp)
|
|
246
|
-
else: # make sure we set to None (in case prev clip had audio)
|
|
247
|
-
self._audioStream = None
|
|
248
|
-
elif not filename.startswith(prefs.paths['resources']):
|
|
249
|
-
# If not found, and we aren't already looking in the Resources folder, try again in the Resources folder
|
|
250
|
-
self.loadMovie(Path(prefs.paths['resources']) / filename, log=False)
|
|
251
|
-
else:
|
|
252
|
-
# Raise error if *still* not found
|
|
253
|
-
raise IOError("Movie file '%s' was not found" % filename)
|
|
254
|
-
# mov has attributes:
|
|
255
|
-
# size, duration, fps
|
|
256
|
-
# mov.audio has attributes
|
|
257
|
-
# duration, fps (aka sampleRate), to_soundarray()
|
|
258
|
-
self._frameInterval = 1.0 / self._mov.fps
|
|
259
|
-
# self.duration = self._mov.duration
|
|
260
|
-
self.filename = filename
|
|
261
|
-
self._updateFrameTexture()
|
|
262
|
-
logAttrib(self, log, 'movie', filename)
|
|
263
|
-
|
|
264
|
-
def play(self, log=True):
|
|
265
|
-
"""Continue a paused movie from current position.
|
|
266
|
-
"""
|
|
267
|
-
status = self.status
|
|
268
|
-
if status != PLAYING:
|
|
269
|
-
self.status = PLAYING # moved this to get better audio behavior - JK
|
|
270
|
-
# Added extra check to prevent audio doubling - JK
|
|
271
|
-
if self._audioStream is not None and self._audioStream.status is not PLAYING:
|
|
272
|
-
self._audioStream.play()
|
|
273
|
-
if status == PAUSED:
|
|
274
|
-
if self.getCurrentFrameTime() < 0: # Check for valid timestamp, correct if needed -JK
|
|
275
|
-
self._audioSeek(0)
|
|
276
|
-
else:
|
|
277
|
-
self._audioSeek(self.getCurrentFrameTime())
|
|
278
|
-
self._videoClock.reset(-self.getCurrentFrameTime())
|
|
279
|
-
if log and self.autoLog:
|
|
280
|
-
self.win.logOnFlip("Set %s playing" % (self.name),
|
|
281
|
-
level=logging.EXP, obj=self)
|
|
282
|
-
self._updateFrameTexture()
|
|
283
|
-
|
|
284
|
-
def pause(self, log=True):
|
|
285
|
-
"""
|
|
286
|
-
Pause the current point in the movie (sound will stop, current frame
|
|
287
|
-
will not advance). If play() is called again both will restart.
|
|
288
|
-
"""
|
|
289
|
-
if self.status == PLAYING:
|
|
290
|
-
self.status = PAUSED
|
|
291
|
-
if self._audioStream:
|
|
292
|
-
if prefs.hardware['audioLib'] in ['sounddevice', 'PTB']:
|
|
293
|
-
self._audioStream.pause() # sounddevice and PTB have a "pause" function -JK
|
|
294
|
-
else:
|
|
295
|
-
self._audioStream.stop()
|
|
296
|
-
if log and self.autoLog:
|
|
297
|
-
self.win.logOnFlip("Set %s paused" %
|
|
298
|
-
(self.name), level=logging.EXP, obj=self)
|
|
299
|
-
return True
|
|
300
|
-
if log and self.autoLog:
|
|
301
|
-
self.win.logOnFlip("Failed Set %s paused" %
|
|
302
|
-
(self.name), level=logging.EXP, obj=self)
|
|
303
|
-
return False
|
|
304
|
-
|
|
305
|
-
def stop(self, log=True):
|
|
306
|
-
"""Stop the current point in the movie (sound will stop, current frame
|
|
307
|
-
will not advance). Once stopped the movie cannot be restarted -
|
|
308
|
-
it must be loaded again. Use pause() if you may need to restart
|
|
309
|
-
the movie.
|
|
310
|
-
"""
|
|
311
|
-
if self.status != STOPPED:
|
|
312
|
-
self._unload()
|
|
313
|
-
self.reset()
|
|
314
|
-
self.status = STOPPED # set status to STOPPED after _unload
|
|
315
|
-
if log and self.autoLog:
|
|
316
|
-
self.win.logOnFlip("Set %s stopped" % (self.name),
|
|
317
|
-
level=logging.EXP, obj=self)
|
|
318
|
-
|
|
319
|
-
def setVolume(self, volume):
|
|
320
|
-
pass # to do
|
|
321
|
-
|
|
322
|
-
def setFlipHoriz(self, newVal=True, log=True):
|
|
323
|
-
"""If set to True then the movie will be flipped horizontally
|
|
324
|
-
(left-to-right). Note that this is relative to the original,
|
|
325
|
-
not relative to the current state.
|
|
326
|
-
"""
|
|
327
|
-
self.flipHoriz = newVal
|
|
328
|
-
logAttrib(self, log, 'flipHoriz')
|
|
329
|
-
self._needVertexUpdate = True
|
|
330
|
-
|
|
331
|
-
def setFlipVert(self, newVal=True, log=True):
|
|
332
|
-
"""If set to True then the movie will be flipped vertically
|
|
333
|
-
(top-to-bottom). Note that this is relative to the original,
|
|
334
|
-
not relative to the current state.
|
|
335
|
-
"""
|
|
336
|
-
self.flipVert = newVal
|
|
337
|
-
logAttrib(self, log, 'flipVert')
|
|
338
|
-
self._needVertexUpdate = True
|
|
339
|
-
|
|
340
|
-
def getFPS(self):
|
|
341
|
-
"""Get the movie frames per second.
|
|
342
|
-
|
|
343
|
-
Returns
|
|
344
|
-
-------
|
|
345
|
-
float
|
|
346
|
-
Frames per second.
|
|
347
|
-
|
|
348
|
-
"""
|
|
349
|
-
return float(self._mov.fps)
|
|
350
|
-
|
|
351
|
-
def getCurrentFrameTime(self):
|
|
352
|
-
"""Get the time that the movie file specified the current
|
|
353
|
-
video frame as having.
|
|
354
|
-
"""
|
|
355
|
-
return self._nextFrameT - self.frameInterval
|
|
356
|
-
|
|
357
|
-
def _updateFrameTexture(self):
|
|
358
|
-
"""Update texture pixel store to contain the present frame. Decoded
|
|
359
|
-
frame image samples are streamed to the texture buffer.
|
|
360
|
-
|
|
361
|
-
"""
|
|
362
|
-
if self._nextFrameT is None or self._nextFrameT < 0:
|
|
363
|
-
# movie has no current position (or invalid position -JK),
|
|
364
|
-
# need to reset the clock to zero in order to have the
|
|
365
|
-
# timing logic work otherwise the video stream would skip
|
|
366
|
-
# frames until the time since creating the movie object has passed
|
|
367
|
-
self._videoClock.reset()
|
|
368
|
-
self._nextFrameT = 0.0
|
|
369
|
-
|
|
370
|
-
# only advance if next frame (half of next retrace rate)
|
|
371
|
-
if self._nextFrameT > self.duration:
|
|
372
|
-
self._onEos()
|
|
373
|
-
elif self._numpyFrame is not None:
|
|
374
|
-
if self._nextFrameT > (self._videoClock.getTime() -
|
|
375
|
-
self._retraceInterval/2.0):
|
|
376
|
-
return None
|
|
377
|
-
|
|
378
|
-
while self._nextFrameT <= (self._videoClock.getTime() - self._frameInterval*2):
|
|
379
|
-
self.nDroppedFrames += 1
|
|
380
|
-
if self.nDroppedFrames <= reportNDroppedFrames:
|
|
381
|
-
logging.warning("{}: Video catchup needed, advancing self._nextFrameT from"
|
|
382
|
-
" {} to {}".format(self._videoClock.getTime(), self._nextFrameT,
|
|
383
|
-
self._nextFrameT+self._frameInterval))
|
|
384
|
-
if self.nDroppedFrames == reportNDroppedFrames:
|
|
385
|
-
logging.warning("Max reportNDroppedFrames reached, will not log any more dropped frames")
|
|
386
|
-
|
|
387
|
-
self._nextFrameT += self._frameInterval
|
|
388
|
-
|
|
389
|
-
try:
|
|
390
|
-
self._numpyFrame = self._mov.get_frame(self._nextFrameT)
|
|
391
|
-
except OSError:
|
|
392
|
-
if self.autoLog:
|
|
393
|
-
logging.warning("Frame {} not found, moving one frame and trying again"
|
|
394
|
-
.format(self._nextFrameT), obj=self)
|
|
395
|
-
self._nextFrameT += self._frameInterval
|
|
396
|
-
self._updateFrameTexture()
|
|
397
|
-
useSubTex = self.useTexSubImage2D
|
|
398
|
-
if self._texID is None:
|
|
399
|
-
self._texID = GL.GLuint()
|
|
400
|
-
GL.glGenTextures(1, ctypes.byref(self._texID))
|
|
401
|
-
useSubTex = False
|
|
402
|
-
|
|
403
|
-
GL.glActiveTexture(GL.GL_TEXTURE0)
|
|
404
|
-
# bind that name to the target
|
|
405
|
-
GL.glBindTexture(GL.GL_TEXTURE_2D, self._texID)
|
|
406
|
-
# bind the texture in openGL
|
|
407
|
-
GL.glEnable(GL.GL_TEXTURE_2D)
|
|
408
|
-
# makes the texture map wrap (this is actually default anyway)
|
|
409
|
-
GL.glTexParameteri(
|
|
410
|
-
GL.GL_TEXTURE_2D, GL.GL_TEXTURE_WRAP_S, GL.GL_CLAMP)
|
|
411
|
-
GL.glTexParameteri(
|
|
412
|
-
GL.GL_TEXTURE_2D, GL.GL_TEXTURE_WRAP_T, GL.GL_CLAMP)
|
|
413
|
-
# data from PIL/numpy is packed, but default for GL is 4 bytes
|
|
414
|
-
GL.glPixelStorei(GL.GL_UNPACK_ALIGNMENT, 1)
|
|
415
|
-
# important if using bits++ because GL_LINEAR
|
|
416
|
-
# sometimes extrapolates to pixel vals outside range
|
|
417
|
-
if self.interpolate:
|
|
418
|
-
GL.glTexParameteri(
|
|
419
|
-
GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MAG_FILTER, GL.GL_LINEAR)
|
|
420
|
-
GL.glTexParameteri(
|
|
421
|
-
GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MIN_FILTER, GL.GL_LINEAR)
|
|
422
|
-
if useSubTex is False:
|
|
423
|
-
GL.glTexImage2D(GL.GL_TEXTURE_2D, 0, GL.GL_RGB8,
|
|
424
|
-
self._numpyFrame.shape[1],
|
|
425
|
-
self._numpyFrame.shape[0], 0,
|
|
426
|
-
GL.GL_RGB, GL.GL_UNSIGNED_BYTE,
|
|
427
|
-
self._numpyFrame.ctypes)
|
|
428
|
-
else:
|
|
429
|
-
GL.glTexSubImage2D(GL.GL_TEXTURE_2D, 0, 0, 0,
|
|
430
|
-
self._numpyFrame.shape[1],
|
|
431
|
-
self._numpyFrame.shape[0],
|
|
432
|
-
GL.GL_RGB, GL.GL_UNSIGNED_BYTE,
|
|
433
|
-
self._numpyFrame.ctypes)
|
|
434
|
-
else:
|
|
435
|
-
GL.glTexParameteri(
|
|
436
|
-
GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MAG_FILTER, GL.GL_NEAREST)
|
|
437
|
-
GL.glTexParameteri(
|
|
438
|
-
GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MIN_FILTER, GL.GL_NEAREST)
|
|
439
|
-
if useSubTex is False:
|
|
440
|
-
GL.glTexImage2D(GL.GL_TEXTURE_2D, 0, GL.GL_RGB8,
|
|
441
|
-
self._numpyFrame.shape[1],
|
|
442
|
-
self._numpyFrame.shape[0], 0,
|
|
443
|
-
GL.GL_BGR, GL.GL_UNSIGNED_BYTE,
|
|
444
|
-
self._numpyFrame.ctypes)
|
|
445
|
-
else:
|
|
446
|
-
GL.glTexSubImage2D(GL.GL_TEXTURE_2D, 0, 0, 0,
|
|
447
|
-
self._numpyFrame.shape[1],
|
|
448
|
-
self._numpyFrame.shape[0],
|
|
449
|
-
GL.GL_BGR, GL.GL_UNSIGNED_BYTE,
|
|
450
|
-
self._numpyFrame.ctypes)
|
|
451
|
-
GL.glTexEnvi(GL.GL_TEXTURE_ENV, GL.GL_TEXTURE_ENV_MODE,
|
|
452
|
-
GL.GL_MODULATE) # ?? do we need this - think not!
|
|
453
|
-
|
|
454
|
-
if self.status == PLAYING:
|
|
455
|
-
self._nextFrameT += self._frameInterval
|
|
456
|
-
|
|
457
|
-
def draw(self, win=None):
|
|
458
|
-
"""Draw the current frame to a particular visual.Window (or to the
|
|
459
|
-
default win for this object if not specified). The current position in
|
|
460
|
-
the movie will be determined automatically.
|
|
461
|
-
|
|
462
|
-
This method should be called on every frame that the movie is meant to
|
|
463
|
-
appear.
|
|
464
|
-
|
|
465
|
-
Parameters
|
|
466
|
-
----------
|
|
467
|
-
win : :class:`~psychopy.visual.Window` or None
|
|
468
|
-
Window the video is being drawn to. If `None`, the window specified
|
|
469
|
-
by property `win` will be used. Default is `None`.
|
|
470
|
-
|
|
471
|
-
"""
|
|
472
|
-
if (self.status == NOT_STARTED or
|
|
473
|
-
(self.status == FINISHED and self.loop)):
|
|
474
|
-
self.play()
|
|
475
|
-
elif self.status == FINISHED and not self.loop:
|
|
476
|
-
return
|
|
477
|
-
if win is None:
|
|
478
|
-
win = self.win
|
|
479
|
-
self._selectWindow(win)
|
|
480
|
-
self._updateFrameTexture() # will check if it's needed
|
|
481
|
-
|
|
482
|
-
# scale the drawing frame and get to centre of field
|
|
483
|
-
GL.glPushMatrix() # push before drawing, pop after
|
|
484
|
-
# push the data for client attributes
|
|
485
|
-
GL.glPushClientAttrib(GL.GL_CLIENT_ALL_ATTRIB_BITS)
|
|
486
|
-
|
|
487
|
-
self.win.setScale('pix')
|
|
488
|
-
# move to centre of stimulus and rotate
|
|
489
|
-
vertsPix = self.verticesPix
|
|
490
|
-
|
|
491
|
-
# bind textures
|
|
492
|
-
GL.glActiveTexture(GL.GL_TEXTURE1)
|
|
493
|
-
GL.glBindTexture(GL.GL_TEXTURE_2D, 0)
|
|
494
|
-
GL.glEnable(GL.GL_TEXTURE_2D)
|
|
495
|
-
GL.glActiveTexture(GL.GL_TEXTURE0)
|
|
496
|
-
GL.glBindTexture(GL.GL_TEXTURE_2D, self._texID)
|
|
497
|
-
GL.glEnable(GL.GL_TEXTURE_2D)
|
|
498
|
-
|
|
499
|
-
# sets opacity (1,1,1 = RGB placeholder)
|
|
500
|
-
GL.glColor4f(1, 1, 1, self.opacity)
|
|
501
|
-
|
|
502
|
-
array = (GL.GLfloat * 32)(
|
|
503
|
-
1, 1, # texture coords
|
|
504
|
-
vertsPix[0, 0], vertsPix[0, 1], 0., # vertex
|
|
505
|
-
0, 1,
|
|
506
|
-
vertsPix[1, 0], vertsPix[1, 1], 0.,
|
|
507
|
-
0, 0,
|
|
508
|
-
vertsPix[2, 0], vertsPix[2, 1], 0.,
|
|
509
|
-
1, 0,
|
|
510
|
-
vertsPix[3, 0], vertsPix[3, 1], 0.,
|
|
511
|
-
)
|
|
512
|
-
|
|
513
|
-
# 2D texture array, 3D vertex array
|
|
514
|
-
GL.glInterleavedArrays(GL.GL_T2F_V3F, 0, array)
|
|
515
|
-
GL.glDrawArrays(GL.GL_QUADS, 0, 4)
|
|
516
|
-
GL.glPopClientAttrib()
|
|
517
|
-
GL.glPopMatrix()
|
|
518
|
-
# unbind the textures
|
|
519
|
-
GL.glActiveTexture(GL.GL_TEXTURE0)
|
|
520
|
-
GL.glBindTexture(GL.GL_TEXTURE_2D, 0)
|
|
521
|
-
GL.glEnable(GL.GL_TEXTURE_2D) # implicitly disables 1D
|
|
522
|
-
|
|
523
|
-
def seek(self, t):
|
|
524
|
-
"""Go to a specific point in time for both the audio and video streams
|
|
525
|
-
"""
|
|
526
|
-
# video is easy: set both times to zero and update the frame texture
|
|
527
|
-
self._nextFrameT = t
|
|
528
|
-
self._videoClock.reset(t)
|
|
529
|
-
self._audioSeek(t)
|
|
530
|
-
|
|
531
|
-
def _audioSeek(self, t):
|
|
532
|
-
sound = self.sound
|
|
533
|
-
if self._audioStream is None:
|
|
534
|
-
return # do nothing
|
|
535
|
-
# check if sounddevice or PTB is being used. If so we can use seek. If not we
|
|
536
|
-
# have to reload the audio stream and begin at the new loc
|
|
537
|
-
if prefs.hardware['audioLib'] in ['sounddevice', 'PTB']:
|
|
538
|
-
self._audioStream.seek(t)
|
|
539
|
-
else:
|
|
540
|
-
self._audioStream.stop()
|
|
541
|
-
sndArray = self._mov.audio.to_soundarray()
|
|
542
|
-
startIndex = int(t * self._mov.audio.fps)
|
|
543
|
-
self._audioStream = sound.Sound(
|
|
544
|
-
sndArray[startIndex:, :], sampleRate=self._mov.audio.fps)
|
|
545
|
-
if self.status != PAUSED: # Allows for seeking while paused - JK
|
|
546
|
-
self._audioStream.play()
|
|
547
|
-
|
|
548
|
-
def _getAudioStreamTime(self):
|
|
549
|
-
return self._audio_stream_clock.getTime()
|
|
550
|
-
|
|
551
|
-
def _unload(self):
|
|
552
|
-
# remove textures from graphics card to prevent crash
|
|
553
|
-
self.clearTextures()
|
|
554
|
-
if self._mov is not None:
|
|
555
|
-
self._mov.close()
|
|
556
|
-
self._mov = None
|
|
557
|
-
self._numpyFrame = None
|
|
558
|
-
if self._audioStream is not None:
|
|
559
|
-
self._audioStream.stop()
|
|
560
|
-
self._audioStream = None
|
|
561
|
-
self.status = FINISHED
|
|
562
|
-
|
|
563
|
-
def _onEos(self):
|
|
564
|
-
if self.loop:
|
|
565
|
-
self.seek(0.0)
|
|
566
|
-
else:
|
|
567
|
-
self.status = FINISHED
|
|
568
|
-
self.stop()
|
|
569
|
-
|
|
570
|
-
if self.autoLog:
|
|
571
|
-
self.win.logOnFlip("Set %s finished" % self.name,
|
|
572
|
-
level=logging.EXP, obj=self)
|
|
573
|
-
|
|
574
|
-
def __del__(self):
|
|
575
|
-
try:
|
|
576
|
-
self._unload()
|
|
577
|
-
except (ImportError, ModuleNotFoundError, TypeError):
|
|
578
|
-
pass # has probably been garbage-collected already
|
|
579
|
-
|
|
580
|
-
def setAutoDraw(self, val, log=None):
|
|
581
|
-
"""Add or remove a stimulus from the list of stimuli that will be
|
|
582
|
-
automatically drawn on each flip.
|
|
583
7
|
|
|
584
|
-
Parameters
|
|
585
|
-
----------
|
|
586
|
-
val : bool
|
|
587
|
-
True to add the stimulus to the draw list, False to remove it.
|
|
588
8
|
|
|
589
|
-
|
|
590
|
-
if val:
|
|
591
|
-
self.play(log=False) # set to play in case stopped
|
|
592
|
-
else:
|
|
593
|
-
self.pause(log=False)
|
|
594
|
-
# add to drawing list and update status
|
|
595
|
-
setAttribute(self, 'autoDraw', val, log)
|
|
9
|
+
from psychopy.tools.pkgtools import PluginStub
|
|
596
10
|
|
|
597
11
|
|
|
598
|
-
|
|
599
|
-
|
|
12
|
+
class MovieStim3(
|
|
13
|
+
PluginStub,
|
|
14
|
+
plugin="psychopy-legacy",
|
|
15
|
+
doclink="https://psychopy.github.io/psychopy-legacy/coder/visual/MovieStim3"
|
|
16
|
+
):
|
|
17
|
+
pass
|
|
@@ -209,7 +209,7 @@ class MovieStim(BaseVisualStim, DraggingMixin, ColorMixin, ContainerMixin):
|
|
|
209
209
|
if isinstance(filename, str):
|
|
210
210
|
# alias default names (so it always points to default.png)
|
|
211
211
|
if filename in defaultStim:
|
|
212
|
-
filename = Path(prefs.paths['
|
|
212
|
+
filename = Path(prefs.paths['assets']) / defaultStim[filename]
|
|
213
213
|
|
|
214
214
|
# check if the file has can be loaded
|
|
215
215
|
if not os.path.isfile(filename):
|
|
@@ -308,7 +308,7 @@ class MovieStim(BaseVisualStim, DraggingMixin, ColorMixin, ContainerMixin):
|
|
|
308
308
|
self._selectWindow(self.win if win is None else win)
|
|
309
309
|
|
|
310
310
|
# handle autoplay
|
|
311
|
-
if self._autoStart and self.
|
|
311
|
+
if self._autoStart and self.isNotStarted:
|
|
312
312
|
self.play()
|
|
313
313
|
|
|
314
314
|
# update the video frame and draw it to a quad
|
|
@@ -675,7 +675,7 @@ class MovieStim(BaseVisualStim, DraggingMixin, ColorMixin, ContainerMixin):
|
|
|
675
675
|
"""
|
|
676
676
|
# get the size of the movie frame and compute the buffer size
|
|
677
677
|
vidWidth, vidHeight = self._player.getMetadata().size
|
|
678
|
-
nBufferBytes = vidWidth * vidHeight *
|
|
678
|
+
nBufferBytes = vidWidth * vidHeight * 4
|
|
679
679
|
|
|
680
680
|
# Create the pixel buffer object which will serve as the texture memory
|
|
681
681
|
# store. Pixel data will be copied to this buffer each frame.
|
|
@@ -696,15 +696,13 @@ class MovieStim(BaseVisualStim, DraggingMixin, ColorMixin, ContainerMixin):
|
|
|
696
696
|
GL.glTexImage2D(
|
|
697
697
|
GL.GL_TEXTURE_2D,
|
|
698
698
|
0,
|
|
699
|
-
GL.
|
|
699
|
+
GL.GL_RGBA8,
|
|
700
700
|
vidWidth, vidHeight, # frame dims in pixels
|
|
701
701
|
0,
|
|
702
|
-
GL.
|
|
702
|
+
GL.GL_BGRA,
|
|
703
703
|
GL.GL_UNSIGNED_BYTE,
|
|
704
704
|
None)
|
|
705
705
|
|
|
706
|
-
GL.glPixelStorei(GL.GL_UNPACK_ALIGNMENT, 1)
|
|
707
|
-
|
|
708
706
|
# setup texture filtering
|
|
709
707
|
if self.interpolate:
|
|
710
708
|
texFilter = GL.GL_LINEAR
|
|
@@ -732,7 +730,7 @@ class MovieStim(BaseVisualStim, DraggingMixin, ColorMixin, ContainerMixin):
|
|
|
732
730
|
# get the size of the movie frame and compute the buffer size
|
|
733
731
|
vidWidth, vidHeight = self._player.getMetadata().size
|
|
734
732
|
|
|
735
|
-
nBufferBytes = vidWidth * vidHeight *
|
|
733
|
+
nBufferBytes = vidWidth * vidHeight * 4
|
|
736
734
|
|
|
737
735
|
# bind pixel unpack buffer
|
|
738
736
|
GL.glBindBuffer(GL.GL_PIXEL_UNPACK_BUFFER, self._pixbuffId)
|
|
@@ -769,12 +767,11 @@ class MovieStim(BaseVisualStim, DraggingMixin, ColorMixin, ContainerMixin):
|
|
|
769
767
|
GL.glBindTexture(GL.GL_TEXTURE_2D, self._textureId)
|
|
770
768
|
|
|
771
769
|
# copy the PBO to the texture
|
|
772
|
-
GL.glPixelStorei(GL.GL_UNPACK_ALIGNMENT, 1)
|
|
773
770
|
GL.glTexSubImage2D(
|
|
774
771
|
GL.GL_TEXTURE_2D, 0, 0, 0,
|
|
775
772
|
vidWidth, vidHeight,
|
|
776
|
-
GL.
|
|
777
|
-
GL.
|
|
773
|
+
GL.GL_BGRA,
|
|
774
|
+
GL.GL_UNSIGNED_INT_8_8_8_8_REV,
|
|
778
775
|
0) # point to the presently bound buffer
|
|
779
776
|
|
|
780
777
|
# update texture filtering only if needed
|
psychopy/visual/movies/frame.py
CHANGED
|
@@ -62,7 +62,8 @@ class MovieFrame:
|
|
|
62
62
|
"_audioSamples",
|
|
63
63
|
"_audioChannels",
|
|
64
64
|
"_movieLib",
|
|
65
|
-
"_userData"
|
|
65
|
+
"_userData",
|
|
66
|
+
'_keepAlive'
|
|
66
67
|
]
|
|
67
68
|
|
|
68
69
|
def __init__(self,
|
|
@@ -76,7 +77,8 @@ class MovieFrame:
|
|
|
76
77
|
audioSamples=None,
|
|
77
78
|
metadata=None,
|
|
78
79
|
movieLib=u"",
|
|
79
|
-
userData=None
|
|
80
|
+
userData=None,
|
|
81
|
+
keepAlive=None):
|
|
80
82
|
|
|
81
83
|
self.frameIndex = frameIndex
|
|
82
84
|
self.absTime = absTime
|
|
@@ -89,6 +91,7 @@ class MovieFrame:
|
|
|
89
91
|
self._metadata = metadata
|
|
90
92
|
self.movieLib = movieLib
|
|
91
93
|
self.userData = userData
|
|
94
|
+
self._keepAlive = keepAlive
|
|
92
95
|
|
|
93
96
|
def __repr__(self):
|
|
94
97
|
return (f"MovieFrame(frameIndex={self.frameIndex}, "
|