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.

Files changed (331) hide show
  1. psychopy/.DS_Store +0 -0
  2. psychopy/CHANGELOG.txt +206 -0
  3. psychopy/GIT_SHA +1 -0
  4. psychopy/VERSION +1 -0
  5. psychopy/__init__.py +77 -15
  6. psychopy/app/Resources/classic/plugin16.png +0 -0
  7. psychopy/app/Resources/classic/plugin16@2x.png +0 -0
  8. psychopy/app/Resources/dark/plugin16.png +0 -0
  9. psychopy/app/Resources/dark/plugin16@2x.png +0 -0
  10. psychopy/app/Resources/light/plugin16.png +0 -0
  11. psychopy/app/Resources/light/plugin16@2x.png +0 -0
  12. psychopy/app/__init__.py +76 -2
  13. psychopy/app/_psychopyApp.py +126 -101
  14. psychopy/app/builder/builder.py +14 -10
  15. psychopy/app/builder/dialogs/__init__.py +8 -8
  16. psychopy/app/builder/dialogs/dlgsConditions.py +12 -13
  17. psychopy/app/builder/dialogs/paramCtrls.py +24 -57
  18. psychopy/app/builder/localizedStrings.py +11 -9
  19. psychopy/app/builder/validators.py +2 -2
  20. psychopy/app/coder/codeEditorBase.py +8 -8
  21. psychopy/app/coder/coder.py +4 -4
  22. psychopy/app/connections/sendusage.py +2 -2
  23. psychopy/app/connections/updates.py +9 -9
  24. psychopy/app/dialogs.py +34 -2
  25. psychopy/app/idle.py +31 -0
  26. psychopy/app/jobs.py +21 -3
  27. psychopy/app/linuxconfig/__init__.py +9 -0
  28. psychopy/app/locale/ar_001/LC_MESSAGE/messages.mo +0 -0
  29. psychopy/app/locale/ar_001/LC_MESSAGE/messages.po +4602 -2540
  30. psychopy/app/locale/es_CO/LC_MESSAGE/messages.mo +0 -0
  31. psychopy/app/locale/es_CO/LC_MESSAGE/messages.po +56 -54
  32. psychopy/app/locale/es_ES/LC_MESSAGE/messages.po +53 -43
  33. psychopy/app/locale/es_US/LC_MESSAGE/messages.mo +0 -0
  34. psychopy/app/locale/es_US/LC_MESSAGE/messages.po +56 -54
  35. psychopy/app/locale/ja_JP/LC_MESSAGE/messages.mo +0 -0
  36. psychopy/app/locale/ja_JP/LC_MESSAGE/messages.po +1258 -1176
  37. psychopy/app/locale/pt_PT/LC_MESSAGE/messages.po +9415 -5
  38. psychopy/app/pavlovia_ui/_base.py +33 -3
  39. psychopy/app/pavlovia_ui/search.py +0 -1
  40. psychopy/app/plugin_manager/dialog.py +104 -51
  41. psychopy/app/plugin_manager/packages.py +5 -0
  42. psychopy/app/plugin_manager/plugins.py +152 -67
  43. psychopy/app/preferencesDlg.py +8 -8
  44. psychopy/app/psychopyApp.py +11 -5
  45. psychopy/app/ribbon.py +124 -14
  46. psychopy/app/runner/runner.py +6 -1
  47. psychopy/app/stdout/stdOutRich.py +27 -11
  48. psychopy/app/themes/icons.py +52 -2
  49. psychopy/assets/__init__.py +0 -0
  50. psychopy/assets/click.png +0 -0
  51. psychopy/assets/clicknext.png +0 -0
  52. psychopy/assets/next.png +0 -0
  53. psychopy/assets/psychopy.ico +0 -0
  54. psychopy/assets/psychopy.png +0 -0
  55. psychopy/assets/templates/__init__.py +0 -0
  56. psychopy/assets/touch.png +0 -0
  57. psychopy/assets/touchnext.png +0 -0
  58. psychopy/assets/window.ico +0 -0
  59. psychopy/changes/2023.1.0.md +9 -0
  60. psychopy/changes/2024.1.0.md +16 -0
  61. psychopy/changes/__init__.py +0 -0
  62. psychopy/clock.py +2 -2
  63. psychopy/colors.py +2 -1
  64. psychopy/compatibility.py +53 -1
  65. psychopy/contrib/.DS_Store +0 -0
  66. psychopy/contrib/configobj/__init__.py +10 -8
  67. psychopy/data/__init__.py +3 -2
  68. psychopy/data/base.py +5 -5
  69. psychopy/data/experiment.py +130 -4
  70. psychopy/data/routine.py +56 -0
  71. psychopy/data/staircase.py +2 -2
  72. psychopy/data/trial.py +559 -97
  73. psychopy/data/utils.py +56 -21
  74. psychopy/demos/.DS_Store +0 -0
  75. psychopy/demos/builder/.DS_Store +0 -0
  76. psychopy/demos/builder/Design Templates/.DS_Store +0 -0
  77. psychopy/demos/builder/Experiments/.DS_Store +0 -0
  78. psychopy/demos/builder/Feature Demos/.DS_Store +0 -0
  79. psychopy/demos/builder/Feature Demos/buttonBox/buttonBoxDemo.psyexp +375 -0
  80. psychopy/demos/builder/Feature Demos/buttonBox/readme.md +5 -0
  81. psychopy/demos/builder/Feature Demos/pilotMode/pilotMode.psyexp +433 -0
  82. psychopy/demos/builder/Feature Demos/pilotMode/readme.md +7 -0
  83. psychopy/demos/builder/Feature Demos/progress/progressBar.psyexp +4 -4
  84. psychopy/demos/builder/Hardware/.DS_Store +0 -0
  85. psychopy/demos/builder/Helper Tools/.DS_Store +0 -0
  86. psychopy/demos/coder/.DS_Store +0 -0
  87. psychopy/demos/coder/hardware/testSoundLatency.py +2 -2
  88. psychopy/demos/coder/iohub/.DS_Store +0 -0
  89. psychopy/demos/coder/misc/hdf5_2_csv +33 -0
  90. psychopy/event.py +30 -29
  91. psychopy/experiment/.DS_Store +0 -0
  92. psychopy/experiment/_experiment.py +6 -6
  93. psychopy/experiment/components/.DS_Store +0 -0
  94. psychopy/experiment/components/__init__.py +6 -3
  95. psychopy/experiment/components/_base.py +286 -131
  96. psychopy/experiment/components/aperture/.DS_Store +0 -0
  97. psychopy/experiment/components/brush/.DS_Store +0 -0
  98. psychopy/experiment/components/button/.DS_Store +0 -0
  99. psychopy/experiment/components/button/__init__.py +5 -1
  100. psychopy/experiment/components/buttonBox/.DS_Store +0 -0
  101. psychopy/experiment/components/buttonBox/__init__.py +21 -12
  102. psychopy/experiment/components/camera/.DS_Store +0 -0
  103. psychopy/experiment/components/code/.DS_Store +0 -0
  104. psychopy/experiment/components/dots/.DS_Store +0 -0
  105. psychopy/experiment/components/eyetracker_record/.DS_Store +0 -0
  106. psychopy/experiment/components/eyetracker_record/__init__.py +92 -30
  107. psychopy/experiment/components/form/.DS_Store +0 -0
  108. psychopy/experiment/components/form/__init__.py +6 -2
  109. psychopy/experiment/components/grating/.DS_Store +0 -0
  110. psychopy/experiment/components/grating/__init__.py +14 -3
  111. psychopy/experiment/components/image/.DS_Store +0 -0
  112. psychopy/experiment/components/image/__init__.py +14 -3
  113. psychopy/experiment/components/joyButtons/.DS_Store +0 -0
  114. psychopy/experiment/components/joystick/.DS_Store +0 -0
  115. psychopy/experiment/components/keyboard/.DS_Store +0 -0
  116. psychopy/experiment/components/keyboard/__init__.py +22 -10
  117. psychopy/experiment/components/microphone/.DS_Store +0 -0
  118. psychopy/experiment/components/microphone/__init__.py +59 -39
  119. psychopy/experiment/components/mouse/.DS_Store +0 -0
  120. psychopy/experiment/components/mouse/__init__.py +44 -29
  121. psychopy/experiment/components/movie/.DS_Store +0 -0
  122. psychopy/experiment/components/movie/__init__.py +1 -1
  123. psychopy/experiment/components/panorama/.DS_Store +0 -0
  124. psychopy/experiment/components/parallelOut/.DS_Store +0 -0
  125. psychopy/experiment/components/patch/.DS_Store +0 -0
  126. psychopy/experiment/components/polygon/.DS_Store +0 -0
  127. psychopy/experiment/components/polygon/__init__.py +26 -6
  128. psychopy/experiment/components/progress/.DS_Store +0 -0
  129. psychopy/experiment/components/progress/__init__.py +1 -1
  130. psychopy/experiment/components/ratingScale/.DS_Store +0 -0
  131. psychopy/experiment/components/resourceManager/.DS_Store +0 -0
  132. psychopy/experiment/components/roi/.DS_Store +0 -0
  133. psychopy/experiment/components/roi/__init__.py +5 -0
  134. psychopy/experiment/components/routineSettings/.DS_Store +0 -0
  135. psychopy/experiment/components/routineSettings/__init__.py +57 -10
  136. psychopy/experiment/components/serialOut/.DS_Store +0 -0
  137. psychopy/experiment/components/settings/.DS_Store +0 -0
  138. psychopy/experiment/components/settings/__init__.py +117 -42
  139. psychopy/experiment/components/slider/.DS_Store +0 -0
  140. psychopy/experiment/components/sound/.DS_Store +0 -0
  141. psychopy/experiment/components/sound/__init__.py +54 -19
  142. psychopy/experiment/components/static/.DS_Store +0 -0
  143. psychopy/experiment/components/static/__init__.py +1 -1
  144. psychopy/experiment/components/text/.DS_Store +0 -0
  145. psychopy/experiment/components/text/__init__.py +28 -3
  146. psychopy/experiment/components/textbox/.DS_Store +0 -0
  147. psychopy/experiment/components/textbox/__init__.py +12 -2
  148. psychopy/experiment/components/unknown/.DS_Store +0 -0
  149. psychopy/experiment/components/unknown/__init__.py +1 -2
  150. psychopy/experiment/components/unknownPlugin/.DS_Store +0 -0
  151. psychopy/experiment/components/unknownPlugin/__init__.py +2 -2
  152. psychopy/experiment/components/variable/.DS_Store +0 -0
  153. psychopy/experiment/flow.py +11 -4
  154. psychopy/experiment/loops.py +85 -37
  155. psychopy/experiment/params.py +74 -32
  156. psychopy/experiment/py2js_transpiler.py +8 -1
  157. psychopy/experiment/routines/.DS_Store +0 -0
  158. psychopy/experiment/routines/_base.py +102 -22
  159. psychopy/experiment/routines/counterbalance/.DS_Store +0 -0
  160. psychopy/experiment/routines/counterbalance/__init__.py +5 -1
  161. psychopy/experiment/routines/eyetracker_calibrate/.DS_Store +0 -0
  162. psychopy/experiment/routines/eyetracker_validate/.DS_Store +0 -0
  163. psychopy/experiment/routines/pavlovia_survey/.DS_Store +0 -0
  164. psychopy/experiment/routines/photodiodeValidator/.DS_Store +0 -0
  165. psychopy/experiment/routines/photodiodeValidator/__init__.py +7 -6
  166. psychopy/experiment/routines/unknown/.DS_Store +0 -0
  167. psychopy/gui/wxgui.py +4 -4
  168. psychopy/hardware/.DS_Store +0 -0
  169. psychopy/hardware/__init__.py +1 -1
  170. psychopy/hardware/base.py +12 -0
  171. psychopy/hardware/camera/__init__.py +1 -15
  172. psychopy/hardware/cedrus.py +10 -11
  173. psychopy/hardware/crs/colorcal.py +13 -22
  174. psychopy/hardware/crs/optical.py +10 -20
  175. psychopy/hardware/emulator.py +17 -14
  176. psychopy/hardware/eyetracker.py +42 -118
  177. psychopy/hardware/gammasci.py +4 -15
  178. psychopy/hardware/keyboard.py +102 -11
  179. psychopy/hardware/listener.py +3 -0
  180. psychopy/hardware/microphone.py +148 -18
  181. psychopy/hardware/minolta.py +8 -15
  182. psychopy/hardware/photodiode.py +191 -16
  183. psychopy/hardware/photometer/__init__.py +11 -19
  184. psychopy/hardware/pr.py +8 -15
  185. psychopy/hardware/speaker.py +39 -4
  186. psychopy/info.py +0 -71
  187. psychopy/iohub/.DS_Store +0 -0
  188. psychopy/iohub/__init__.py +1 -1
  189. psychopy/iohub/client/__init__.py +30 -20
  190. psychopy/iohub/client/keyboard.py +24 -24
  191. psychopy/iohub/datastore/__init__.py +2 -2
  192. psychopy/iohub/datastore/util.py +2 -2
  193. psychopy/iohub/default_config.yaml +1 -1
  194. psychopy/iohub/devices/.DS_Store +0 -0
  195. psychopy/iohub/devices/__init__.py +112 -25
  196. psychopy/iohub/devices/deviceConfigValidation.py +2 -1
  197. psychopy/iohub/devices/experiment/default_experiment.yaml +12 -1
  198. psychopy/iohub/devices/experiment/supported_config_settings.yaml +5 -1
  199. psychopy/iohub/devices/eyetracker/.DS_Store +0 -0
  200. psychopy/iohub/devices/eyetracker/__init__.py +46 -0
  201. psychopy/iohub/devices/eyetracker/calibration/procedure.py +2 -2
  202. psychopy/iohub/devices/eyetracker/hw/gazepoint/__init__.py +14 -2
  203. psychopy/iohub/devices/eyetracker/hw/mouse/eyetracker.py +3 -4
  204. psychopy/iohub/server.py +2 -2
  205. psychopy/iohub/start_iohub_process.py +3 -0
  206. psychopy/iohub/util/__init__.py +62 -70
  207. psychopy/layout.py +5 -5
  208. psychopy/logging.py +8 -1
  209. psychopy/microphone.py +10 -37
  210. psychopy/platform_specific/__init__.py +0 -2
  211. psychopy/platform_specific/darwin.py +1 -3
  212. psychopy/platform_specific/linux.py +31 -33
  213. psychopy/platform_specific/win32.py +38 -13
  214. psychopy/plugins/__init__.py +148 -116
  215. psychopy/plugins/util.py +39 -0
  216. psychopy/preferences/Darwin.spec +4 -2
  217. psychopy/preferences/FreeBSD.spec +4 -2
  218. psychopy/preferences/Linux.spec +4 -2
  219. psychopy/preferences/Windows.spec +4 -2
  220. psychopy/preferences/baseNoArch.spec +4 -2
  221. psychopy/preferences/preferences.py +47 -24
  222. psychopy/projects/pavlovia.py +47 -4
  223. psychopy/scripts/psyexpCompile.py +0 -4
  224. psychopy/session.py +153 -21
  225. psychopy/sound/__init__.py +31 -21
  226. psychopy/sound/_base.py +20 -3
  227. psychopy/sound/audioclip.py +320 -33
  228. psychopy/sound/backend_ptb.py +47 -58
  229. psychopy/sound/backend_pygame.py +1 -1
  230. psychopy/sound/backend_pysound.py +6 -15
  231. psychopy/sound/transcribe.py +53 -0
  232. psychopy/tests/.DS_Store +0 -0
  233. psychopy/tests/data/.DS_Store +0 -0
  234. psychopy/tests/data/TestUnknownPluginComponent_load_resave.psyexp +135 -0
  235. psychopy/tests/data/Test_textbox/test_ori_0_bottom right.png +0 -0
  236. psychopy/tests/data/Test_textbox/test_ori_0_center.png +0 -0
  237. psychopy/tests/data/Test_textbox/test_ori_0_top left.png +0 -0
  238. psychopy/tests/data/Test_textbox/test_ori_120_bottom right.png +0 -0
  239. psychopy/tests/data/Test_textbox/test_ori_120_center.png +0 -0
  240. psychopy/tests/data/Test_textbox/test_ori_120_top left.png +0 -0
  241. psychopy/tests/data/Test_textbox/test_ori_180_bottom right.png +0 -0
  242. psychopy/tests/data/Test_textbox/test_ori_180_center.png +0 -0
  243. psychopy/tests/data/Test_textbox/test_ori_180_top left.png +0 -0
  244. psychopy/tests/data/Test_textbox/test_ori_240_bottom right.png +0 -0
  245. psychopy/tests/data/Test_textbox/test_ori_240_center.png +0 -0
  246. psychopy/tests/data/Test_textbox/test_ori_240_top left.png +0 -0
  247. psychopy/tests/data/correctScript/.DS_Store +0 -0
  248. psychopy/tests/data/test_components/testClearKeyboard/testClearKeyboard.psyexp +200 -0
  249. psychopy/tests/data/test_session/.DS_Store +0 -0
  250. psychopy/tests/data/test_session/root/testFutureTrials/testFutureTrials.psyexp +155 -0
  251. psychopy/tests/data/test_session/root/testTrialNav/trialNav.psyexp +158 -0
  252. psychopy/tests/test_app/.DS_Store +0 -0
  253. psychopy/tests/test_app/conftest.py +2 -2
  254. psychopy/tests/test_app/test_speed.py +4 -1
  255. psychopy/tests/test_data/test_TrialHandler2.py +146 -1
  256. psychopy/tests/test_experiment/.DS_Store +0 -0
  257. psychopy/tests/test_experiment/needs_wx/genComponsTemplate.py +3 -3
  258. psychopy/tests/test_experiment/needs_wx/test_components.py +2 -2
  259. psychopy/tests/test_experiment/test_components/test_KeyboardComponent.py +28 -0
  260. psychopy/tests/test_experiment/test_components/test_UnknownPluginComponent.py +27 -0
  261. psychopy/tests/test_experiment/test_components/test_base_components.py +58 -0
  262. psychopy/tests/test_experiment/test_py2js.py +1 -1
  263. psychopy/tests/test_hardware/test_keyboard.py +184 -16
  264. psychopy/tests/test_hardware/test_ports.py +1 -11
  265. psychopy/tests/test_liaison/test_Liaison.py +47 -0
  266. psychopy/tests/test_misc/test_core.py +5 -0
  267. psychopy/tests/test_session/test_Session.py +5 -1
  268. psychopy/tests/test_tools/test_versionchooser.py +39 -8
  269. psychopy/tests/test_visual/test_all_stimuli.py +0 -97
  270. psychopy/tests/test_visual/test_image.py +6 -5
  271. psychopy/tests/test_visual/test_textbox.py +36 -0
  272. psychopy/tests/utils.py +4 -0
  273. psychopy/tools/filetools.py +1 -1
  274. psychopy/tools/pkgtools.py +160 -137
  275. psychopy/tools/versionchooser.py +10 -10
  276. psychopy/tools/wizard.py +3 -3
  277. psychopy/visual/.DS_Store +0 -0
  278. psychopy/visual/backends/pygletbackend.py +24 -13
  279. psychopy/visual/basevisual.py +5 -11
  280. psychopy/visual/button.py +2 -14
  281. psychopy/visual/helpers.py +5 -5
  282. psychopy/visual/line.py +1 -2
  283. psychopy/visual/movie2.py +7 -816
  284. psychopy/visual/movie3.py +7 -589
  285. psychopy/visual/movies/__init__.py +8 -11
  286. psychopy/visual/movies/frame.py +5 -2
  287. psychopy/visual/movies/players/ffpyplayer_player.py +5 -2
  288. psychopy/visual/noise.py +8 -7
  289. psychopy/visual/patch.py +7 -16
  290. psychopy/visual/progress.py +1 -1
  291. psychopy/visual/radial.py +9 -7
  292. psychopy/visual/ratingscale.py +8 -1415
  293. psychopy/visual/secondorder.py +10 -9
  294. psychopy/visual/shape.py +7 -2
  295. psychopy/visual/text.py +1 -1
  296. psychopy/visual/textbox2/textbox2.py +28 -5
  297. psychopy/web.py +5 -2
  298. {psychopy-2024.1.3.dist-info → psychopy-2024.2.0.dist-info}/METADATA +8 -13
  299. {psychopy-2024.1.3.dist-info → psychopy-2024.2.0.dist-info}/RECORD +313 -219
  300. {psychopy-2024.1.3.dist-info → psychopy-2024.2.0.dist-info}/WHEEL +1 -1
  301. psychopy/app/Resources/click.png +0 -0
  302. psychopy/app/Resources/next.png +0 -0
  303. psychopy/experiment/components/patch/__init__.py +0 -121
  304. psychopy/experiment/components/patch/classic/patch.png +0 -0
  305. psychopy/experiment/components/patch/dark/patch.png +0 -0
  306. psychopy/experiment/components/patch/dark/patch@2x.png +0 -0
  307. psychopy/experiment/components/patch/light/patch.png +0 -0
  308. psychopy/experiment/components/patch/light/patch@2x.png +0 -0
  309. psychopy/experiment/components/ratingScale/__init__.py +0 -337
  310. psychopy/experiment/components/ratingScale/classic/ratingscale.png +0 -0
  311. psychopy/experiment/components/ratingScale/classic/ratingscale@2x.png +0 -0
  312. psychopy/experiment/components/ratingScale/dark/ratingScale@2x.png +0 -0
  313. psychopy/experiment/components/ratingScale/dark/ratingscale.png +0 -0
  314. psychopy/experiment/components/ratingScale/light/ratingScale@2x.png +0 -0
  315. psychopy/experiment/components/ratingScale/light/ratingscale.png +0 -0
  316. psychopy/platform_specific/posix.py +0 -16
  317. psychopy/tests/test_sound/test_microphone.py +0 -217
  318. psychopy/tests/test_visual/test_ratingScale.py +0 -299
  319. /psychopy/{app/Resources → assets}/Psychopy Window Favicon@16w.png +0 -0
  320. /psychopy/{app/Resources → assets}/Psychopy Window Favicon@32w.png +0 -0
  321. /psychopy/{app/Resources → assets}/USB-C.png +0 -0
  322. /psychopy/{app/Resources → assets}/USB.png +0 -0
  323. /psychopy/{app/Resources → assets}/creditCard.png +0 -0
  324. /psychopy/{app/Resources → assets}/default.mp3 +0 -0
  325. /psychopy/{app/Resources → assets}/default.mp4 +0 -0
  326. /psychopy/{app/Resources → assets}/default.png +0 -0
  327. /psychopy/{app/Resources → assets/templates}/instruct1.png +0 -0
  328. /psychopy/{app/Resources → assets/templates}/instruct2.png +0 -0
  329. {psychopy-2024.1.3.dist-info → psychopy-2024.2.0.dist-info}/entry_points.txt +0 -0
  330. {psychopy-2024.1.3.dist-info → psychopy-2024.2.0.dist-info}/licenses/AUTHORS.md +0 -0
  331. {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
- if __name__ == "__main__":
599
- pass
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['resources']) / defaultStim[filename]
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.status == NOT_STARTED:
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 * 3
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.GL_RGB8,
699
+ GL.GL_RGBA8,
700
700
  vidWidth, vidHeight, # frame dims in pixels
701
701
  0,
702
- GL.GL_RGB,
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 * 3
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.GL_RGB,
777
- GL.GL_UNSIGNED_BYTE,
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
@@ -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}, "