//! Encoding and decoding QR codes. use keyfork_bug as bug; use image::io::Reader as ImageReader; use std::{ io::{Cursor, Write}, time::{Duration, SystemTime}, process::{Command, Stdio}, }; use v4l::{ buffer::Type, io::{userptr::Stream, traits::CaptureStream}, video::Capture, FourCC, Device, }; /// A QR code could not be generated. #[derive(thiserror::Error, Debug)] pub enum QRGenerationError { /// The resulting QR coode could not be read from the generator program. #[error("{0}")] Io(#[from] std::io::Error), /// The generator program produced invalid data. #[error("Could not decode output of qrencode (this is a bug!): {0}")] StringParse(#[from] std::string::FromUtf8Error), } /// An error occurred while scanning for a QR code. #[derive(thiserror::Error, Debug)] pub enum QRCodeScanError { /// The camera could not load the requested format. #[error("Camera could not use {expected} format, instead used {actual}")] CameraGaveBadFormat { /// The expected format, in FourCC format. expected: String, /// The actual format, in FourCC format. actual: String, }, /// Interfacing with the camera resulted in an error. #[error("Unable to interface with camera: {0}")] CameraIO(#[from] std::io::Error), /// Decoding an image from the camera resulted in an error. #[error("Could not decode image: {0}")] ImageDecode(#[from] image::ImageError), } /// The level of error correction when generating a QR code. #[derive(Default)] pub enum ErrorCorrection { /// 7% of the QR code can be recovered. #[default] Lowest, /// 15% of the QR code can be recovered. Medium, /// 25% of the QR code can be recovered. Quartile, /// 30% of the QR code can be recovered. Highest, } /// Generate a terminal-printable QR code for a given string. Uses the `qrencode` CLI utility. /// /// # Errors /// The function may return an error if interacting with the QR code generation program fails. 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) } const VIDEO_FORMAT_READ_ERROR: &str = "Failed to read video device format"; /// Continuously scan the `index`-th camera for a QR code. #[cfg(feature = "decode-backend-rqrr")] pub fn scan_camera(timeout: Duration, index: usize) -> Result, QRCodeScanError> { let device = Device::new(index)?; let mut fmt = device.format().unwrap_or_else(bug::panic!(VIDEO_FORMAT_READ_ERROR)); fmt.fourcc = FourCC::new(b"MPG1"); device.set_format(&fmt)?; 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 = rqrr::PreparedImage::prepare(image); for grid in image.detect_grids() { if let Ok((_, content)) = grid.decode() { return Ok(Some(content)) } } } Ok(None) } /// Continuously scan the `index`-th camera for a QR code. #[cfg(feature = "decode-backend-zbar")] pub fn scan_camera(timeout: Duration, index: usize) -> Result, QRCodeScanError> { let device = Device::new(index)?; let mut fmt = device.format().unwrap_or_else(bug::panic!(VIDEO_FORMAT_READ_ERROR)); fmt.fourcc = FourCC::new(b"MPG1"); device.set_format(&fmt)?; let mut stream = Stream::with_buffers(&device, Type::VideoCapture, 4)?; let start = SystemTime::now(); let mut scanner = keyfork_zbar::image_scanner::ImageScanner::new(); 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()?; let image = keyfork_zbar::image::Image::from(image); for symbol in scanner.scan_image(&image) { return Ok(Some(String::from_utf8_lossy(symbol.data()).to_string())); } } Ok(None) }