From 1ca6c1aec49c45ba59634e61842612e92fe28321 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Laboissi=C3=A8re?= <rafael@laboissiere.net> Date: Sat, 22 Mar 2025 11:47:12 +0100 Subject: [PATCH 01/10] Add check box for updating the list of video devices --- .../routines/videoRecorderRoutine/__init__.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/psychopy_video_recorder/routines/videoRecorderRoutine/__init__.py b/psychopy_video_recorder/routines/videoRecorderRoutine/__init__.py index 424efb9..ad5a9e4 100644 --- a/psychopy_video_recorder/routines/videoRecorderRoutine/__init__.py +++ b/psychopy_video_recorder/routines/videoRecorderRoutine/__init__.py @@ -37,6 +37,15 @@ class VideoRecorderRoutine(BaseStandaloneRoutine): del self.params["stopType"] # ------ video recorder parameters ------ + self.params["RefreshVideoDevices"] = Param( + False, + valType="bool", + inputType="bool", + categ="Video", + hint="Will refresh the list of video devices whenever it is clicked", + label="Refresh video devices", + ) + def get_camera_devices(): indices = [] names = [] -- GitLab From 3b2fb473cd8f692c974a93f947756734f24c6527 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Laboissi=C3=A8re?= <rafael@laboissiere.net> Date: Sat, 22 Mar 2025 11:47:57 +0100 Subject: [PATCH 02/10] Update choices in VideoDevice parameter whenever RefreshVideoDevices is clicked Unfortunately, there is no way to add a simple button for accomplishing the task that we are pursuing here. The fact that both checking and unckecking the box will have the same effect may be disturbing. --- .../routines/videoRecorderRoutine/__init__.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/psychopy_video_recorder/routines/videoRecorderRoutine/__init__.py b/psychopy_video_recorder/routines/videoRecorderRoutine/__init__.py index ad5a9e4..7b01bd3 100644 --- a/psychopy_video_recorder/routines/videoRecorderRoutine/__init__.py +++ b/psychopy_video_recorder/routines/videoRecorderRoutine/__init__.py @@ -81,6 +81,13 @@ class VideoRecorderRoutine(BaseStandaloneRoutine): hint="Video device", label="Video device", ) + self.depends.append({ + "dependsOn": "RefreshVideoDevices", # if... + "condition": "", # meets... + "param": "VideoDevice", # then... + "true": "populate", # should... + "false": "populate", # otherwise... + }) def get_camera_resolutions(): resolutions = [] -- GitLab From 4c3150c848750604f2f9989c6d16500505273921 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Laboissi=C3=A8re?= <rafael@laboissiere.net> Date: Sat, 22 Mar 2025 11:49:49 +0100 Subject: [PATCH 03/10] Terminate PyAudio connexion --- .../routines/videoRecorderRoutine/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/psychopy_video_recorder/routines/videoRecorderRoutine/__init__.py b/psychopy_video_recorder/routines/videoRecorderRoutine/__init__.py index 7b01bd3..005833b 100644 --- a/psychopy_video_recorder/routines/videoRecorderRoutine/__init__.py +++ b/psychopy_video_recorder/routines/videoRecorderRoutine/__init__.py @@ -143,6 +143,7 @@ class VideoRecorderRoutine(BaseStandaloneRoutine): info = p.get_device_info_by_host_api_device_index(0, i) name = info.get("name") names.append(name) + p.terminate() return indices, names def get_audio_devices_indices(): -- GitLab From a4138d6e3c74602896ab8d9f1f3bfd9124612942 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Laboissi=C3=A8re?= <rafael@laboissiere.net> Date: Sat, 22 Mar 2025 11:51:47 +0100 Subject: [PATCH 04/10] Use variable for specifying the PyAudio API --- .../routines/videoRecorderRoutine/__init__.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/psychopy_video_recorder/routines/videoRecorderRoutine/__init__.py b/psychopy_video_recorder/routines/videoRecorderRoutine/__init__.py index 005833b..db56c1e 100644 --- a/psychopy_video_recorder/routines/videoRecorderRoutine/__init__.py +++ b/psychopy_video_recorder/routines/videoRecorderRoutine/__init__.py @@ -132,15 +132,16 @@ class VideoRecorderRoutine(BaseStandaloneRoutine): # ------ audio recorder parameters ------ def get_audio_devices(): p = pyaudio.PyAudio() - info = p.get_host_api_info_by_index(0) + api = 0 + info = p.get_host_api_info_by_index(api) numdevices = info.get("deviceCount") indices = [] names = [] for i in range(0, numdevices): - if ((p.get_device_info_by_host_api_device_index(0, i) + if ((p.get_device_info_by_host_api_device_index(api, i) .get("maxInputChannels")) > 0): indices.append(i) - info = p.get_device_info_by_host_api_device_index(0, i) + info = p.get_device_info_by_host_api_device_index(api, i) name = info.get("name") names.append(name) p.terminate() -- GitLab From 2ee66103937eefd29f10511bf96666214886b224 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Laboissi=C3=A8re?= <rafael@laboissiere.net> Date: Sat, 22 Mar 2025 12:02:37 +0100 Subject: [PATCH 05/10] Drop useless argument in the call of range() --- .../routines/videoRecorderRoutine/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/psychopy_video_recorder/routines/videoRecorderRoutine/__init__.py b/psychopy_video_recorder/routines/videoRecorderRoutine/__init__.py index db56c1e..d608399 100644 --- a/psychopy_video_recorder/routines/videoRecorderRoutine/__init__.py +++ b/psychopy_video_recorder/routines/videoRecorderRoutine/__init__.py @@ -137,7 +137,7 @@ class VideoRecorderRoutine(BaseStandaloneRoutine): numdevices = info.get("deviceCount") indices = [] names = [] - for i in range(0, numdevices): + for i in range(numdevices): if ((p.get_device_info_by_host_api_device_index(api, i) .get("maxInputChannels")) > 0): indices.append(i) -- GitLab From 2ad0e9de593c8e5669b9fe7b957fb51ff5dded7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Laboissi=C3=A8re?= <rafael@laboissiere.net> Date: Sat, 22 Mar 2025 12:05:05 +0100 Subject: [PATCH 06/10] Avoid double call of method get_device_info_by_host_api_device_index() --- .../routines/videoRecorderRoutine/__init__.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/psychopy_video_recorder/routines/videoRecorderRoutine/__init__.py b/psychopy_video_recorder/routines/videoRecorderRoutine/__init__.py index d608399..65bedea 100644 --- a/psychopy_video_recorder/routines/videoRecorderRoutine/__init__.py +++ b/psychopy_video_recorder/routines/videoRecorderRoutine/__init__.py @@ -138,10 +138,9 @@ class VideoRecorderRoutine(BaseStandaloneRoutine): indices = [] names = [] for i in range(numdevices): - if ((p.get_device_info_by_host_api_device_index(api, i) - .get("maxInputChannels")) > 0): + info = p.get_device_info_by_host_api_device_index(api, i) + if info.get("maxInputChannels") > 0: indices.append(i) - info = p.get_device_info_by_host_api_device_index(api, i) name = info.get("name") names.append(name) p.terminate() -- GitLab From 0c22e112da4ca7740c6edbe0db49cc8cc0bc2172 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Laboissi=C3=A8re?= <rafael@laboissiere.net> Date: Sat, 22 Mar 2025 12:05:45 +0100 Subject: [PATCH 07/10] Add check box for updating the list of audio devices --- .../routines/videoRecorderRoutine/__init__.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/psychopy_video_recorder/routines/videoRecorderRoutine/__init__.py b/psychopy_video_recorder/routines/videoRecorderRoutine/__init__.py index 65bedea..2cf4cdd 100644 --- a/psychopy_video_recorder/routines/videoRecorderRoutine/__init__.py +++ b/psychopy_video_recorder/routines/videoRecorderRoutine/__init__.py @@ -130,6 +130,15 @@ class VideoRecorderRoutine(BaseStandaloneRoutine): }) # ------ audio recorder parameters ------ + self.params["RefreshAudioDevices"] = Param( + False, + valType="bool", + inputType="bool", + categ="Audio", + hint="Will refresh the list of audio devices whenever it is clicked", + label="Refresh audio devices", + ) + def get_audio_devices(): p = pyaudio.PyAudio() api = 0 -- GitLab From 2d7882325dcbf120e6b315d76af16d63ecf6cfe5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Laboissi=C3=A8re?= <rafael@laboissiere.net> Date: Sat, 22 Mar 2025 12:06:05 +0100 Subject: [PATCH 08/10] Add note about unexpected behavior of method get_audio_devices --- .../routines/videoRecorderRoutine/__init__.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/psychopy_video_recorder/routines/videoRecorderRoutine/__init__.py b/psychopy_video_recorder/routines/videoRecorderRoutine/__init__.py index 2cf4cdd..1ca1670 100644 --- a/psychopy_video_recorder/routines/videoRecorderRoutine/__init__.py +++ b/psychopy_video_recorder/routines/videoRecorderRoutine/__init__.py @@ -139,6 +139,9 @@ class VideoRecorderRoutine(BaseStandaloneRoutine): label="Refresh audio devices", ) + # For some unknown reason, the list of input audio devices is not + # updated in successive calls of the function below, when + # microphones are plugged in and out (at least on Linux systems). def get_audio_devices(): p = pyaudio.PyAudio() api = 0 -- GitLab From 6f72763be3d6a0e5996dffb4109c17c3ba47fa6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Laboissi=C3=A8re?= <rafael@laboissiere.net> Date: Sat, 22 Mar 2025 12:07:31 +0100 Subject: [PATCH 09/10] Update choices in AudioDevice parameter whenever RefreshAudioDevices is clicked --- .../routines/videoRecorderRoutine/__init__.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/psychopy_video_recorder/routines/videoRecorderRoutine/__init__.py b/psychopy_video_recorder/routines/videoRecorderRoutine/__init__.py index 1ca1670..1c08399 100644 --- a/psychopy_video_recorder/routines/videoRecorderRoutine/__init__.py +++ b/psychopy_video_recorder/routines/videoRecorderRoutine/__init__.py @@ -174,6 +174,13 @@ class VideoRecorderRoutine(BaseStandaloneRoutine): hint="Audio device", label="Audio device", ) + self.depends.append({ + "dependsOn": "RefreshAudioDevices", # if... + "condition": "", # meets... + "param": "AudioDevice", # then... + "true": "populate", # should... + "false": "populate", # otherwise... + }) # ------ save parameters ------ self.params["GenerateFelyx"] = Param( -- GitLab From ebe25c8198b6eaaac2d8e19c7dad461707dd41ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Laboissi=C3=A8re?= <rafael@laboissiere.net> Date: Mon, 24 Mar 2025 12:16:35 +0100 Subject: [PATCH 10/10] Add a "no selection" option fr video devices and resolutions --- .../routines/videoRecorderRoutine/__init__.py | 51 ++++++++++--------- 1 file changed, 28 insertions(+), 23 deletions(-) diff --git a/psychopy_video_recorder/routines/videoRecorderRoutine/__init__.py b/psychopy_video_recorder/routines/videoRecorderRoutine/__init__.py index 1c08399..1904921 100644 --- a/psychopy_video_recorder/routines/videoRecorderRoutine/__init__.py +++ b/psychopy_video_recorder/routines/videoRecorderRoutine/__init__.py @@ -26,6 +26,9 @@ class VideoRecorderRoutine(BaseStandaloneRoutine): "640x480", ] + no_selection_index = -1 + no_selection_label = "(no selection)" + def __init__( self, exp, @@ -47,8 +50,8 @@ class VideoRecorderRoutine(BaseStandaloneRoutine): ) def get_camera_devices(): - indices = [] - names = [] + indices = [self.no_selection_index] + names = [self.no_selection_label] try: backend = { "linux": cv2.CAP_V4L2, @@ -72,7 +75,7 @@ class VideoRecorderRoutine(BaseStandaloneRoutine): return get_camera_devices()[1] self.params["VideoDevice"] = Param( - 0, + self.no_selection_index, valType="int", inputType="choice", categ="Video", @@ -90,29 +93,31 @@ class VideoRecorderRoutine(BaseStandaloneRoutine): }) def get_camera_resolutions(): - resolutions = [] - for resolution in self.standard_resolutions: - capture = cv2.VideoCapture(int(self.params["VideoDevice"].val)) - if capture.isOpened(): - width, height = resolution.split("x") - capture.set(cv2.CAP_PROP_FRAME_WIDTH, int(width)) - capture.set(cv2.CAP_PROP_FRAME_HEIGHT, int(height)) - capture.set( - cv2.CAP_PROP_FOURCC, - cv2.VideoWriter_fourcc("M", "J", "P", "G"), - ) - status, image = capture.read() - if ( - status - and image.shape[1] == int(width) - and image.shape[0] == int(height) - ): - resolutions.append(resolution) - capture.release() + resolutions = [self.no_selection_label] + video_device = int(self.params["VideoDevice"].val) + if video_device != self.no_selection_index: + for resolution in self.standard_resolutions: + capture = cv2.VideoCapture(video_device) + if capture.isOpened(): + width, height = resolution.split("x") + capture.set(cv2.CAP_PROP_FRAME_WIDTH, int(width)) + capture.set(cv2.CAP_PROP_FRAME_HEIGHT, int(height)) + capture.set( + cv2.CAP_PROP_FOURCC, + cv2.VideoWriter_fourcc("M", "J", "P", "G"), + ) + status, image = capture.read() + if ( + status + and image.shape[1] == int(width) + and image.shape[0] == int(height) + ): + resolutions.append(resolution) + capture.release() return resolutions self.params["Resolution"] = Param( - "", + self.no_selection_label, valType="str", inputType="choice", categ="Video", -- GitLab