use image::io::Reader as ImageReader; use rqrr::PreparedImage; use std::{ io::{Cursor, Write}, time::{Duration, SystemTime}, process::{Command, Stdio}, }; use v4l::{ buffer::Type, io::{userptr::Stream, traits::CaptureStream}, video::Capture, Device, FourCC, }; static MJPEG: &[u8; 4] = b"MJPG"; #[derive(thiserror::Error, Debug)] pub enum QRGenerationError { #[error("{0}")] Io(#[from] std::io::Error), #[error("Could not decode output of qrencode (this is a bug!): {0}")] StringParse(#[from] std::string::FromUtf8Error), } #[derive(thiserror::Error, Debug)] pub enum QRCodeScanError { #[error("Camera could not use {expected} format, instead used {actual}")] CameraGaveBadFormat { expected: String, actual: String, }, #[error("Unable to interface with camera: {0}")] CameraIO(#[from] std::io::Error), #[error("Could not decode image: {0}")] ImageDecode(#[from] image::ImageError), #[error("Could not format FourCC as string (this is a bug!): {0}")] FourCC(#[from] std::string::FromUtf8Error), } #[derive(Default)] pub enum ErrorCorrection { #[default] Lowest, Medium, Quartile, Highest, } /// Generate a terminal-printable QR code for a given string. Uses the `qrencode` CLI utility. pub fn qrencode( text: &str, error_correction: impl Into>, ) -> Result { let error_correction_arg = match error_correction.into().unwrap_or_default() { ErrorCorrection::Lowest => "L", ErrorCorrection::Medium => "M", ErrorCorrection::Quartile => "Q", ErrorCorrection::Highest => "H", }; let mut qrencode = Command::new("qrencode") .arg("-t") .arg("ansiutf8") .arg("-m") .arg("2") .arg("-l") .arg(error_correction_arg) .stdin(Stdio::piped()) .stdout(Stdio::piped()) .spawn()?; if let Some(stdin) = qrencode.stdin.as_mut() { stdin.write_all(text.as_bytes())?; } let output = qrencode.wait_with_output()?; let result = String::from_utf8(output.stdout)?; Ok(result) } pub fn scan_camera(timeout: Duration, index: usize) -> Result, QRCodeScanError> { let device = Device::new(index)?; let mut format = device.format()?; format.width = 1280; format.height = 720; format.fourcc = FourCC::new(MJPEG); let format = device.set_format(&format)?; if MJPEG != &format.fourcc.repr { return Err(QRCodeScanError::CameraGaveBadFormat { expected: String::from_utf8(MJPEG.to_vec())?, actual: String::from_utf8(format.fourcc.repr.to_vec())?, }) } let mut stream = Stream::with_buffers(&device, Type::VideoCapture, 4)?; let start = SystemTime::now(); while SystemTime::now() .duration_since(start) .unwrap_or(Duration::from_secs(0)) < timeout { let (buffer, _) = stream.next()?; let image = ImageReader::new(Cursor::new(buffer)) .with_guessed_format()? .decode()? .to_luma8(); let mut image = PreparedImage::prepare(image); for grid in image.detect_grids() { if let Ok((_, content)) = grid.decode() { return Ok(Some(content)) } } } Ok(None) }