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
15pub fn clean_camera_name(name: &str) -> String {
16 let trimmed = name.trim();
17 if trimmed.starts_with("Integrated Camera:") {
18 return "Integrated Camera".to_string();
19 }
20 if trimmed.starts_with("Integrated Webcam:") {
21 return "Integrated Webcam".to_string();
22 }
23 trimmed.to_string()
24}
25
26#[cfg(target_os = "macos")]
27pub fn parse_macos_camera(stdout: &str) -> Vec<String> {
28 let mut devices = Vec::new();
29 let mut in_cameras = false;
30 for line in stdout.lines() {
31 let trimmed = line.trim();
32 let indent = line.len() - line.trim_start().len();
33 if trimmed.starts_with("Video Support:")
34 || trimmed.starts_with("Camera:")
35 || trimmed.starts_with("Cameras:")
36 {
37 in_cameras = true;
38 continue;
39 }
40 if in_cameras {
41 if indent < 4
42 && !trimmed.is_empty()
43 && !trimmed.starts_with("Camera")
44 && !trimmed.starts_with("Video Support")
45 {
46 in_cameras = false;
47 continue;
48 }
49 if (indent == 4 || indent == 6 || indent == 8) && trimmed.ends_with(':') {
50 let name = trimmed.trim_end_matches(':').trim().to_string();
51 if !name.is_empty() && is_real_camera(&name) {
52 let cleaned = clean_camera_name(&name);
53 if !devices.contains(&cleaned) {
54 devices.push(cleaned);
55 }
56 }
57 }
58 }
59 }
60 devices
61}
62
63pub(crate) fn detect_camera() -> Vec<String> {
64 #[cfg(target_os = "linux")]
65 {
66 let mut cameras = Vec::new();
67 if let Ok(entries) = std::fs::read_dir("/sys/class/video4linux") {
68 for entry in entries.filter_map(|e| e.ok()) {
69 let path = entry.path().join("name");
70 if path.exists() {
71 if let Ok(name) = std::fs::read_to_string(path) {
72 let trimmed = name.trim().to_string();
73 if !trimmed.is_empty() && is_real_camera(&trimmed) {
74 let cleaned = clean_camera_name(&trimmed);
75 if !cameras.contains(&cleaned) {
76 cameras.push(cleaned);
77 }
78 }
79 }
80 }
81 }
82 }
83 cameras
84 }
85
86 #[cfg(target_os = "macos")]
87 {
88 let mut cameras = crate::macos_ffi::get_usb_cameras();
89 cameras.retain(|name| is_real_camera(name));
91 cameras
92 }
93
94 #[cfg(target_os = "windows")]
95 {
96 let cmd = "Get-PnpDevice -Class Camera,Image -PresentOnly -ErrorAction SilentlyContinue | Where-Object { $_.Status -eq 'OK' } | Select-Object -ExpandProperty FriendlyName";
97 if let Ok(output) = std::process::Command::new("powershell")
98 .args(["-Command", cmd])
99 .output()
100 {
101 if let Ok(stdout) = String::from_utf8(output.stdout) {
102 let mut cameras = Vec::new();
103 for line in stdout.lines() {
104 let trimmed = line.trim().to_string();
105 if !trimmed.is_empty() && is_real_camera(&trimmed) {
106 let cleaned = clean_camera_name(&trimmed);
107 if !cameras.contains(&cleaned) {
108 cameras.push(cleaned);
109 }
110 }
111 }
112 return cameras;
113 }
114 }
115 Vec::new()
116 }
117
118 #[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "windows")))]
119 {
120 Vec::new()
121 }
122}
123
124#[cfg(test)]
125mod tests {
126 use super::*;
127
128 #[test]
129 fn test_is_real_camera() {
130 assert!(!is_real_camera("Infrared Camera"));
131 assert!(!is_real_camera("IR Camera"));
132 assert!(!is_real_camera("Integrated IR Camera"));
133 assert!(!is_real_camera("Depth Camera"));
134 assert!(is_real_camera("FaceTime HD Camera"));
135 assert!(is_real_camera("Integrated Camera"));
136 assert!(is_real_camera("HD Webcam C920"));
137 }
138
139 #[test]
140 fn test_clean_camera_name() {
141 assert_eq!(
142 clean_camera_name("Integrated Camera: Real"),
143 "Integrated Camera"
144 );
145 assert_eq!(
146 clean_camera_name("Integrated Webcam: HD"),
147 "Integrated Webcam"
148 );
149 assert_eq!(clean_camera_name(" HD Webcam C920 "), "HD Webcam C920");
150 assert_eq!(
151 clean_camera_name("FaceTime HD Camera"),
152 "FaceTime HD Camera"
153 );
154 }
155
156 #[cfg(target_os = "macos")]
157 #[test]
158 fn test_parse_macos_camera() {
159 let sample = "Camera:\n\n FaceTime HD Camera:\n\n Model ID: UVC Camera VendorID_1452 ProductID_34068\n Unique ID: 0x8020000005ac8514\n";
160 assert_eq!(
161 parse_macos_camera(sample),
162 vec!["FaceTime HD Camera".to_string()]
163 );
164 }
165}