A facial recognition login service for Linux.
2

Configure Feed

Select the types of activity you want to include in your feed.

core: Filter enumerate() to only return real VIDEO_CAPTURE devices

UVC cameras commonly expose multiple /dev/video* nodes per physical
device: typically one or two real Video Capture interfaces plus matching
Metadata Capture siblings. Our enumerate() was accepting all of them by
filename pattern alone, so a Logitech BRIO (with video0/2 real + video1/3
metadata-only) showed up as four rows in `pareidolia cameras` and got
exercised four times by the capture hardware test.

The metadata-only nodes don't have VIDEO_CAPTURE capability, so opening
them for capture (set_format, REQBUFS, STREAMON) fails after the
firmware-touching ioctls have already woken the device. On the BRIO this
puts the IR illuminator into a 'warmed' state that lingers for several
seconds after the test ends — visible IR LEDs glowing between test runs
— and also causes control-query timeouts on subsequent v4l2-ctl
inspections.

Querying VIDIOC_QUERYCAP on the node (the device_caps field, which is
per-node rather than the union across the driver) and filtering by
VIDEO_CAPTURE excludes the metadata siblings without any extra fragility:
real capture devices keep advertising the cap; metadata ones don't.

After this, `pareidolia cameras` on a BRIO returns:
PATH NAME KIND
/dev/video2 Logitech BRIO ir
/dev/video0 Logitech BRIO rgb

instead of four rows where two were unusable noise.

Isaac Corbrey (May 27, 2026, 7:15 PM EDT) 3cb4bbce 8ee17ca5

+29 -10
+29 -10
crates/core/src/camera/v4l2.rs
··· 34 34 35 35 use self_cell::self_cell; 36 36 use v4l::buffer::Type; 37 + use v4l::capability::Flags as CapabilityFlags; 37 38 use v4l::io::mmap::Stream; 38 39 use v4l::io::traits::CaptureStream; 39 40 use v4l::video::Capture; ··· 230 231 } 231 232 } 232 233 233 - /// Walk `/dev/video*` and return a [`CameraInfo`] for each device that opens 234 - /// successfully and reports its capabilities. 234 + /// Walk `/dev/video*` and return a [`CameraInfo`] for each device that 235 + /// (a) opens successfully and (b) advertises the `VIDEO_CAPTURE` capability. 236 + /// 237 + /// The capability filter matters: UVC cameras commonly expose multiple 238 + /// `/dev/video*` nodes per physical device, where only some are real 239 + /// capture interfaces and the rest are metadata-only sidecars 240 + /// (`META_CAPTURE`). Returning the metadata nodes makes downstream code 241 + /// (configuration, `pareidolia cameras`, hardware tests) attempt to open 242 + /// them as capture devices, which fails after firmware-touching ioctls 243 + /// — on cameras like the Logitech BRIO that can leave the IR 244 + /// illuminator partially powered on for some seconds afterwards. 235 245 /// 236 246 /// Best-effort: devices that fail to open (perms, busy, no driver loaded) 237 247 /// are silently skipped. Callers wanting hard errors should open paths ··· 260 270 continue; 261 271 }; 262 272 263 - // Use the device's `card` field as the human-readable name, falling 264 - // back to the device-node basename if querying capabilities fails. 265 - let card_name = dev 266 - .query_caps() 267 - .ok() 268 - .map(|c| c.card.trim_end_matches('\0').trim().to_string()) 269 - .filter(|s| !s.is_empty()) 270 - .unwrap_or_else(|| name_str.to_string()); 273 + // Query node-level capabilities. The `capabilities` field on the 274 + // returned struct reflects this *node*'s caps (V4L2's 275 + // `device_caps`), not the union across the underlying driver — 276 + // which is what we want for filtering out metadata-only siblings. 277 + let Ok(caps) = dev.query_caps() else { continue }; 278 + if !caps.capabilities.contains(CapabilityFlags::VIDEO_CAPTURE) { 279 + continue; 280 + } 281 + 282 + let card_name = { 283 + let trimmed = caps.card.trim_end_matches('\0').trim().to_string(); 284 + if trimmed.is_empty() { 285 + name_str.to_string() 286 + } else { 287 + trimmed 288 + } 289 + }; 271 290 272 291 let kind = guess_camera_kind(&dev); 273 292