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/movie2.py CHANGED
@@ -1,62 +1,5 @@
1
1
  #!/usr/bin/env python
2
2
  # -*- coding: utf-8 -*-
3
-
4
- """
5
- A stimulus class for playing movies (mpeg, avi, etc...) in PsychoPy.
6
- Demo using the experimental movie2 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
- Movie2 does require:
11
- ~~~~~~~~~~~~~~~~~~~~~
12
-
13
- 1. Python OpenCV package (so openCV libs and the cv2 python interface).
14
- *. For Windows, a binary installer is available at
15
- http://www.lfd.uci.edu/~gohlke/pythonlibs/#opencv
16
- *. For Linux, it is available via whatever package manager you use.
17
- *. For OSX, ..... ?
18
- 2. VLC application. Just install the standard VLC (32bit) for your OS.
19
- http://www.videolan.org/vlc/index.html
20
-
21
- To play a video, you /must/:
22
- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
23
-
24
- a. Create a visual.MovieStim2(..) instance; pretend it is called mov.
25
- b. Call mov.play() when you want to start playing the video.
26
- c. Call win.flip(), which will display the first frame of the video.
27
- d. In the experiment loop, call mov.draw() followed by win.flip() to draw
28
- the video frame again mov.draw() determines if the current frame,
29
- or the next frame should be redrawn and does so accordingly. If the next
30
- frame is drawn, mov.draw() will return the frame index just drawn. If
31
- the same frame is drawn as before, None is returned.
32
-
33
- This method call sequence must be followed. This should be improved (I think)
34
- depending on how movie stim calls are actually made. The current movie stim
35
- code doc's seem a bit mixed in message.
36
-
37
- Current known issues:
38
- ~~~~~~~~~~~~~~~~~~~~~~
39
-
40
- 1. Loop functionality are known to be broken at this time.
41
- 2. Auto draw not implemented.
42
- 3. Video must have 3 color channels.
43
- 4. Intentional Frame dropping (to keep video playing at expected rate
44
- on slow machines) is not yet implemented.
45
-
46
- What does work so far:
47
- ~~~~~~~~~~~~~~~~~~~~~~~~~
48
-
49
- 1. mov.setMovie(filename) / mov.loadMovie(filename)
50
- 2. mov.play()
51
- 3. mov.pause()
52
- 4. mov.seek()
53
- 4. mov.stop()
54
- 5. mov.set/getVolume()
55
- 6. Standard BaseVisualStim, ContainerMixin methods, unless noted above.
56
-
57
- Testing has only been done on Windows and Linux so far.
58
- """
59
-
60
3
  # Part of the PsychoPy library
61
4
  # Copyright (C) 2002-2018 Jonathan Peirce (C) 2019-2024 Open Science Tools Ltd.
62
5
  # Distributed under the terms of the GNU General Public License (GPL).
@@ -66,764 +9,12 @@ Testing has only been done on Windows and Linux so far.
66
9
  # of avbin
67
10
 
68
11
 
12
+ from psychopy.tools.pkgtools import PluginStub
69
13
 
70
14
 
71
- # If True then, on each flip a new movie frame is displayed, the frame index,
72
- # flip time, and time since last movie frame flip will be printed
73
- reportNDroppedFrames = 10
74
-
75
- import os
76
- import sys
77
- import weakref # don't create circular references with vlc classes
78
-
79
- # Ensure setting pyglet.options['debug_gl'] to False is done prior to any
80
- # other calls to pyglet or pyglet submodules, otherwise it may not get picked
81
- # up by the pyglet GL engine and have no effect.
82
- # Shaders will work but require OpenGL2.0 drivers AND PyOpenGL3.0+
83
- import pyglet
84
- pyglet.options['debug_gl'] = False
85
- GL = pyglet.gl
86
-
87
- import psychopy # so we can get the __path__
88
- from psychopy import core, logging
89
-
90
- from psychopy.tools.arraytools import val2array
91
- from psychopy.tools.attributetools import logAttrib, setAttribute
92
- from psychopy.tools.filetools import pathToString
93
- from psychopy.visual.basevisual import BaseVisualStim, ContainerMixin
94
- from psychopy.clock import Clock
95
- from psychopy.constants import FINISHED, NOT_STARTED, PAUSED, PLAYING, STOPPED
96
-
97
- import ctypes
98
- import numpy
99
- import cv2
100
- if hasattr(cv2, 'cv'):
101
- # as of version 3 these ar in cv2 not cv2.cv
102
- cv2.CAP_PROP_FRAME_COUNT = cv2.cv.CV_CAP_PROP_FRAME_COUNT
103
- cv2.CAP_PROP_FRAME_WIDTH = cv2.cv.CV_CAP_PROP_FRAME_WIDTH
104
- cv2.CAP_PROP_FRAME_HEIGHT = cv2.cv.CV_CAP_PROP_FRAME_HEIGHT
105
- cv2.CAP_PROP_FORMAT = cv2.cv.CV_CAP_PROP_FORMAT
106
- cv2.CAP_PROP_FPS = cv2.cv.CV_CAP_PROP_FPS
107
- cv2.CAP_PROP_POS_MSEC = cv2.cv.CV_CAP_PROP_POS_MSEC
108
- cv2.CAP_PROP_POS_FRAMES = cv2.cv.CV_CAP_PROP_POS_FRAMES
109
- cv2.CAP_PROP_POS_AVI_RATIO = cv2.cv.CV_CAP_PROP_POS_AVI_RATIO
110
-
111
- try:
112
- import vlc
113
- except Exception as err:
114
- if sys.maxsize == 9223372036854775807:
115
- bits = 64
116
- else:
117
- bits = 32
118
- if "wrong architecture" in str(err):
119
- msg = ("Failed to import vlc module for MovieStim2.\n"
120
- "You're using %i-bit python. Is your VLC install the same?"
121
- % bits)
122
- raise OSError(msg)
123
- else:
124
- raise err
125
-
126
-
127
- # these are used internally by the MovieStim2 class but need to be kept
128
- # separate to prevent circular references with vlc's event handler
129
-
130
-
131
- def _audioEndCallback(event, movieInstanceRef):
132
- movieInstanceRef()._onEos()
133
-
134
-
135
- def _audioTimeCallback(event, movieInstanceRef, streamPlayer):
136
- """
137
- Called by VLC every few hundred msec providing the current audio track
138
- time. This info is used to pace the display of video frames read using
139
- cv2.
140
- """
141
- if movieInstanceRef():
142
- tm = -event.u.new_time/1000.0
143
- movieInstanceRef()._audio_stream_clock.reset(tm)
144
-
145
-
146
- def _setPluginPathEnviron():
147
- """Plugins aren't in the same path as the libvlc.dylib
148
- """
149
- if 'VLC_PLUGIN_PATH' in os.environ:
150
- return
151
- dllPath = vlc.dll._name
152
- from os.path import split, join
153
- # try stepping back from dll path and adding 'plugins'
154
- # (2 steps on OSX, 1 on win32?)
155
- nSteps = 0
156
- last = dllPath
157
- while nSteps < 4:
158
- if last is None:
159
- return 0
160
- last = split(last)[0]
161
- pluginPath = join(last, 'plugins')
162
- if os.path.isdir(pluginPath):
163
- os.environ['VLC_PLUGIN_PATH'] = pluginPath
164
- return 1
165
- nSteps += 1
166
- # if we got here we never found a path
167
- return 0
168
-
169
- OK = _setPluginPathEnviron()
170
- if not OK:
171
- logging.warn("Failed to set VLC plugins path. This is only important for "
172
- "MovieStim2 movies (the OpenCV backend)")
173
-
174
-
175
- class MovieStim2(BaseVisualStim, ContainerMixin):
176
- """A stimulus class for playing movies (mpeg, avi, etc...) in PsychoPy
177
- that does not require avbin. Instead it requires the cv2 python package
178
- for OpenCV. The VLC media player also needs to be installed on the
179
- psychopy computer. This is a lazy-imported class, therefore import using
180
- full path `from psychopy.visual.movie2 import MovieStim2` when
181
- inheriting from it.
182
-
183
- **Example**::
184
-
185
- See Movie2Stim.py for demo.
186
- """
187
-
188
- def __init__(self, win,
189
- filename="",
190
- units='pix',
191
- size=None,
192
- pos=(0.0, 0.0),
193
- anchor="center",
194
- ori=0.0,
195
- flipVert=False,
196
- flipHoriz=False,
197
- color=(1.0, 1.0, 1.0),
198
- colorSpace='rgb',
199
- opacity=1.0,
200
- volume=1.0,
201
- name='',
202
- loop=False,
203
- autoLog=True,
204
- depth=0.0,
205
- noAudio=False,
206
- vframe_callback=None,
207
- fps=None,
208
- interpolate=True):
209
- """
210
- :Parameters:
211
-
212
- filename :
213
- a string giving the relative or absolute path to the movie.
214
- flipVert : True or *False*
215
- If True then the movie will be top-bottom flipped
216
- flipHoriz : True or *False*
217
- If True then the movie will be right-left flipped
218
- volume :
219
- The nominal level is 100, and 0 is silence.
220
- loop : bool, optional
221
- Whether to start the movie over from the beginning if draw is
222
- called and the movie is done.
223
-
224
- """
225
- # what local vars are defined (these are the init params) for use
226
- # by __repr__
227
- self._initParams = dir()
228
- self._initParams.remove('self')
229
- super(MovieStim2, self).__init__(win, units=units, name=name,
230
- autoLog=False)
231
- # check for pyglet
232
- if win.winType != 'pyglet':
233
- logging.error(
234
- 'Movie stimuli can only be used with a pyglet window')
235
- core.quit()
236
- self._retracerate = win._monitorFrameRate
237
- # if self._retracerate is None:
238
- # self._retracerate = win.getActualFrameRate()
239
- if self._retracerate is None:
240
- logging.warning("FrameRate could not be supplied by psychopy; "
241
- "defaulting to 60.0")
242
- self._retracerate = 60.0
243
- self.filename = pathToString(filename)
244
- self.loop = loop
245
- self.flipVert = flipVert
246
- self.flipHoriz = flipHoriz
247
- self.pos = numpy.asarray(pos, float)
248
- self.anchor = anchor
249
- self.depth = depth
250
- self.opacity = float(opacity)
251
- self.volume = volume
252
- self._av_stream_time_offset = 0.145
253
- self._no_audio = noAudio
254
- self._vframe_callback = vframe_callback
255
- self.interpolate = interpolate
256
-
257
- self.useTexSubImage2D = True
258
-
259
- self._texID = None
260
- self._video_stream = cv2.VideoCapture()
261
-
262
- self._reset()
263
- self.loadMovie(self.filename)
264
- self.setVolume(volume)
265
- self.nDroppedFrames = 0
266
-
267
- self.aspectRatio = self._video_width/float(self._video_height)
268
- # size
269
- if size is None:
270
- self.size = numpy.array([self._video_width, self._video_height],
271
- float)
272
- elif isinstance(size, (int, float, int)):
273
- # treat size as desired width, and calc a height
274
- # that maintains the aspect ratio of the video.
275
- self.size = numpy.array([size, size/self.aspectRatio], float)
276
- else:
277
- self.size = val2array(size)
278
- self.ori = ori
279
- self._updateVertices()
280
- # set autoLog (now that params have been initialised)
281
- self.autoLog = autoLog
282
- if autoLog:
283
- logging.exp("Created {} = {}".format(self.name, self))
284
-
285
- def _reset(self):
286
- self.duration = None
287
- self.status = NOT_STARTED
288
- self._numpy_frame = None
289
- if self._texID is not None:
290
- GL.glDeleteTextures(1, self._texID)
291
- self._texID = None
292
- # self._video_stream = None
293
- self._total_frame_count = None
294
- self._video_width = None
295
- self._video_height = None
296
- # TODO: Read depth from video source
297
- self._video_frame_depth = 3
298
- self._video_frame_rate = None
299
- self._inter_frame_interval = None
300
- self._prev_frame_sec = None
301
- self._next_frame_sec = None
302
- self._next_frame_index = None
303
- self._prev_frame_index = None
304
- self._video_perc_done = None
305
- # self._last_video_flip_time = None
306
- self._next_frame_displayed = False
307
- self._video_track_clock = Clock()
308
-
309
- self._audio_stream_clock = Clock()
310
- self._vlc_instance = None
311
- self._audio_stream = None
312
- self._audio_stream_player = None
313
- self._audio_stream_started = False
314
- self._audio_stream_event_manager = None
315
-
316
- def setMovie(self, filename, log=True):
317
- """See `~MovieStim.loadMovie` (the functions are identical).
318
-
319
- This form is provided for syntactic consistency with other
320
- visual stimuli.
321
- """
322
- self.loadMovie(filename, log=log)
323
-
324
- def loadMovie(self, filename, log=True):
325
- """Load a movie from file
326
-
327
- :Parameters:
328
-
329
- filename: string
330
- The name of the file, including path if necessary
331
-
332
-
333
- After the file is loaded MovieStim.duration is updated with the movie
334
- duration (in seconds).
335
- """
336
- filename = pathToString(filename)
337
- self._unload()
338
- self._reset()
339
- if self._no_audio is False:
340
- self._createAudioStream()
341
-
342
- # Create Video Stream stuff
343
- self._video_stream.open(filename)
344
- vfstime = core.getTime()
345
- opened = self._video_stream.isOpened()
346
- if not opened and core.getTime() - vfstime < 1:
347
- raise RuntimeError("Error when reading image file")
348
-
349
- if not opened:
350
- raise RuntimeError("Error when reading image file")
351
-
352
- self._total_frame_count = self._video_stream.get(
353
- cv2.CAP_PROP_FRAME_COUNT)
354
- self._video_width = int(self._video_stream.get(
355
- cv2.CAP_PROP_FRAME_WIDTH))
356
- self._video_height = int(self._video_stream.get(
357
- cv2.CAP_PROP_FRAME_HEIGHT))
358
- self._format = self._video_stream.get(
359
- cv2.CAP_PROP_FORMAT)
360
- # TODO: Read depth from video source
361
- self._video_frame_depth = 3
362
-
363
- cv_fps = self._video_stream.get(cv2.CAP_PROP_FPS)
364
-
365
- self._video_frame_rate = cv_fps
366
-
367
- self._inter_frame_interval = 1.0/self._video_frame_rate
368
-
369
- # Create a numpy array that can hold one video frame, as returned by
370
- # cv2.
371
- self._numpy_frame = numpy.zeros((self._video_height,
372
- self._video_width,
373
- self._video_frame_depth),
374
- dtype=numpy.uint8)
375
- self.duration = self._total_frame_count * self._inter_frame_interval
376
- self.status = NOT_STARTED
377
-
378
- self.filename = filename
379
- logAttrib(self, log, 'movie', filename)
380
-
381
- def _createAudioStream(self):
382
- """
383
- Create the audio stream player for the video using pyvlc.
384
- """
385
- if not os.access(self.filename, os.R_OK):
386
- raise RuntimeError('Error: %s file not readable' % self.filename)
387
- self._vlc_instance = vlc.Instance('--novideo')
388
- try:
389
- self._audio_stream = self._vlc_instance.media_new(self.filename)
390
- except NameError:
391
- msg = 'NameError: %s vs LibVLC %s'
392
- raise ImportError(msg % (vlc.__version__,
393
- vlc.libvlc_get_version()))
394
- self._audio_stream_player = self._vlc_instance.media_player_new()
395
- self._audio_stream_player.set_media(self._audio_stream)
396
- self._audio_stream_event_manager = self._audio_stream_player.event_manager()
397
- self._audio_stream_event_manager.event_attach(
398
- vlc.EventType.MediaPlayerTimeChanged, _audioTimeCallback,
399
- weakref.ref(self), self._audio_stream_player)
400
- self._audio_stream_event_manager.event_attach(
401
- vlc.EventType.MediaPlayerEndReached, _audioEndCallback,
402
- weakref.ref(self))
403
-
404
- def _releaseAudioStream(self):
405
- if self._audio_stream_player:
406
- self._audio_stream_player.stop()
407
-
408
- if self._audio_stream_event_manager:
409
- self._audio_stream_event_manager.event_detach(
410
- vlc.EventType.MediaPlayerTimeChanged)
411
- self._audio_stream_event_manager.event_detach(
412
- vlc.EventType.MediaPlayerEndReached)
413
-
414
- if self._audio_stream:
415
- self._audio_stream.release()
416
-
417
- if self._vlc_instance:
418
- self._vlc_instance.release()
419
-
420
- self._audio_stream = None
421
- self._audio_stream_event_manager = None
422
- self._audio_stream_player = None
423
- self._vlc_instance = None
424
-
425
- def _flipCallback(self):
426
- self._next_frame_displayed = True
427
-
428
- def play(self, log=True):
429
- """Continue a paused movie from current position.
430
- """
431
- cstat = self.status
432
- if cstat != PLAYING:
433
- self.status = PLAYING
434
-
435
- if self._next_frame_sec is None:
436
- # movie has no current position, need to reset the clock
437
- # to zero in order to have the timing logic work
438
- # otherwise the video stream would skip frames until the
439
- # time since creating the movie object has passed
440
- self._video_track_clock.reset()
441
-
442
- if cstat == PAUSED:
443
- # toggle audio pause
444
- if self._audio_stream_player:
445
- self._audio_stream_player.pause()
446
- self._audio_stream_clock.reset(
447
- -self._audio_stream_player.get_time()/1000.0)
448
- if self._next_frame_sec:
449
- self._video_track_clock.reset(-self._next_frame_sec)
450
- else:
451
- nt = self._getNextFrame()
452
- self._video_track_clock.reset(-nt)
453
-
454
- if log and self.autoLog:
455
- self.win.logOnFlip("Set %s playing" % (self.name),
456
- level=logging.EXP, obj=self)
457
-
458
- self._updateFrameTexture()
459
- self.win.callOnFlip(self._flipCallback)
460
- return self._next_frame_index
461
-
462
- def pause(self, log=True):
463
- """Pause the current point in the movie (sound will stop, current
464
- frame will not advance). If play() is called again both will restart.
465
- """
466
- if self.status == PLAYING:
467
- self.status = PAUSED
468
- player = self._audio_stream_player
469
- if player and player.can_pause():
470
- player.pause()
471
- if log and self.autoLog:
472
- self.win.logOnFlip("Set %s paused" % self.name,
473
- level=logging.EXP, obj=self)
474
- return True
475
- if log and self.autoLog:
476
- self.win.logOnFlip("Failed Set %s paused" % self.name,
477
- level=logging.EXP, obj=self)
478
- return False
479
-
480
- def stop(self, log=True):
481
- """Stop the current point in the movie (sound will stop,
482
- current frame will not advance). Once stopped the movie cannot
483
- be restarted - it must be loaded again.
484
-
485
- Use pause() if you may need to restart the movie.
486
- """
487
- if self.status != STOPPED:
488
- self.status = STOPPED
489
- self._unload()
490
- self._reset()
491
- if log and self.autoLog:
492
- self.win.logOnFlip("Set %s stopped" % (self.name),
493
- level=logging.EXP, obj=self)
494
-
495
- def seek(self, timestamp, log=True):
496
- """Seek to a particular timestamp in the movie.
497
- """
498
- if self.status in [PLAYING, PAUSED]:
499
- if timestamp > 0.0:
500
- if self.status == PLAYING:
501
- self.pause()
502
- player = self._audio_stream_player
503
- if player and player.is_seekable():
504
- player.set_time(int(timestamp * 1000.0))
505
- self._audio_stream_clock.reset(-timestamp)
506
-
507
- MSEC = cv2.CAP_PROP_POS_MSEC
508
- FRAMES = cv2.CAP_PROP_POS_FRAMES
509
- self._video_stream.set(MSEC, timestamp * 1000.0)
510
- self._video_track_clock.reset(-timestamp)
511
- self._next_frame_index = self._video_stream.get(FRAMES)
512
- self._next_frame_sec = self._video_stream.get(MSEC)/1000.0
513
- else:
514
- self.stop()
515
- self.loadMovie(self.filename)
516
- if log:
517
- logAttrib(self, log, 'seek', timestamp)
518
-
519
- self.play()
520
-
521
- def setFlipHoriz(self, newVal=True, log=True):
522
- """If set to True then the movie will be flipped horizontally
523
- (left-to-right). Note that this is relative to the original,
524
- not relative to the current state.
525
- """
526
- self.flipHoriz = newVal
527
- logAttrib(self, log, 'flipHoriz')
528
-
529
- def setFlipVert(self, newVal=True, log=True):
530
- """If set to True then the movie will be flipped vertically
531
- (top-to-bottom). Note that this is relative to the original,
532
- not relative to the current state.
533
- """
534
- self.flipVert = not newVal
535
- logAttrib(self, log, 'flipVert')
536
-
537
- def setVolume(self, v):
538
- """Set the audio track volume. 0 = mute, 100 = 0 dB. float values
539
- between 0.0 and 1.0 are also accepted, and scaled to an int
540
- between 0 and 100.
541
- """
542
- if self._audio_stream_player:
543
- if 0.0 <= v <= 1.0 and isinstance(v, float):
544
- v = int(v * 100)
545
- else:
546
- v = int(v)
547
- self.volume = v
548
- if self._audio_stream_player:
549
- self._audio_stream_player.audio_set_volume(v)
550
-
551
- def getVolume(self):
552
- """Returns the current movie audio volume.
553
-
554
- 0 is no audio, 100 is max audio volume.
555
- """
556
- if self._audio_stream_player:
557
- self.volume = self._audio_stream_player.audio_get_volume()
558
- return self.volume
559
-
560
- def getFPS(self):
561
- """
562
- Returns the movie frames per second playback speed.
563
- """
564
- return self._video_frame_rate
565
-
566
- def getTimeToNextFrameDraw(self):
567
- """Get the number of sec.msec remaining until the next
568
- movie video frame should be drawn.
569
- """
570
- try:
571
- _tm = self._video_track_clock.getTime()
572
- return self._next_frame_sec - 1.0/self._retracerate - _tm
573
- except Exception:
574
- logging.warning("MovieStim2.getTimeToNextFrameDraw failed.")
575
- return 0.0
576
-
577
- def shouldDrawVideoFrame(self):
578
- """True if the next movie frame should be drawn,
579
- False if it is not yet time. See getTimeToNextFrameDraw().
580
- """
581
- return self.getTimeToNextFrameDraw() <= 0.0
582
-
583
- def getCurrentFrameNumber(self):
584
- """Get the current movie frame number.
585
- The first frame number in a file is 1.
586
- """
587
- return self._next_frame_index
588
-
589
- def getCurrentFrameTime(self):
590
- """Get the time that the movie file specified the current
591
- video frame as having.
592
- """
593
- return self._next_frame_sec
594
-
595
- def getPercentageComplete(self):
596
- """Provides a value between 0.0 and 100.0, indicating the
597
- amount of the movie that has been already played.
598
- """
599
- return self._video_perc_done
600
-
601
- def isCurrentFrameVisible(self):
602
- """The current video frame goes through two stages;
603
- the first being when the movie frame is being loaded,
604
- but is not visible on the display.
605
- The second is when the frame has actually been presented
606
- on the display. Returns False if the frame is in the first stage,
607
- True when in stage 2.
608
- """
609
- return self._next_frame_displayed
610
-
611
- def _getNextFrame(self):
612
- """get next frame info ( do not decode frame yet)
613
- """
614
- while self.status == PLAYING:
615
- if self._video_stream.grab():
616
- self._prev_frame_index = self._next_frame_index
617
- self._prev_frame_sec = self._next_frame_sec
618
- self._next_frame_index = self._video_stream.get(
619
- cv2.CAP_PROP_POS_FRAMES)
620
- self._next_frame_sec = self._video_stream.get(
621
- cv2.CAP_PROP_POS_MSEC)/1000.0
622
- self._video_perc_done = self._video_stream.get(
623
- cv2.CAP_PROP_POS_AVI_RATIO)
624
- self._next_frame_displayed = False
625
- halfInterval = self._inter_frame_interval/2.0
626
- if self.getTimeToNextFrameDraw() > -halfInterval:
627
- return self._next_frame_sec
628
- else:
629
- self.nDroppedFrames += 1
630
- if self.nDroppedFrames < reportNDroppedFrames:
631
- msg = "MovieStim2 dropping video frame index: %d"
632
- logging.warning(msg % self._next_frame_index)
633
- elif self.nDroppedFrames == reportNDroppedFrames:
634
- msg = ("Multiple Movie frames have occurred - "
635
- "I'll stop bothering you about them!")
636
- logging.warning(msg)
637
- else:
638
- self._onEos()
639
- break
640
-
641
- def _updateFrameTexture(self):
642
- """Decode frame into np array and move to opengl tex.
643
- """
644
- ret, self._numpy_frame = self._video_stream.retrieve()
645
- if ret:
646
- useSubTex = self.useTexSubImage2D
647
- if self._texID is None:
648
- self._texID = GL.GLuint()
649
- GL.glGenTextures(1, ctypes.byref(self._texID))
650
- useSubTex = False
651
-
652
- # bind the texture in openGL
653
- GL.glEnable(GL.GL_TEXTURE_2D)
654
- # bind that name to the target
655
- GL.glBindTexture(GL.GL_TEXTURE_2D, self._texID)
656
- # don't allow a movie texture to wrap around
657
- GL.glTexParameteri(
658
- GL.GL_TEXTURE_2D, GL.GL_TEXTURE_WRAP_S, GL.GL_CLAMP)
659
- GL.glTexParameteri(
660
- GL.GL_TEXTURE_2D, GL.GL_TEXTURE_WRAP_T, GL.GL_CLAMP)
661
- # data from PIL/numpy is packed, but default for GL is 4 bytes
662
- GL.glPixelStorei(GL.GL_UNPACK_ALIGNMENT, 1)
663
- # important if using bits++ because GL_LINEAR
664
- # sometimes extrapolates to pixel vals outside range
665
- if self.interpolate:
666
- GL.glTexParameteri(
667
- GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MAG_FILTER, GL.GL_LINEAR)
668
- GL.glTexParameteri(
669
- GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MIN_FILTER, GL.GL_LINEAR)
670
- if useSubTex is False:
671
- GL.glTexImage2D(GL.GL_TEXTURE_2D, 0, pyglet.gl.GL_RGB8,
672
- self._numpy_frame.shape[1],
673
- self._numpy_frame.shape[0], 0,
674
- GL.GL_BGR, GL.GL_UNSIGNED_BYTE,
675
- self._numpy_frame.ctypes)
676
- else:
677
- GL.glTexSubImage2D(GL.GL_TEXTURE_2D, 0, 0, 0,
678
- self._numpy_frame.shape[1],
679
- self._numpy_frame.shape[0],
680
- GL.GL_BGR, GL.GL_UNSIGNED_BYTE,
681
- self._numpy_frame.ctypes)
682
- else:
683
- GL.glTexParameteri(
684
- GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MAG_FILTER, GL.GL_NEAREST)
685
- GL.glTexParameteri(
686
- GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MIN_FILTER, GL.GL_NEAREST)
687
- if useSubTex is False:
688
- GL.glTexImage2D(GL.GL_TEXTURE_2D, 0, GL.GL_RGB8,
689
- self._numpy_frame.shape[1],
690
- self._numpy_frame.shape[0], 0,
691
- GL.GL_BGR, GL.GL_UNSIGNED_BYTE,
692
- self._numpy_frame.ctypes)
693
- else:
694
- GL.glTexSubImage2D(GL.GL_TEXTURE_2D, 0, 0, 0,
695
- self._numpy_frame.shape[1],
696
- self._numpy_frame.shape[0],
697
- GL.GL_BGR, GL.GL_UNSIGNED_BYTE,
698
- self._numpy_frame.ctypes)
699
- GL.glTexEnvi(GL.GL_TEXTURE_ENV, GL.GL_TEXTURE_ENV_MODE,
700
- GL.GL_MODULATE) # ?? do we need this - think not!
701
- else:
702
- raise RuntimeError("Could not load video frame data.")
703
-
704
- def _getVideoAudioTimeDiff(self):
705
- if self._audio_stream_started is False:
706
- return 0
707
- return self.getCurrentFrameTime() - self._getAudioStreamTime()
708
-
709
- def draw(self, win=None):
710
- """Draw the current frame to a particular visual.Window (or to the
711
- default win for this object if not specified).
712
- The current position in the movie will be determined automatically.
713
-
714
- This method should be called on every frame that the movie is meant
715
- to appear.
716
- """
717
- if self.status == NOT_STARTED or (self.status == FINISHED and self.loop):
718
- self.play()
719
- elif self.status == FINISHED and not self.loop:
720
- return
721
- return_next_frame_index = False
722
- if win is None:
723
- win = self.win
724
- self._selectWindow(win)
725
-
726
- vtClock = self._video_track_clock
727
- if (self._no_audio is False and
728
- not self._audio_stream_started and
729
- vtClock.getTime() >= self._av_stream_time_offset):
730
- self._startAudio()
731
-
732
- if self._next_frame_displayed:
733
- if self._getVideoAudioTimeDiff() > self._inter_frame_interval:
734
- vtClock.reset(-self._next_frame_sec)
735
- else:
736
- self._getNextFrame()
737
-
738
- if self.shouldDrawVideoFrame() and not self._next_frame_displayed:
739
- self._updateFrameTexture()
740
- return_next_frame_index = True
741
-
742
- # make sure that textures are on and GL_TEXTURE0 is active
743
- GL.glActiveTexture(GL.GL_TEXTURE0)
744
- GL.glEnable(GL.GL_TEXTURE_2D)
745
- # sets opacity (1,1,1 = RGB placeholder)
746
- GL.glColor4f(1, 1, 1, self.opacity)
747
- GL.glPushMatrix()
748
- self.win.setScale('pix')
749
- # move to centre of stimulus and rotate
750
- vertsPix = self.verticesPix
751
-
752
- array = (GL.GLfloat * 32)(
753
- 1, 1, # texture coords
754
- vertsPix[0, 0], vertsPix[0, 1], 0., # vertex
755
- 0, 1,
756
- vertsPix[1, 0], vertsPix[1, 1], 0.,
757
- 0, 0,
758
- vertsPix[2, 0], vertsPix[2, 1], 0.,
759
- 1, 0,
760
- vertsPix[3, 0], vertsPix[3, 1], 0.,
761
- )
762
- GL.glPushAttrib(GL.GL_ENABLE_BIT)
763
- GL.glEnable(GL.GL_TEXTURE_2D)
764
- GL.glBindTexture(GL.GL_TEXTURE_2D, self._texID)
765
- GL.glPushClientAttrib(GL.GL_CLIENT_VERTEX_ARRAY_BIT)
766
- # 2D texture array, 3D vertex array
767
- GL.glInterleavedArrays(GL.GL_T2F_V3F, 0, array)
768
- GL.glDrawArrays(GL.GL_QUADS, 0, 4)
769
- GL.glPopClientAttrib()
770
- GL.glPopAttrib()
771
- GL.glPopMatrix()
772
- # GL.glActiveTexture(0)
773
- # GL.glDisable(GL.GL_TEXTURE_2D)
774
- if return_next_frame_index:
775
- self.win.callOnFlip(self._flipCallback)
776
- return self._next_frame_index
777
-
778
- def setContrast(self):
779
- """Not yet implemented for MovieStim
780
- """
781
- pass
782
-
783
- def _startAudio(self):
784
- """Start the audio playback stream.
785
- """
786
- if self._audio_stream_player:
787
- self._audio_stream_started = True
788
- self._audio_stream_player.play()
789
- _tm = -self._audio_stream_player.get_time()
790
- self._audio_stream_clock.reset(_tm/1000.0)
791
-
792
- def _getAudioStreamTime(self):
793
- return self._audio_stream_clock.getTime()
794
-
795
- def _unload(self):
796
- # if self._video_stream:
797
- self._video_stream.release()
798
- # self._video_stream = None
799
- self._numpy_frame = None
800
- self._releaseAudioStream()
801
- self.status = FINISHED
802
-
803
- def _onEos(self):
804
- if self.loop:
805
- self.seek(0.0)
806
- else:
807
- self.status = FINISHED
808
- self.stop()
809
- if self.autoLog:
810
- self.win.logOnFlip("Set %s finished" % self.name,
811
- level=logging.EXP, obj=self)
812
-
813
- def __del__(self):
814
- self._unload()
815
-
816
- def setAutoDraw(self, val, log=None):
817
- """Add or remove a stimulus from the list of stimuli that will be
818
- automatically drawn on each flip
819
-
820
- :parameters:
821
- - val: True/False
822
- True to add the stimulus to the draw list, False to remove it
823
- """
824
- if val:
825
- self.play(log=False) # set to play in case stopped
826
- else:
827
- self.pause(log=False)
828
- # add to drawing list and update status
829
- setAttribute(self, 'autoDraw', val, log)
15
+ class MovieStim2(
16
+ PluginStub,
17
+ plugin="psychopy-legacy",
18
+ doclink="https://psychopy.github.io/psychopy-legacy/coder/visual/MovieStim2"
19
+ ):
20
+ pass