Skip to content

Commit 67637fd

Browse files
Merge pull request #41 from Dev-Sox/main
Added support for camera selection
2 parents 9f596eb + 584bdf1 commit 67637fd

File tree

4 files changed

+118
-62
lines changed

4 files changed

+118
-62
lines changed

BabbleApp/camera.py

Lines changed: 33 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
from colorama import Fore
1010
from config import BabbleConfig, BabbleSettingsConfig
11+
from utils.misc_utils import get_camera_index_by_name, list_camera_names
1112
from enum import Enum
1213

1314
WAIT_TIME = 0.1
@@ -39,11 +40,14 @@ def __init__(
3940
camera_output_outgoing: "queue.Queue(maxsize=2)",
4041
settings: BabbleSettingsConfig,
4142
):
43+
self.current_capture_sourc = None
4244
self.camera_status = CameraState.CONNECTING
4345
self.config = config
4446
self.settings = settings
4547
self.camera_index = camera_index
46-
self.camera_address = config.capture_source
48+
self.camera_list = list_camera_names()
49+
# The variable is not used
50+
# self.camera_address = config.capture_source
4751
self.camera_status_outgoing = camera_status_outgoing
4852
self.camera_output_outgoing = camera_output_outgoing
4953
self.capture_event = capture_event
@@ -81,10 +85,11 @@ def run(self):
8185
# If things aren't open, retry until they are. Don't let read requests come in any earlier
8286
# than this, otherwise we can deadlock ourselves.
8387
if (
84-
self.config.capture_source != None and self.config.capture_source != ""
88+
self.config.capture_source is not None and self.config.capture_source != ""
8589
):
8690
self.current_capture_source = self.config.capture_source
87-
if ("COM" in str(self.config.capture_source)):
91+
92+
if "COM" in str(self.config.capture_source) and self.config.capture_source not in self.camera_list:
8893
if (
8994
self.serial_connection is None
9095
or self.camera_status == CameraState.DISCONNECTED
@@ -105,14 +110,23 @@ def run(self):
105110
# firmware. Fickle things.
106111
if self.cancellation_event.wait(WAIT_TIME):
107112
return
108-
self.current_capture_source = self.config.capture_source
109-
if self.config.use_ffmpeg == True:
113+
114+
if self.config.capture_source not in self.camera_list:
115+
self.current_capture_source = self.config.capture_source
116+
else:
117+
self.current_capture_source = get_camera_index_by_name(self.current_capture_source)
118+
119+
if self.config.use_ffmpeg:
110120
self.cv2_camera = cv2.VideoCapture(self.current_capture_source, cv2.CAP_FFMPEG)
111121
else:
112122
self.cv2_camera = cv2.VideoCapture(self.current_capture_source)
113-
if not self.settings.gui_cam_resolution_x == 0: self.cv2_camera.set(cv2.CAP_PROP_FRAME_WIDTH, self.settings.gui_cam_resolution_x)
114-
if not self.settings.gui_cam_resolution_y == 0: self.cv2_camera.set(cv2.CAP_PROP_FRAME_HEIGHT, self.settings.gui_cam_resolution_y)
115-
if not self.settings.gui_cam_framerate == 0: self.cv2_camera.set(cv2.CAP_PROP_FPS, self.settings.gui_cam_framerate)
123+
124+
if not self.settings.gui_cam_resolution_x == 0: self.cv2_camera.set(cv2.CAP_PROP_FRAME_WIDTH,
125+
self.settings.gui_cam_resolution_x)
126+
if not self.settings.gui_cam_resolution_y == 0: self.cv2_camera.set(cv2.CAP_PROP_FRAME_HEIGHT,
127+
self.settings.gui_cam_resolution_y)
128+
if not self.settings.gui_cam_framerate == 0: self.cv2_camera.set(cv2.CAP_PROP_FPS,
129+
self.settings.gui_cam_framerate)
116130
should_push = False
117131
else:
118132
# We don't have a capture source to try yet, wait for one to show up in the GUI.
@@ -124,8 +138,8 @@ def run(self):
124138
# python event as a context-less, resettable one-shot channel.
125139
if should_push and not self.capture_event.wait(timeout=0.02):
126140
continue
127-
if self.config.capture_source != None:
128-
if ("COM" in str(self.config.capture_source)):
141+
if self.config.capture_source is not None:
142+
if "COM" in str(self.config.capture_source):
129143
self.get_serial_camera_picture(should_push)
130144
else:
131145
self.get_cv2_camera_picture(should_push)
@@ -162,7 +176,8 @@ def get_cv2_camera_picture(self, should_push):
162176
if should_push:
163177
self.push_image_to_queue(image, frame_number, self.fps)
164178
except:
165-
print(f"{Fore.YELLOW}[WARN] Capture source problem, assuming camera disconnected, waiting for reconnect.{Fore.RESET}")
179+
print(
180+
f"{Fore.YELLOW}[WARN] Capture source problem, assuming camera disconnected, waiting for reconnect.{Fore.RESET}")
166181
self.camera_status = CameraState.DISCONNECTED
167182
pass
168183

@@ -182,8 +197,8 @@ def get_next_packet_bounds(self):
182197

183198
def get_next_jpeg_frame(self):
184199
beg, end = self.get_next_packet_bounds()
185-
jpeg = self.buffer[beg+ETVR_HEADER_LEN:end+ETVR_HEADER_LEN]
186-
self.buffer = self.buffer[end+ETVR_HEADER_LEN:]
200+
jpeg = self.buffer[beg + ETVR_HEADER_LEN:end + ETVR_HEADER_LEN]
201+
self.buffer = self.buffer[end + ETVR_HEADER_LEN:]
187202
return jpeg
188203

189204
def get_serial_camera_picture(self, should_push):
@@ -226,7 +241,8 @@ def get_serial_camera_picture(self, should_push):
226241
if should_push:
227242
self.push_image_to_queue(image, self.frame_number, self.fps)
228243
except Exception:
229-
print(f"{Fore.YELLOW}[WARN] Serial capture source problem, assuming camera disconnected, waiting for reconnect.{Fore.RESET}")
244+
print(
245+
f"{Fore.YELLOW}[WARN] Serial capture source problem, assuming camera disconnected, waiting for reconnect.{Fore.RESET}")
230246
conn.close()
231247
self.camera_status = CameraState.DISCONNECTED
232248
pass
@@ -244,13 +260,13 @@ def start_serial_connection(self, port):
244260
return
245261
try:
246262
conn = serial.Serial(
247-
baudrate = 3000000,
248-
port = port,
263+
baudrate=3000000,
264+
port=port,
249265
xonxoff=False,
250266
dsrdtr=False,
251267
rtscts=False)
252268
# Set explicit buffer size for serial.
253-
conn.set_buffer_size(rx_size = 32768, tx_size = 32768)
269+
conn.set_buffer_size(rx_size=32768, tx_size=32768)
254270

255271
print(f"{Fore.CYAN}[INFO] ETVR Serial Tracker device connected on {port}{Fore.RESET}")
256272
self.serial_connection = conn

BabbleApp/camera_widget.py

Lines changed: 59 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,17 @@
1-
import PySimpleGUI as sg
2-
from config import BabbleConfig
3-
from config import BabbleSettingsConfig
41
from collections import deque
2+
from queue import Queue, Empty
53
from threading import Event, Thread
4+
5+
import PySimpleGUI as sg
6+
7+
import cv2
68
from babble_processor import BabbleProcessor, CamInfoOrigin
7-
from landmark_processor import LandmarkProcessor
8-
from enum import Enum
9-
from queue import Queue, Empty
109
from camera import Camera, CameraState
10+
from config import BabbleConfig
11+
from landmark_processor import LandmarkProcessor
1112
from osc import Tab
12-
import cv2
13-
import sys
14-
from utils.misc_utils import PlaySound,SND_FILENAME,SND_ASYNC
15-
import traceback
16-
import numpy as np
13+
from utils.misc_utils import PlaySound, SND_FILENAME, SND_ASYNC, list_camera_names
14+
1715

1816
class CameraWidget:
1917
def __init__(self, widget_id: Tab, main_config: BabbleConfig, osc_queue: Queue):
@@ -44,6 +42,7 @@ def __init__(self, widget_id: Tab, main_config: BabbleConfig, osc_queue: Queue):
4442
self.settings_config = main_config.settings
4543
self.config = main_config.cam
4644
self.settings = main_config.settings
45+
self.camera_list = list_camera_names()
4746
if self.cam_id == Tab.CAM:
4847
self.config = main_config.cam
4948
else:
@@ -92,17 +91,17 @@ def __init__(self, widget_id: Tab, main_config: BabbleConfig, osc_queue: Queue):
9291

9392
self.roi_layout = [
9493
[
95-
sg.Button("Auto ROI", key=self.gui_autoroi, button_color='#539e8a', tooltip = "Automatically set ROI",),
94+
sg.Button("Auto ROI", key=self.gui_autoroi, button_color='#539e8a', tooltip="Automatically set ROI", ),
9695
],
9796
[
98-
sg.Graph(
99-
(640, 480),
100-
(0, 480),
101-
(640, 0),
102-
key=self.gui_roi_selection,
103-
drag_submits=True,
104-
enable_events=True,
105-
background_color='#424042',
97+
sg.Graph(
98+
(640, 480),
99+
(0, 480),
100+
(640, 0),
101+
key=self.gui_roi_selection,
102+
drag_submits=True,
103+
enable_events=True,
104+
background_color='#424042',
106105
)
107106
]
108107
]
@@ -117,12 +116,14 @@ def __init__(self, widget_id: Tab, main_config: BabbleConfig, osc_queue: Queue):
117116
orientation="h",
118117
key=self.gui_rotation_slider,
119118
background_color='#424042',
120-
tooltip = "Adjust the rotation of your cameras, make them level.",
119+
tooltip="Adjust the rotation of your cameras, make them level.",
121120
),
122121
],
123122
[
124-
sg.Button("Start Calibration", key=self.gui_restart_calibration, button_color='#539e8a', tooltip = "Start calibration. Look all arround to all extreams without blinking until sound is heard.",),
125-
sg.Button("Stop Calibration", key=self.gui_stop_calibration, button_color='#539e8a', tooltip = "Stop calibration manualy.",),
123+
sg.Button("Start Calibration", key=self.gui_restart_calibration, button_color='#539e8a',
124+
tooltip="Start calibration. Look all arround to all extreams without blinking until sound is heard.", ),
125+
sg.Button("Stop Calibration", key=self.gui_stop_calibration, button_color='#539e8a',
126+
tooltip="Stop calibration manualy.", ),
126127
],
127128
[
128129
sg.Checkbox(
@@ -152,7 +153,7 @@ def __init__(self, widget_id: Tab, main_config: BabbleConfig, osc_queue: Queue):
152153
default=self.config.gui_vertical_flip,
153154
key=self.gui_vertical_flip,
154155
background_color='#424042',
155-
tooltip = "Vertically flip camera feed.",
156+
tooltip="Vertically flip camera feed.",
156157
),
157158
sg.Checkbox(
158159
"Horizontal Flip:",
@@ -171,14 +172,19 @@ def __init__(self, widget_id: Tab, main_config: BabbleConfig, osc_queue: Queue):
171172
self.widget_layout = [
172173
[
173174
sg.Text("Camera Address", background_color='#424042'),
174-
sg.InputText(self.config.capture_source, key=self.gui_camera_addr, tooltip = "Enter the IP address or UVC port of your camera. (Include the 'http://')",),
175+
sg.InputCombo(self.camera_list, default_value=self.config.capture_source,
176+
key=self.gui_camera_addr,
177+
tooltip="Enter the IP address or UVC port of your camera. (Include the 'http://')",
178+
enable_events=True)
175179
],
176180
[
177181
sg.Button("Save and Restart Tracking", key=self.gui_save_tracking_button, button_color='#539e8a'),
178182
],
179183
[
180-
sg.Button("Tracking Mode", key=self.gui_tracking_button, button_color='#539e8a', tooltip = "Go here to track your mouth.",),
181-
sg.Button("Cropping Mode", key=self.gui_roi_button, button_color='#539e8a', tooltip = "Go here to crop out your mouth.",),
184+
sg.Button("Tracking Mode", key=self.gui_tracking_button, button_color='#539e8a',
185+
tooltip="Go here to track your mouth.", ),
186+
sg.Button("Cropping Mode", key=self.gui_roi_button, button_color='#539e8a',
187+
tooltip="Go here to crop out your mouth.", ),
182188
],
183189
[
184190
sg.Column(self.tracking_layout, key=self.gui_tracking_layout, background_color='#424042'),
@@ -237,28 +243,38 @@ def render(self, window, event, values):
237243
changed = False
238244
# If anything has changed in our configuration settings, change/update those.
239245
if (
240-
event == self.gui_save_tracking_button
241-
and values[self.gui_camera_addr] != self.config.capture_source
246+
event == self.gui_save_tracking_button
247+
and values[self.gui_camera_addr] != self.config.capture_source
242248
):
243-
print("\033[94m[INFO] New value: {}\033[0m".format(values[self.gui_camera_addr]))
249+
value = values[self.gui_camera_addr]
250+
print("\033[94m[INFO] New value: {}\033[0m".format(value))
244251
try:
245252
self.config.use_ffmpeg = False
246253
# Try storing ints as ints, for those using wired cameras.
247-
self.config.capture_source = int(values[self.gui_camera_addr])
254+
if value not in self.camera_list:
255+
self.config.capture_source = int(value)
256+
else:
257+
self.config.capture_source = value
248258
except ValueError:
249-
if values[self.gui_camera_addr] == "":
259+
if value == "":
250260
self.config.capture_source = None
251261
else:
252-
if len(values[self.gui_camera_addr]) > 5 and "http" not in values[self.gui_camera_addr] and ".mp4" not in values[self.gui_camera_addr] and "udp" not in values[self.gui_camera_addr]: # If http is not in camera address, add it.
253-
self.config.capture_source = f"http://{values[self.gui_camera_addr]}/"
254-
elif "udp" in values[self.gui_camera_addr]:
255-
self.config.use_ffmpeg = True
256-
self.config.capture_source = values[self.gui_camera_addr]
257-
else:
258-
self.config.capture_source = values[self.gui_camera_addr]
259-
changed = True
262+
# If http is not in camera address, add it.
263+
self.config.capture_source = value
260264

265+
if "udp" in value:
266+
self.config.use_ffmpeg = True
267+
elif (
268+
"http" not in value
269+
and ".mp4" not in value
270+
and "udp" not in value
271+
and "COM" not in value
272+
and "/dev/tty" not in value
273+
and value not in self.camera_list
274+
): # If http is not in camera address, add it.
275+
self.config.capture_source = f"http://{values[self.gui_camera_addr]}/"
261276

277+
changed = True
262278

263279
if self.config.rotation_angle != values[self.gui_rotation_slider]:
264280
self.config.rotation_angle = int(values[self.gui_rotation_slider])
@@ -302,9 +318,9 @@ def render(self, window, event, values):
302318
# Event for mouse button up in ROI mode
303319
self.is_mouse_up = True
304320
if self.x1 < 0:
305-
self.x1 = 0
321+
self.x1 = 0
306322
if self.y1 < 0:
307-
self.y1 = 0
323+
self.y1 = 0
308324
if abs(self.x0 - self.x1) != 0 and abs(self.y0 - self.y1) != 0:
309325
self.config.roi_window_x = min([self.x0, self.x1])
310326
self.config.roi_window_y = min([self.y0, self.y1])
@@ -370,7 +386,7 @@ def render(self, window, event, values):
370386
window[self.gui_tracking_bps].update(self._movavg_bps(self.camera.bps))
371387

372388
if self.in_roi_mode:
373-
try:
389+
try:
374390
if self.roi_queue.empty():
375391
self.capture_event.set()
376392
maybe_image = self.roi_queue.get(block=False)
@@ -399,7 +415,6 @@ def render(self, window, event, values):
399415
imgbytes = cv2.imencode(".ppm", maybe_image)[1].tobytes()
400416
window[self.gui_tracking_image].update(data=imgbytes)
401417

402-
403418
# Relay information to OSC
404419
if cam_info.info_type != CamInfoOrigin.FAILURE:
405420
self.osc_queue.put((self.cam_id, cam_info))

BabbleApp/utils/misc_utils.py

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,37 @@
11
import os
22
import typing
3+
from pygrabber.dshow_graph import FilterGraph
34

45
is_nt = True if os.name == "nt" else False
6+
graph = FilterGraph()
7+
8+
9+
def list_camera_names():
10+
cam_list = graph.get_input_devices()
11+
cam_names = []
12+
for index, name in enumerate(cam_list):
13+
cam_names.append(name)
14+
return cam_names
15+
16+
17+
def get_camera_index_by_name(name):
18+
cam_list = graph.get_input_devices()
19+
20+
for i, device_name in enumerate(cam_list):
21+
if device_name == name:
22+
return i
23+
24+
return None
25+
526

627
def PlaySound(*args, **kwargs): pass
28+
29+
730
SND_FILENAME = SND_ASYNC = 1
831

932
if is_nt:
1033
import winsound
34+
1135
PlaySound = winsound.PlaySound
1236
SND_FILENAME = winsound.SND_FILENAME
13-
SND_ASYNC = winsound.SND_ASYNC
37+
SND_ASYNC = winsound.SND_ASYNC

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,4 @@ pyserial
1111
colorama
1212
winotify
1313
poetry
14+
pygrabber

0 commit comments

Comments
 (0)