use crate::platform::PlatformCamera;
use crate::quality::QualityValidator;
use crate::types::{CameraFormat, CameraFrame, CameraInitParams};
use std::collections::HashMap;
use std::fs::File;
use std::sync::{Arc, Mutex as SyncMutex};
use tauri::command;
use tokio::sync::RwLock;
lazy_static::lazy_static! {
static ref CAMERA_REGISTRY: Arc<RwLock<HashMap<String, Arc<SyncMutex<PlatformCamera>>>>> = Arc::new(RwLock::new(HashMap::new()));
}
#[command]
pub async fn capture_single_photo(
device_id: Option<String>,
format: Option<CameraFormat>,
) -> Result<CameraFrame, String> {
log::info!("Capturing single photo from camera: {:?}", device_id);
let camera_id = device_id.unwrap_or_else(|| "0".to_string());
let capture_format = format.unwrap_or_else(CameraFormat::standard);
match capture_with_reconnect(camera_id, capture_format, 3).await {
Ok(frame) => {
log::info!(
"Successfully captured frame: {}x{} ({} bytes)",
frame.width,
frame.height,
frame.size_bytes
);
Ok(frame)
}
Err(e) => {
log::error!("Failed to capture frame: {}", e);
Err(format!("Failed to capture frame: {}", e))
}
}
}
#[command]
pub async fn capture_photo_sequence(
device_id: String,
count: u32,
interval_ms: u32,
format: Option<CameraFormat>,
) -> Result<Vec<CameraFrame>, String> {
log::info!(
"Capturing {} photos from camera {} with {}ms interval",
count,
device_id,
interval_ms
);
if count == 0 || count > 20 {
return Err("Invalid photo count (must be 1-20)".to_string());
}
let capture_format = format.unwrap_or_else(CameraFormat::standard);
let camera = match get_or_create_camera(device_id.clone(), capture_format).await {
Ok(cam) => cam,
Err(e) => return Err(e),
};
{
let camera_clone = camera.clone();
tokio::task::spawn_blocking(move || {
if let Ok(mut camera_guard) = camera_clone.lock() {
if let Err(e) = camera_guard.start_stream() {
log::warn!("Failed to start camera stream: {}", e);
}
}
})
.await
.map_err(|e| format!("Task join error: {}", e))?;
}
let mut frames = Vec::new();
for i in 0..count {
log::debug!("Capturing photo {} of {}", i + 1, count);
let camera_clone = camera.clone();
let frame = tokio::task::spawn_blocking(move || {
let mut camera_guard = camera_clone
.lock()
.map_err(|_| "Mutex poisoned".to_string())?;
camera_guard
.capture_frame()
.map_err(|e| format!("Failed to capture frame: {}", e))
})
.await
.map_err(|e| format!("Task join error: {}", e))??;
frames.push(frame);
if i < count - 1 {
tokio::time::sleep(tokio::time::Duration::from_millis(interval_ms as u64)).await;
}
}
log::info!("Successfully captured {} photos", frames.len());
Ok(frames)
}
#[command]
pub async fn capture_with_quality_retry(
device_id: Option<String>,
max_attempts: Option<u32>,
min_quality_score: Option<f32>,
format: Option<CameraFormat>,
) -> Result<CameraFrame, String> {
let camera_id = device_id.unwrap_or_else(|| "0".to_string());
let attempts = max_attempts.unwrap_or(10).min(50); let quality_threshold = min_quality_score.unwrap_or(0.7).clamp(0.0, 1.0);
let capture_format = format.unwrap_or_else(CameraFormat::standard);
log::info!(
"Starting quality capture: camera={}, max_attempts={}, min_quality={}",
camera_id,
attempts,
quality_threshold
);
let camera = match get_or_create_camera(camera_id.clone(), capture_format).await {
Ok(cam) => cam,
Err(e) => return Err(e),
};
{
let camera_clone = camera.clone();
tokio::task::spawn_blocking(move || {
if let Ok(mut camera_guard) = camera_clone.lock() {
if let Err(e) = camera_guard.start_stream() {
log::warn!("Failed to start camera stream: {}", e);
}
}
})
.await
.map_err(|e| format!("Task join error: {}", e))?;
}
let validator = QualityValidator::default();
let mut best_frame: Option<(CameraFrame, f32)> = None;
for attempt in 1..=attempts {
let frame = {
let camera_clone = camera.clone();
tokio::task::spawn_blocking(move || {
let mut camera_guard = camera_clone
.lock()
.map_err(|_| "Mutex poisoned".to_string())?;
camera_guard.capture_frame().map_err(|e| e.to_string())
})
.await
.map_err(|e| format!("Task join error: {}", e))??
};
let quality = validator.validate_frame(&frame);
let score = quality.score.overall;
log::debug!(
"Attempt {}/{}: quality_score={:.3} (blur={:.3}, exposure={:.3})",
attempt,
attempts,
score,
quality.score.blur,
quality.score.exposure
);
if best_frame.is_none() || score > best_frame.as_ref().unwrap().1 {
best_frame = Some((frame.clone(), score));
}
if score >= quality_threshold {
log::info!(
"Quality threshold met on attempt {}: score={:.3} >= {:.3}",
attempt,
score,
quality_threshold
);
return Ok(frame);
}
if attempt < attempts {
tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
}
}
if let Some((frame, score)) = best_frame {
log::warn!(
"Quality threshold not met after {} attempts. Returning best frame: score={:.3}",
attempts,
score
);
Ok(frame)
} else {
Err(format!(
"Failed to capture any valid frames after {} attempts",
attempts
))
}
}
#[command]
pub async fn set_frame_callback(
device_id: String,
format: Option<CameraFormat>,
) -> Result<String, String> {
log::info!("Setting frame callback for device: {}", device_id);
let capture_format = format.unwrap_or_else(CameraFormat::standard);
let camera = match get_or_create_camera(device_id.clone(), capture_format).await {
Ok(cam) => cam,
Err(e) => return Err(e),
};
let device_id_clone = device_id.clone();
let callback = move |frame: CameraFrame| {
log::debug!(
"Callback received frame from {}: {}x{} ({} bytes)",
device_id_clone,
frame.width,
frame.height,
frame.size_bytes
);
};
let camera_clone = camera.clone();
let device_id_clone = device_id.clone();
tokio::task::spawn_blocking(move || {
let mut camera_guard = camera_clone
.lock()
.map_err(|_| "Mutex poisoned".to_string())?;
match camera_guard.frame_callback(callback) {
Ok(_) => {
log::info!("Frame callback set for device: {}", device_id_clone);
Ok(format!("Callback set for camera {}", device_id_clone))
}
Err(e) => {
log::error!("Failed to set frame callback: {}", e);
Err(format!("Failed to set callback: {}", e))
}
}
})
.await
.map_err(|e| format!("Task join error: {}", e))?
}
#[command]
pub async fn start_camera_preview(
device_id: String,
format: Option<CameraFormat>,
) -> Result<String, String> {
log::info!("Starting camera preview for device: {}", device_id);
let capture_format = format.unwrap_or_else(CameraFormat::standard);
let camera = match get_or_create_camera(device_id.clone(), capture_format).await {
Ok(cam) => cam,
Err(e) => return Err(e),
};
let camera_clone = camera.clone();
let device_id_clone = device_id.clone();
tokio::task::spawn_blocking(move || {
let mut camera_guard = camera_clone
.lock()
.map_err(|_| "Mutex poisoned".to_string())?;
match camera_guard.start_stream() {
Ok(_) => {
log::info!("Camera preview started for device: {}", device_id_clone);
Ok(format!("Preview started for camera {}", device_id_clone))
}
Err(e) => {
log::error!("Failed to start camera preview: {}", e);
Err(format!("Failed to start camera preview: {}", e))
}
}
})
.await
.map_err(|e| format!("Task join error: {}", e))?
}
#[command]
pub async fn stop_camera_preview(device_id: String) -> Result<String, String> {
log::info!("Stopping camera preview for device: {}", device_id);
let registry = CAMERA_REGISTRY.read().await;
if let Some(camera) = registry.get(&device_id) {
let camera_clone = camera.clone();
let device_id_clone = device_id.clone();
tokio::task::spawn_blocking(move || {
let mut camera_guard = camera_clone
.lock()
.map_err(|_| "Mutex poisoned".to_string())?;
match camera_guard.stop_stream() {
Ok(_) => {
log::info!("Camera preview stopped for device: {}", device_id_clone);
Ok(format!("Preview stopped for camera {}", device_id_clone))
}
Err(e) => {
log::error!("Failed to stop camera preview: {}", e);
Err(format!("Failed to stop camera preview: {}", e))
}
}
})
.await
.map_err(|e| format!("Task join error: {}", e))?
} else {
let msg = format!("No active camera found with ID: {}", device_id);
log::warn!("{}", msg);
Err(msg)
}
}
#[command]
pub async fn release_camera(device_id: String) -> Result<String, String> {
log::info!("Releasing camera: {}", device_id);
let mut registry = CAMERA_REGISTRY.write().await;
if let Some(camera) = registry.remove(&device_id) {
let camera_clone = camera.clone();
let device_id_clone = device_id.clone();
tokio::task::spawn_blocking(move || {
if let Ok(mut camera_guard) = camera_clone.lock() {
let _ = camera_guard.stop_stream(); log::info!("Camera {} released", device_id_clone);
}
})
.await
.ok();
Ok(format!("Camera {} released", device_id))
} else {
let msg = format!("No active camera found with ID: {}", device_id);
log::info!("{}", msg);
Ok(msg) }
}
#[command]
pub async fn get_capture_stats(device_id: String) -> Result<CaptureStats, String> {
let registry = CAMERA_REGISTRY.read().await;
if let Some(camera) = registry.get(&device_id) {
let camera_clone = camera.clone();
let device_id_clone = device_id.clone();
let stats = tokio::task::spawn_blocking(move || {
let camera_guard = camera_clone
.lock()
.map_err(|_| "Mutex poisoned".to_string())?;
let is_active = camera_guard.is_available();
let device_id_opt = camera_guard.get_device_id();
Ok::<CaptureStats, String>(CaptureStats {
device_id: device_id_clone,
is_active,
device_info: device_id_opt.map(|s| s.to_string()),
})
})
.await
.map_err(|e| format!("Task join error: {}", e))??;
Ok(stats)
} else {
Ok(CaptureStats {
device_id: device_id.clone(),
is_active: false,
device_info: None,
})
}
}
#[command]
pub async fn save_frame_to_disk(frame: CameraFrame, file_path: String) -> Result<String, String> {
log::info!("Saving frame {} to disk: {}", frame.id, file_path);
let img = image::RgbImage::from_vec(frame.width, frame.height, frame.data)
.ok_or_else(|| "Failed to create image from frame data".to_string())?;
let dynamic_img = image::DynamicImage::ImageRgb8(img);
let format = if file_path.to_lowercase().ends_with(".jpg")
|| file_path.to_lowercase().ends_with(".jpeg")
{
image::ImageFormat::Jpeg
} else {
image::ImageFormat::Png
};
let file_path_clone = file_path.clone();
match tokio::task::spawn_blocking(move || {
dynamic_img.save_with_format(&file_path_clone, format)
})
.await
{
Ok(Ok(_)) => {
log::info!("Frame saved successfully to: {}", file_path);
Ok(format!("Frame saved to {}", file_path))
}
Ok(Err(e)) => {
log::error!("Failed to save frame: {}", e);
Err(format!("Failed to save frame: {}", e))
}
Err(e) => {
log::error!("Task join error: {}", e);
Err("Failed to execute save task".to_string())
}
}
}
#[command]
pub async fn save_frame_compressed(
frame: CameraFrame,
file_path: String,
quality: Option<u8>,
) -> Result<String, String> {
log::info!(
"Saving compressed frame {} to disk: {}",
frame.id,
file_path
);
let quality = quality.unwrap_or(85);
let img = image::RgbImage::from_vec(frame.width, frame.height, frame.data)
.ok_or_else(|| "Failed to create image from frame data".to_string())?;
let dynamic_img = image::DynamicImage::ImageRgb8(img);
let file_path_clone = file_path.clone();
match tokio::task::spawn_blocking(move || {
let mut file = File::create(&file_path_clone)?;
let encoder = image::codecs::jpeg::JpegEncoder::new_with_quality(&mut file, quality);
dynamic_img.write_with_encoder(encoder)
})
.await
{
Ok(Ok(_)) => {
log::info!("Compressed frame saved to: {}", file_path);
Ok(format!("Compressed frame saved to {}", file_path))
}
Ok(Err(e)) => {
log::error!("Failed to save compressed frame: {}", e);
Err(format!("Failed to save compressed frame: {}", e))
}
Err(e) => {
log::error!("Task join error: {}", e);
Err("Failed to execute save task".to_string())
}
}
}
pub async fn get_or_create_camera(
device_id: String,
format: CameraFormat,
) -> Result<Arc<SyncMutex<PlatformCamera>>, String> {
{
let registry = CAMERA_REGISTRY.read().await;
if let Some(camera) = registry.get(&device_id) {
log::debug!("Using existing camera: {}", device_id);
return Ok(camera.clone());
}
}
let mut registry = CAMERA_REGISTRY.write().await;
if let Some(camera) = registry.get(&device_id) {
log::debug!("Using camera created by another task: {}", device_id);
return Ok(camera.clone());
}
log::debug!("Creating new camera: {}", device_id);
let params = CameraInitParams::new(device_id.clone()).with_format(format);
match PlatformCamera::new(params) {
Ok(camera) => {
let camera_arc = Arc::new(SyncMutex::new(camera));
registry.insert(device_id.clone(), camera_arc.clone());
Ok(camera_arc)
}
Err(e) => {
log::error!("Failed to create camera: {}", e);
Err(format!("Failed to create camera: {}", e))
}
}
}
pub async fn reconnect_camera(
device_id: String,
format: CameraFormat,
max_retries: u32,
) -> Result<Arc<SyncMutex<PlatformCamera>>, String> {
log::info!(
"Attempting to reconnect camera: {} (max retries: {})",
device_id,
max_retries
);
{
let mut registry = CAMERA_REGISTRY.write().await;
if let Some(old_camera) = registry.remove(&device_id) {
let old_camera_clone = old_camera.clone();
tokio::task::spawn_blocking(move || {
if let Ok(mut camera_guard) = old_camera_clone.lock() {
let _ = camera_guard.stop_stream();
log::debug!("Removed old camera instance from registry");
}
})
.await
.ok();
}
}
for attempt in 1..=max_retries {
log::debug!(
"Reconnection attempt {}/{} for camera: {}",
attempt,
max_retries,
device_id
);
match get_or_create_camera(device_id.clone(), format.clone()).await {
Ok(camera) => {
log::info!("Camera reconnected successfully on attempt {}", attempt);
return Ok(camera);
}
Err(e) => {
log::warn!("Reconnection attempt {} failed: {}", attempt, e);
if attempt < max_retries {
let backoff_ms = (100 * 2_u64.pow(attempt - 1)).min(2000);
tokio::time::sleep(tokio::time::Duration::from_millis(backoff_ms)).await;
}
}
}
}
Err(format!(
"Failed to reconnect camera after {} attempts",
max_retries
))
}
pub async fn capture_with_reconnect(
device_id: String,
format: CameraFormat,
max_reconnect_attempts: u32,
) -> Result<CameraFrame, String> {
log::debug!(
"Attempting capture with reconnect for device: {}",
device_id
);
let camera = match get_or_create_camera(device_id.clone(), format.clone()).await {
Ok(cam) => cam,
Err(e) => return Err(format!("Failed to get camera: {}", e)),
};
let camera_clone = camera.clone();
let capture_result = tokio::task::spawn_blocking(move || {
let mut camera_guard = camera_clone
.lock()
.map_err(|_| "Mutex poisoned".to_string())?;
if let Err(e) = camera_guard.start_stream() {
log::warn!("Failed to start stream: {}", e);
}
for i in 0..5 {
match camera_guard.capture_frame() {
Ok(_) => {
log::debug!("Warmup frame {} captured", i + 1);
}
Err(e) => {
log::debug!(
"Warmup frame {} failed (normal during startup): {}",
i + 1,
e
);
}
}
std::thread::sleep(std::time::Duration::from_millis(30));
}
camera_guard
.capture_frame()
.map_err(|e| format!("Initial capture failed: {}, attempting reconnect", e))
})
.await
.map_err(|e| format!("Task join error: {}", e))?;
if let Ok(frame) = capture_result {
return Ok(frame);
}
let camera = reconnect_camera(device_id.clone(), format, max_reconnect_attempts).await?;
tokio::task::spawn_blocking(move || {
let mut camera_guard = camera.lock().map_err(|_| "Mutex poisoned".to_string())?;
if let Err(e) = camera_guard.start_stream() {
log::warn!("Failed to start stream after reconnect: {}", e);
}
for _ in 0..10 {
let _ = camera_guard.capture_frame();
std::thread::sleep(std::time::Duration::from_millis(50));
}
camera_guard
.capture_frame()
.map_err(|e| format!("Capture failed after reconnection: {}", e))
})
.await
.map_err(|e| format!("Task join error: {}", e))?
}
pub struct FramePool {
pool: Arc<SyncMutex<Vec<Vec<u8>>>>,
max_frames: usize,
frame_size: usize,
}
impl FramePool {
pub fn new(max_frames: usize, frame_size: usize) -> Self {
let mut pool = Vec::with_capacity(max_frames);
for _ in 0..max_frames {
pool.push(Vec::with_capacity(frame_size));
}
Self {
pool: Arc::new(SyncMutex::new(pool)),
max_frames,
frame_size,
}
}
pub async fn get_buffer(&self) -> Vec<u8> {
let pool = self.pool.clone();
let frame_size = self.frame_size;
tokio::task::spawn_blocking(move || {
let mut pool_guard = pool.lock().unwrap();
pool_guard
.pop()
.unwrap_or_else(|| Vec::with_capacity(frame_size))
})
.await
.unwrap()
}
pub async fn return_buffer(&self, mut buffer: Vec<u8>) {
let pool = self.pool.clone();
let max_frames = self.max_frames;
tokio::task::spawn_blocking(move || {
buffer.clear();
let mut pool_guard = pool.lock().unwrap();
if pool_guard.len() < max_frames {
pool_guard.push(buffer);
}
})
.await
.ok();
}
}
lazy_static::lazy_static! {
static ref FRAME_POOL: FramePool = FramePool::new(10, 1920 * 1080 * 3); }
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct CaptureStats {
pub device_id: String,
pub is_active: bool,
pub device_info: Option<String>,
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_quality_retry_returns_best_frame() {
let result = capture_with_quality_retry(
Some("test_device".to_string()),
Some(3),
Some(0.9), None,
)
.await;
assert!(result.is_err() || result.is_ok());
}
#[test]
fn test_quality_threshold_clamping() {
assert_eq!(1.5_f32.clamp(0.0, 1.0), 1.0);
assert_eq!((-0.5_f32).clamp(0.0, 1.0), 0.0);
assert_eq!(0.75_f32.clamp(0.0, 1.0), 0.75);
}
#[test]
fn test_max_attempts_capping() {
let attempts = 50;
assert_eq!(attempts, 50);
let attempts = 10_u32;
assert_eq!(attempts, 10);
}
}