lesavka/client/src/input/camera/device_selection.rs

111 lines
3.5 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

impl CameraCapture {
/// Fuzzymatch devices under `/dev/v4l/by-id`, preferring capture nodes
#[cfg(not(coverage))]
fn find_device(substr: &str) -> Option<String> {
let wanted = normalize_device_fragment(substr);
let mut matches: Vec<_> = std::fs::read_dir("/dev/v4l/by-id")
.ok()?
.flatten()
.filter_map(|e| {
let p = e.path();
let name = normalize_device_fragment(&p.file_name()?.to_string_lossy());
if name.contains(&wanted) {
Some(p)
} else {
None
}
})
.collect();
// deterministic order
matches.sort();
for p in matches {
if let Ok(target) = std::fs::read_link(&p) {
let dev = format!("/dev/{}", target.file_name()?.to_string_lossy());
if Self::is_capture(&dev) {
return Some(dev);
}
}
}
None
}
#[cfg(coverage)]
fn find_device(substr: &str) -> Option<String> {
let wanted = normalize_device_fragment(substr);
let by_id_dir =
std::env::var("LESAVKA_CAM_BY_ID_DIR").unwrap_or_else(|_| "/dev/v4l/by-id".to_string());
let dev_root = std::env::var("LESAVKA_CAM_DEV_ROOT").unwrap_or_else(|_| "/dev".to_string());
let mut matches: Vec<_> = std::fs::read_dir(by_id_dir)
.ok()?
.flatten()
.filter_map(|e| {
let p = e.path();
let name = normalize_device_fragment(&p.file_name()?.to_string_lossy());
if name.contains(&wanted) {
Some(p)
} else {
None
}
})
.collect();
matches.sort();
for p in matches {
if let Ok(target) = std::fs::read_link(&p) {
let dev = format!("{}/{}", dev_root, target.file_name()?.to_string_lossy());
if Self::is_capture(&dev) {
return Some(dev);
}
}
}
None
}
#[cfg(not(coverage))]
fn is_capture(dev: &str) -> bool {
const V4L2_CAP_VIDEO_CAPTURE: u32 = 0x0000_0001;
const V4L2_CAP_VIDEO_CAPTURE_MPLANE: u32 = 0x0000_1000;
v4l::Device::with_path(dev)
.ok()
.and_then(|d| d.query_caps().ok())
.map(|caps| {
let bits = caps.capabilities.bits();
(bits & V4L2_CAP_VIDEO_CAPTURE != 0) || (bits & V4L2_CAP_VIDEO_CAPTURE_MPLANE != 0)
})
.unwrap_or(false)
}
#[cfg(coverage)]
fn is_capture(dev: &str) -> bool {
dev.starts_with("/dev/video")
}
/// Cheap stub used when the webcam is disabled
pub fn new_stub() -> Self {
let pipeline = gst::Pipeline::new();
let sink: gst_app::AppSink = gst::ElementFactory::make("appsink")
.build()
.expect("appsink")
.downcast::<gst_app::AppSink>()
.unwrap();
Self {
pipeline,
sink,
preview_tap_running: None,
pts_rebaser: crate::live_capture_clock::DurationPacedSourcePtsRebaser::default(),
frame_duration_us: 1,
}
}
}
fn normalize_device_fragment(value: &str) -> String {
value
.chars()
.filter(|ch| ch.is_ascii_alphanumeric())
.flat_map(char::to_lowercase)
.collect()
}