-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathlinux-webcam-util.py
More file actions
276 lines (251 loc) · 9.83 KB
/
linux-webcam-util.py
File metadata and controls
276 lines (251 loc) · 9.83 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
#!/usr/bin/env python3
import os
import subprocess
import sys
import re
# File dialog support
try:
import tkinter as tk
from tkinter import filedialog
except ImportError:
print("tkinter is required for the file dialog. Please install it (e.g., sudo apt install python3-tk).")
sys.exit(1)
GUV_PROFILE_TO_V4L2 = {
"Brightness": "brightness",
"Contrast": "contrast",
"Saturation": "saturation",
"Hue": "hue",
"Gamma": "gamma",
"Gain": "gain",
"Sharpness": "sharpness",
"White Balance, Automatic": "white_balance_temperature_auto",
"White Balance Temperature": "white_balance_temperature",
"Backlight Compensation": "backlight_compensation",
"Power Line Frequency": "power_line_frequency",
"Auto Exposure": "exposure_auto",
"Exposure, Dynamic Framerate": "exposure_dynamic_framerate",
"Pan, Absolute": "pan_absolute",
"Tilt, Absolute": "tilt_absolute",
"Focus, Absolute": "focus_absolute",
"Focus, Automatic Continuous": "focus_auto",
"Zoom, Absolute": "zoom_absolute",
"Exposure, Auto Priority": "exposure_auto_priority",
}
GUV_CONFIG_TO_V4L2 = {
"brightness": "brightness",
"contrast": "contrast",
"saturation": "saturation",
"gain": "gain",
"sharpness": "sharpness",
"gamma": "gamma",
"exposure_abs": "exposure_absolute",
"exposure_auto": "exposure_auto",
"white_balance_temperature_auto": "white_balance_temperature_auto",
"white_balance_temperature": "white_balance_temperature",
"focus_auto": "focus_auto",
"focus_abs": "focus_absolute",
}
def list_video_devices():
devices = []
for dev in sorted(os.listdir('/dev')):
if dev.startswith('video'):
path = f'/dev/{dev}'
try:
info = subprocess.check_output(
f'v4l2-ctl -d {path} --info', shell=True, stderr=subprocess.DEVNULL
).decode().strip()
except Exception:
info = "Unknown device"
devices.append((path, info))
return devices
def select_from_list(options, prompt):
print(prompt)
for idx, (dev, info) in enumerate(options):
print(f"{idx+1}: {dev} ({info.splitlines()[0]})")
while True:
try:
choice = int(input("Enter the number of your choice: "))
if 1 <= choice <= len(options):
return options[choice-1][0]
except Exception:
pass
print("Invalid selection, try again.")
def prompt_for_config_file():
print("\nHow would you like to select the GUVCView config file?")
print("1: Enter the path manually")
print("2: Open file explorer to select the file")
while True:
choice = input("Enter 1 or 2: ").strip()
if choice == "2":
root = tk.Tk()
root.withdraw()
file_path = filedialog.askopenfilename(
title="Select GUVCView config or profile file",
filetypes=[("GUVCView files", "*.gpfl *.conf *video* *rc*"), ("All files", "*.*")]
)
root.destroy()
if file_path and os.path.isfile(file_path):
print(f"Selected: {file_path}")
return file_path
else:
print("No file selected. Please try again.")
elif choice == "1":
while True:
path = input("Enter the full path to your GUVCView config file (e.g. ~/.config/guvcview2/video0 or ~/Documents/default.gpfl): ").strip()
path = os.path.expanduser(path)
if os.path.isfile(path):
return path
print("File not found, try again.")
else:
print("Invalid selection, try again.")
def parse_guvcview_profile(profile_path):
settings = {}
last_ctrl_name = None
with open(profile_path) as f:
for line in f:
line = line.strip()
if line.startswith("#") and not line.startswith("#V4L2/CTRL"):
last_ctrl_name = line.lstrip("#").strip()
elif "=VAL{" in line and last_ctrl_name:
m = re.search(r"=VAL{([-\d]+)}", line)
if m:
val = m.group(1)
v4l2_ctrl = GUV_PROFILE_TO_V4L2.get(last_ctrl_name)
if v4l2_ctrl:
settings[v4l2_ctrl] = val
last_ctrl_name = None
return settings
def parse_guvcview_config(config_path):
settings = {}
with open(config_path) as f:
for line in f:
if "=" in line:
key, val = line.strip().split("=", 1)
key = key.strip()
val = val.strip()
if key in GUV_CONFIG_TO_V4L2:
if val.lower() in ("true", "yes", "on"):
val = "1"
elif val.lower() in ("false", "no", "off"):
val = "0"
settings[GUV_CONFIG_TO_V4L2[key]] = val
return settings
def parse_guvcview_config_or_profile(path):
with open(path) as f:
first_line = f.readline()
if path.endswith('.gpfl') or first_line.startswith('#V4L2/CTRL'):
return parse_guvcview_profile(path)
else:
return parse_guvcview_config(path)
def write_restore_script(settings, script_path, device):
with open(script_path, "w") as f:
f.write("#!/bin/bash\n")
for ctrl, val in settings.items():
f.write(f"v4l2-ctl -d {device} -c {ctrl}={val}\n")
os.chmod(script_path, 0o755)
print(f"Restore script written to {script_path}")
def setup_autostart(script_path):
autostart_dir = os.path.expanduser("~/.config/autostart")
os.makedirs(autostart_dir, exist_ok=True)
autostart_file = os.path.join(autostart_dir, "restore-webcam-settings.desktop")
with open(autostart_file, "w") as f:
f.write(f"""[Desktop Entry]
Type=Application
Exec={script_path}
Hidden=false
NoDisplay=false
X-GNOME-Autostart-enabled=true
Name=Restore Webcam Settings
""")
print(f"Autostart entry created at {autostart_file}")
def get_usb_ids(device):
try:
udev_info = subprocess.check_output(
f"udevadm info --query=all --name={device}", shell=True
).decode()
vid, pid = None, None
for line in udev_info.splitlines():
if "ID_VENDOR_ID=" in line:
vid = line.split("=",1)[1].strip()
if "ID_MODEL_ID=" in line:
pid = line.split("=",1)[1].strip()
return vid, pid
except Exception as e:
print("Could not determine USB IDs for udev rule:", e)
return None, None
def setup_udev_rule(script_path, device):
vid, pid = get_usb_ids(device)
if not (vid and pid):
print("Skipping udev rule creation (could not determine USB IDs).")
return
rule = (
f'ACTION=="add", SUBSYSTEM=="video4linux", '
f'ATTRS{{idVendor}}=="{vid}", ATTRS{{idProduct}}=="{pid}", '
f'RUN+="{script_path}"\n'
)
udev_rule_path = "/etc/udev/rules.d/99-webcam-settings.rules"
tmp_rule = "/tmp/99-webcam-settings.rules"
with open(tmp_rule, "w") as f:
f.write(rule)
print(f"Creating udev rule at {udev_rule_path} (requires sudo)...")
try:
subprocess.run(f"sudo mv {tmp_rule} {udev_rule_path}", shell=True, check=True)
subprocess.run("sudo udevadm control --reload-rules", shell=True, check=True)
subprocess.run("sudo udevadm trigger", shell=True, check=True)
print("udev rule installed and reloaded.")
except Exception as e:
print("Failed to install udev rule:", e)
def run_restore_script(script_path):
print(f"Applying settings immediately by running: {script_path}")
subprocess.run([script_path])
def setup_systemd_service(script_path):
service_name = "restore-webcam-settings"
service_path = f"/etc/systemd/system/{service_name}.service"
python_path = sys.executable
user = os.environ.get("USER")
with open("/tmp/restore-webcam-settings.service", "w") as f:
f.write(f"""[Unit]
Description=Restore webcam settings at boot
After=network.target
[Service]
Type=oneshot
ExecStart={script_path}
User={user}
[Install]
WantedBy=multi-user.target
""")
print(f"Creating systemd service at {service_path} (requires sudo)...")
try:
subprocess.run(f"sudo mv /tmp/restore-webcam-settings.service {service_path}", shell=True, check=True)
subprocess.run("sudo systemctl daemon-reload", shell=True, check=True)
subprocess.run(f"sudo systemctl enable {service_name}.service", shell=True, check=True)
print(f"Systemd service {service_name}.service enabled.")
except Exception as e:
print("Failed to install systemd service:", e)
def main():
devices = list_video_devices()
if not devices:
print("No video devices found.")
sys.exit(1)
device = select_from_list(devices, "Select the camera to apply settings to:")
config_path = prompt_for_config_file()
settings = parse_guvcview_config_or_profile(config_path)
if not settings:
print("No relevant settings found in GUVCView config/profile.")
print("\nIf you selected a GUVCView profile (.gpfl), make sure it contains control data.")
sys.exit(1)
restore_script = os.path.expanduser("~/.restore-webcam-settings.sh")
write_restore_script(settings, restore_script, device)
setup_autostart(restore_script)
setup_udev_rule(restore_script, device)
run_restore_script(restore_script)
print("\nWould you like to also set up a systemd service to apply settings at system boot (before login)?")
print("1: Yes")
print("2: No")
choice = input("Enter 1 or 2: ").strip()
if choice == "1":
setup_systemd_service(restore_script)
print("\nAll done! Your webcam settings will now persist at login and when the camera is reconnected.")
print(f"To apply settings manually, run:\n {restore_script}")
if __name__ == "__main__":
main()