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