1pub fn is_real_camera(name: &str) -> bool {
7 let name_lower = name.to_lowercase();
8 !name_lower.contains("infrared")
9 && !name_lower.contains("ir camera")
10 && !name_lower.contains("integrated i")
11 && !name_lower.contains("integrated ir")
12 && !name_lower.contains("depth camera")
13}
14
15#[cfg(any(target_os = "windows", test))]
22fn is_windows_virtual_camera(name: &str) -> bool {
23 name.to_lowercase().contains("virtual camera")
24}
25
26pub fn clean_camera_name(name: &str) -> String {
27 let trimmed = name.trim();
28 if trimmed.starts_with("Integrated Camera:") {
29 return "Integrated Camera".to_string();
30 }
31 if trimmed.starts_with("Integrated Webcam:") {
32 return "Integrated Webcam".to_string();
33 }
34 trimmed.to_string()
35}
36
37#[cfg(target_os = "macos")]
38pub fn parse_macos_camera(stdout: &str) -> Vec<String> {
39 let mut devices = Vec::new();
40 let mut in_cameras = false;
41 for line in stdout.lines() {
42 let trimmed = line.trim();
43 let indent = line.len() - line.trim_start().len();
44 if trimmed.starts_with("Video Support:")
45 || trimmed.starts_with("Camera:")
46 || trimmed.starts_with("Cameras:")
47 {
48 in_cameras = true;
49 continue;
50 }
51 if in_cameras {
52 if indent < 4
53 && !trimmed.is_empty()
54 && !trimmed.starts_with("Camera")
55 && !trimmed.starts_with("Video Support")
56 {
57 in_cameras = false;
58 continue;
59 }
60 if (indent == 4 || indent == 6 || indent == 8) && trimmed.ends_with(':') {
61 let name = trimmed.trim_end_matches(':').trim().to_string();
62 if !name.is_empty() && is_real_camera(&name) {
63 let cleaned = clean_camera_name(&name);
64 if !devices.contains(&cleaned) {
65 devices.push(cleaned);
66 }
67 }
68 }
69 }
70 }
71 devices
72}
73
74pub(crate) fn detect_camera() -> Vec<String> {
75 #[cfg(target_os = "linux")]
76 {
77 let mut cameras = Vec::new();
78 if let Ok(entries) = std::fs::read_dir("/sys/class/video4linux") {
79 for entry in entries.filter_map(|e| e.ok()) {
80 let path = entry.path().join("name");
81 if path.exists() {
82 if let Ok(name) = std::fs::read_to_string(path) {
83 let trimmed = name.trim().to_string();
84 if !trimmed.is_empty() && is_real_camera(&trimmed) {
85 let cleaned = clean_camera_name(&trimmed);
86 if !cameras.contains(&cleaned) {
87 cameras.push(cleaned);
88 }
89 }
90 }
91 }
92 }
93 }
94 cameras
95 }
96
97 #[cfg(target_os = "macos")]
98 {
99 let mut cameras = crate::macos_ffi::get_usb_cameras();
100 cameras.retain(|name| is_real_camera(name));
102 cameras
103 }
104
105 #[cfg(target_os = "windows")]
106 {
107 let mut cameras = Vec::new();
116 for name in crate::win_setupapi::present_interface_device_names(
117 &crate::win_setupapi::KSCATEGORY_VIDEO_CAMERA,
118 ) {
119 if is_real_camera(&name) && !is_windows_virtual_camera(&name) {
120 let cleaned = clean_camera_name(&name);
121 if !cleaned.is_empty() && !cameras.contains(&cleaned) {
122 cameras.push(cleaned);
123 }
124 }
125 }
126 cameras
127 }
128
129 #[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "windows")))]
130 {
131 Vec::new()
132 }
133}
134
135#[cfg(test)]
136mod tests {
137 use super::*;
138
139 #[test]
140 fn test_is_real_camera() {
141 assert!(!is_real_camera("Infrared Camera"));
142 assert!(!is_real_camera("IR Camera"));
143 assert!(!is_real_camera("Integrated IR Camera"));
144 assert!(!is_real_camera("Depth Camera"));
145 assert!(is_real_camera("FaceTime HD Camera"));
146 assert!(is_real_camera("Integrated Camera"));
147 assert!(is_real_camera("HD Webcam C920"));
148 }
149
150 #[test]
151 fn test_is_windows_virtual_camera() {
152 assert!(is_windows_virtual_camera("Windows Virtual Camera Device"));
154 assert!(is_windows_virtual_camera("OBS Virtual Camera"));
155 assert!(!is_windows_virtual_camera("Logitech BRIO"));
157 assert!(!is_windows_virtual_camera("ASUS FHD webcam"));
158 }
159
160 #[test]
161 fn test_clean_camera_name() {
162 assert_eq!(
163 clean_camera_name("Integrated Camera: Real"),
164 "Integrated Camera"
165 );
166 assert_eq!(
167 clean_camera_name("Integrated Webcam: HD"),
168 "Integrated Webcam"
169 );
170 assert_eq!(clean_camera_name(" HD Webcam C920 "), "HD Webcam C920");
171 assert_eq!(
172 clean_camera_name("FaceTime HD Camera"),
173 "FaceTime HD Camera"
174 );
175 }
176
177 #[cfg(target_os = "macos")]
178 #[test]
179 fn test_parse_macos_camera() {
180 let sample = "Camera:\n\n FaceTime HD Camera:\n\n Model ID: UVC Camera VendorID_1452 ProductID_34068\n Unique ID: 0x8020000005ac8514\n";
181 assert_eq!(
182 parse_macos_camera(sample),
183 vec!["FaceTime HD Camera".to_string()]
184 );
185 }
186}