Skip to content

Commit d4fa6d1

Browse files
2 parents f4c3dc0 + 67637fd commit d4fa6d1

File tree

9 files changed

+202
-333
lines changed

9 files changed

+202
-333
lines changed

.github/workflows/Build.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,5 +21,5 @@ jobs:
2121

2222
- uses: actions/upload-artifact@v2
2323
with:
24-
name: name-of-artifact
25-
path: src/dist/windows
24+
name: BabbleApp
25+
path: BabbleApp/dist/windows

BabbleApp/babbleapp.spec

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ a = Analysis(['babbleapp.py'],
99
datas=[("Audio/*", "Audio"), ("Images/*", "Images/")],
1010
hiddenimports=[],
1111
hookspath=[],
12-
hooksconfig={},
1312
runtime_hooks=[],
1413
excludes=[],
1514
win_no_prefer_redirects=False,

BabbleApp/camera.py

Lines changed: 36 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,11 +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-
self.cv2_camera = cv2.VideoCapture(self.current_capture_source)
110-
if not self.settings.gui_cam_resolution_x == 0: self.cv2_camera.set(cv2.CAP_PROP_FRAME_WIDTH, self.settings.gui_cam_resolution_x)
111-
if not self.settings.gui_cam_resolution_y == 0: self.cv2_camera.set(cv2.CAP_PROP_FRAME_HEIGHT, self.settings.gui_cam_resolution_y)
112-
if not self.settings.gui_cam_framerate == 0: self.cv2_camera.set(cv2.CAP_PROP_FPS, self.settings.gui_cam_framerate)
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:
120+
self.cv2_camera = cv2.VideoCapture(self.current_capture_source, cv2.CAP_FFMPEG)
121+
else:
122+
self.cv2_camera = cv2.VideoCapture(self.current_capture_source)
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)
113130
should_push = False
114131
else:
115132
# We don't have a capture source to try yet, wait for one to show up in the GUI.
@@ -121,8 +138,8 @@ def run(self):
121138
# python event as a context-less, resettable one-shot channel.
122139
if should_push and not self.capture_event.wait(timeout=0.02):
123140
continue
124-
if self.config.capture_source != None:
125-
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):
126143
self.get_serial_camera_picture(should_push)
127144
else:
128145
self.get_cv2_camera_picture(should_push)
@@ -159,7 +176,8 @@ def get_cv2_camera_picture(self, should_push):
159176
if should_push:
160177
self.push_image_to_queue(image, frame_number, self.fps)
161178
except:
162-
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}")
163181
self.camera_status = CameraState.DISCONNECTED
164182
pass
165183

@@ -179,8 +197,8 @@ def get_next_packet_bounds(self):
179197

180198
def get_next_jpeg_frame(self):
181199
beg, end = self.get_next_packet_bounds()
182-
jpeg = self.buffer[beg+ETVR_HEADER_LEN:end+ETVR_HEADER_LEN]
183-
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:]
184202
return jpeg
185203

186204
def get_serial_camera_picture(self, should_push):
@@ -223,7 +241,8 @@ def get_serial_camera_picture(self, should_push):
223241
if should_push:
224242
self.push_image_to_queue(image, self.frame_number, self.fps)
225243
except Exception:
226-
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}")
227246
conn.close()
228247
self.camera_status = CameraState.DISCONNECTED
229248
pass
@@ -241,13 +260,13 @@ def start_serial_connection(self, port):
241260
return
242261
try:
243262
conn = serial.Serial(
244-
baudrate = 3000000,
245-
port = port,
263+
baudrate=3000000,
264+
port=port,
246265
xonxoff=False,
247266
dsrdtr=False,
248267
rtscts=False)
249268
# Set explicit buffer size for serial.
250-
conn.set_buffer_size(rx_size = 32768, tx_size = 32768)
269+
conn.set_buffer_size(rx_size=32768, tx_size=32768)
251270

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

BabbleApp/camera_widget.py

Lines changed: 62 additions & 41 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):
@@ -43,6 +41,7 @@ def __init__(self, widget_id: Tab, main_config: BabbleConfig, osc_queue: Queue):
4341
self.settings_config = main_config.settings
4442
self.config = main_config.cam
4543
self.settings = main_config.settings
44+
self.camera_list = list_camera_names()
4645
self.maybe_image = None
4746
if self.cam_id == Tab.CAM:
4847
self.config = main_config.cam
@@ -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(
@@ -145,7 +146,7 @@ def __init__(self, widget_id: Tab, main_config: BabbleConfig, osc_queue: Queue):
145146
default=self.config.gui_vertical_flip,
146147
key=self.gui_vertical_flip,
147148
background_color='#424042',
148-
tooltip = "Vertically flip camera feed.",
149+
tooltip="Vertically flip camera feed.",
149150
),
150151
sg.Checkbox(
151152
"Horizontal Flip:",
@@ -164,14 +165,19 @@ def __init__(self, widget_id: Tab, main_config: BabbleConfig, osc_queue: Queue):
164165
self.widget_layout = [
165166
[
166167
sg.Text("Camera Address", background_color='#424042'),
167-
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://')",),
168+
sg.InputCombo(self.camera_list, default_value=self.config.capture_source,
169+
key=self.gui_camera_addr,
170+
tooltip="Enter the IP address or UVC port of your camera. (Include the 'http://')",
171+
enable_events=True)
168172
],
169173
[
170174
sg.Button("Save and Restart Tracking", key=self.gui_save_tracking_button, button_color='#539e8a'),
171175
],
172176
[
173-
sg.Button("Tracking Mode", key=self.gui_tracking_button, button_color='#539e8a', tooltip = "Go here to track your mouth.",),
174-
sg.Button("Cropping Mode", key=self.gui_roi_button, button_color='#539e8a', tooltip = "Go here to crop out your mouth.",),
177+
sg.Button("Tracking Mode", key=self.gui_tracking_button, button_color='#539e8a',
178+
tooltip="Go here to track your mouth.", ),
179+
sg.Button("Cropping Mode", key=self.gui_roi_button, button_color='#539e8a',
180+
tooltip="Go here to crop out your mouth.", ),
175181
],
176182
[
177183
sg.Column(self.tracking_layout, key=self.gui_tracking_layout, background_color='#424042'),
@@ -230,25 +236,41 @@ def render(self, window, event, values):
230236
changed = False
231237
# If anything has changed in our configuration settings, change/update those.
232238
if (
233-
event == self.gui_save_tracking_button
234-
and values[self.gui_camera_addr] != self.config.capture_source
239+
event == self.gui_save_tracking_button
240+
and values[self.gui_camera_addr] != self.config.capture_source
235241
):
236-
print("\033[94m[INFO] New value: {}\033[0m".format(values[self.gui_camera_addr]))
242+
value = values[self.gui_camera_addr]
243+
print("\033[94m[INFO] New value: {}\033[0m".format(value))
237244
try:
245+
self.config.use_ffmpeg = False
238246
# Try storing ints as ints, for those using wired cameras.
239-
self.config.capture_source = int(values[self.gui_camera_addr])
247+
if value not in self.camera_list:
248+
self.config.capture_source = int(value)
249+
else:
250+
self.config.capture_source = value
240251
except ValueError:
241-
if values[self.gui_camera_addr] == "":
252+
if value == "":
242253
self.config.capture_source = None
243254
else:
244-
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]: # If http is not in camera address, add it.
245-
self.config.capture_source = f"http://{values[self.gui_camera_addr]}/"
246-
else:
247-
self.config.capture_source = values[self.gui_camera_addr]
255+
# If http is not in camera address, add it.
256+
self.config.capture_source = value
257+
258+
if "udp" in value:
259+
self.config.use_ffmpeg = True
260+
elif (
261+
"http" not in value
262+
and ".mp4" not in value
263+
and "udp" not in value
264+
and "COM" not in value
265+
and "/dev/tty" not in value
266+
and value not in self.camera_list
267+
): # If http is not in camera address, add it.
268+
self.config.capture_source = f"http://{values[self.gui_camera_addr]}/"
269+
248270
changed = True
249271

250272

251-
273+
252274
if self.config.rotation_angle != values[self.gui_rotation_slider]:
253275
self.config.rotation_angle = int(values[self.gui_rotation_slider])
254276
changed = True
@@ -287,9 +309,9 @@ def render(self, window, event, values):
287309
# Event for mouse button up in ROI mode
288310
self.is_mouse_up = True
289311
if self.x1 < 0:
290-
self.x1 = 0
312+
self.x1 = 0
291313
if self.y1 < 0:
292-
self.y1 = 0
314+
self.y1 = 0
293315
if abs(self.x0 - self.x1) != 0 and abs(self.y0 - self.y1) != 0:
294316
self.config.roi_window_x = min([self.x0, self.x1])
295317
self.config.roi_window_y = min([self.y0, self.y1])
@@ -334,7 +356,7 @@ def render(self, window, event, values):
334356
window[self.gui_tracking_bps].update(self._movavg_bps(self.camera.bps))
335357

336358
if self.in_roi_mode:
337-
try:
359+
try:
338360
if self.roi_queue.empty():
339361
self.capture_event.set()
340362
maybe_image = self.roi_queue.get(block=False)
@@ -364,7 +386,6 @@ def render(self, window, event, values):
364386
imgbytes = cv2.imencode(".ppm", maybe_image)[1].tobytes()
365387
window[self.gui_tracking_image].update(data=imgbytes)
366388

367-
368389
# Relay information to OSC
369390
if cam_info.info_type != CamInfoOrigin.FAILURE:
370391
self.osc_queue.put((self.cam_id, cam_info))

BabbleApp/config.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ class BabbleCameraConfig(BaseModel):
1818
capture_source: Union[int, str, None] = None
1919
gui_vertical_flip: bool = False
2020
gui_horizontal_flip: bool = False
21+
use_ffmpeg: bool = False
2122

2223
class BabbleSettingsConfig(BaseModel):
2324
gui_min_cutoff: str = "0.9"

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

LICENSE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@
175175

176176
END OF TERMS AND CONDITIONS
177177

178-
Copyright 2023 Sameer Suri
178+
Copyright 2024 Sameer Suri
179179

180180
Licensed under the Apache License, Version 2.0 (the "License");
181181
you may not use this file except in compliance with the License.

0 commit comments

Comments
 (0)