use crate::constants::{
DEFAULT_FPS, DEFAULT_RESOLUTION_HEIGHT, DEFAULT_RESOLUTION_WIDTH, FALLBACK_RESOLUTION_HEIGHT,
FALLBACK_RESOLUTION_WIDTH, FORMAT_RGB, MIN_RESOLUTION_HEIGHT, MIN_RESOLUTION_WIDTH,
};
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use uuid::Uuid;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum Platform {
Windows,
MacOS,
Linux,
Unknown,
}
impl Platform {
pub fn current() -> Self {
if cfg!(target_os = "windows") {
Platform::Windows
} else if cfg!(target_os = "macos") {
Platform::MacOS
} else if cfg!(target_os = "linux") {
Platform::Linux
} else {
Platform::Unknown
}
}
pub fn as_str(&self) -> &'static str {
match self {
Platform::Windows => "windows",
Platform::MacOS => "macos",
Platform::Linux => "linux",
Platform::Unknown => "unknown",
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CameraDeviceInfo {
pub id: String,
pub name: String,
pub description: Option<String>,
pub is_available: bool,
pub supports_formats: Vec<CameraFormat>,
pub platform: Platform,
}
impl CameraDeviceInfo {
pub fn new(id: String, name: String) -> Self {
Self {
id,
name,
description: None,
is_available: true,
supports_formats: Vec::new(),
platform: Platform::current(),
}
}
#[must_use]
pub fn with_description(mut self, description: String) -> Self {
self.description = Some(description);
self
}
#[must_use]
pub fn with_formats(mut self, formats: Vec<CameraFormat>) -> Self {
self.supports_formats = formats;
self
}
#[must_use]
pub fn with_availability(mut self, available: bool) -> Self {
self.is_available = available;
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct CameraFormat {
pub width: u32,
pub height: u32,
pub fps: f32,
pub format_type: String,
}
impl CameraFormat {
pub fn new(width: u32, height: u32, fps: f32) -> Self {
Self {
width,
height,
fps,
format_type: FORMAT_RGB.to_string(),
}
}
pub fn hd() -> Self {
Self::new(
DEFAULT_RESOLUTION_WIDTH,
DEFAULT_RESOLUTION_HEIGHT,
DEFAULT_FPS,
)
}
pub fn standard() -> Self {
Self::new(
FALLBACK_RESOLUTION_WIDTH,
FALLBACK_RESOLUTION_HEIGHT,
DEFAULT_FPS,
)
}
pub fn low() -> Self {
Self::new(MIN_RESOLUTION_WIDTH, MIN_RESOLUTION_HEIGHT, DEFAULT_FPS)
}
#[must_use]
pub fn with_format_type(mut self, format_type: String) -> Self {
self.format_type = format_type;
self
}
}
impl Default for CameraFormat {
fn default() -> Self {
Self::standard()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CameraFrame {
pub id: String,
pub data: Vec<u8>,
pub width: u32,
pub height: u32,
pub format: String,
pub timestamp: DateTime<Utc>,
pub device_id: String,
pub size_bytes: usize,
pub metadata: FrameMetadata,
}
impl CameraFrame {
pub fn new(data: Vec<u8>, width: u32, height: u32, device_id: String) -> Self {
let size_bytes = data.len();
Self {
id: Uuid::new_v4().to_string(),
data,
width,
height,
format: FORMAT_RGB.to_string(),
timestamp: Utc::now(),
device_id,
size_bytes,
metadata: FrameMetadata::default(),
}
}
#[must_use]
pub fn with_format(mut self, format: String) -> Self {
self.format = format;
self
}
pub fn aspect_ratio(&self) -> f32 {
#[allow(clippy::cast_precision_loss)]
let w = self.width as f32;
#[allow(clippy::cast_precision_loss)]
let h = self.height as f32;
w / h
}
pub fn is_valid(&self) -> bool {
!self.data.is_empty() && self.width > 0 && self.height > 0
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ControlApplicationResult {
pub applied: Vec<String>,
pub rejected: Vec<String>,
}
impl ControlApplicationResult {
pub fn fully_applied(&self) -> bool {
self.rejected.is_empty()
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct CameraControls {
pub auto_focus: Option<bool>,
pub focus_distance: Option<f32>,
pub auto_exposure: Option<bool>,
pub exposure_time: Option<f32>,
pub iso_sensitivity: Option<u32>,
pub white_balance: Option<WhiteBalance>,
pub aperture: Option<f32>,
pub zoom: Option<f32>,
pub brightness: Option<f32>,
pub contrast: Option<f32>,
pub saturation: Option<f32>,
pub sharpness: Option<f32>,
pub noise_reduction: Option<bool>,
pub image_stabilization: Option<bool>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum WhiteBalance {
Auto,
Daylight,
Fluorescent,
Incandescent,
Flash,
Cloudy,
Shade,
Custom(u32),
}
impl Default for CameraControls {
fn default() -> Self {
Self {
auto_focus: Some(true),
focus_distance: None,
auto_exposure: Some(true),
exposure_time: None,
iso_sensitivity: Some(400),
white_balance: Some(WhiteBalance::Auto),
aperture: None,
zoom: Some(1.0),
brightness: Some(0.0),
contrast: Some(0.0),
saturation: Some(0.0),
sharpness: Some(0.0),
noise_reduction: Some(true),
image_stabilization: Some(true),
}
}
}
impl CameraControls {
pub fn professional() -> Self {
Self {
auto_focus: Some(false),
focus_distance: Some(0.5),
auto_exposure: Some(false),
exposure_time: Some(1.0 / 60.0),
iso_sensitivity: Some(100),
white_balance: Some(WhiteBalance::Daylight),
aperture: Some(8.0),
zoom: Some(1.0),
brightness: Some(0.0),
contrast: Some(0.3),
saturation: Some(0.4),
sharpness: Some(0.5),
noise_reduction: Some(true),
image_stabilization: Some(true),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BurstConfig {
pub count: u32,
pub interval_ms: u32,
pub bracketing: Option<ExposureBracketing>,
pub focus_stacking: bool,
pub auto_save: bool,
pub save_directory: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExposureBracketing {
pub stops: Vec<f32>,
pub base_exposure: f32,
}
impl BurstConfig {
pub fn hdr_burst() -> Self {
Self {
count: 3,
interval_ms: 200,
bracketing: Some(ExposureBracketing {
stops: vec![-1.0, 0.0, 1.0],
base_exposure: 1.0 / 125.0,
}),
focus_stacking: false,
auto_save: true,
save_directory: Some("hdr_captures".to_string()),
}
}
}
#[allow(clippy::struct_excessive_bools)]
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct CameraCapabilityFlags {
pub auto_focus: bool,
pub manual_focus: bool,
pub auto_exposure: bool,
pub manual_exposure: bool,
pub white_balance: bool,
pub zoom: bool,
pub flash: bool,
pub burst_mode: bool,
pub hdr: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CameraCapabilities {
pub supports: CameraCapabilityFlags,
pub max_resolution: (u32, u32),
pub max_fps: f32,
pub exposure_range: Option<(f32, f32)>,
pub iso_range: Option<(u32, u32)>,
pub focus_range: Option<(f32, f32)>,
}
impl Default for CameraCapabilities {
fn default() -> Self {
Self {
supports: CameraCapabilityFlags {
auto_focus: true,
manual_focus: false,
auto_exposure: true,
manual_exposure: false,
white_balance: true,
zoom: false,
flash: false,
burst_mode: true,
hdr: false,
},
max_resolution: (1920, 1080),
max_fps: 30.0,
exposure_range: None,
iso_range: None,
focus_range: None,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct FrameMetadata {
pub exposure_time: Option<f32>,
pub iso_sensitivity: Option<u32>,
pub white_balance: Option<WhiteBalance>,
pub focus_distance: Option<f32>,
pub aperture: Option<f32>,
pub flash_fired: Option<bool>,
pub scene_mode: Option<String>,
pub capture_settings: Option<CameraControls>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CameraPerformanceMetrics {
pub capture_latency_ms: f32,
pub processing_time_ms: f32,
pub memory_usage_mb: f32,
pub fps_actual: f32,
pub dropped_frames: u32,
pub buffer_overruns: u32,
pub quality_score: f32,
}
impl Default for CameraPerformanceMetrics {
fn default() -> Self {
Self {
capture_latency_ms: 0.0,
processing_time_ms: 0.0,
memory_usage_mb: 0.0,
fps_actual: 0.0,
dropped_frames: 0,
buffer_overruns: 0,
quality_score: 0.0,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CameraInitParams {
pub device_id: String,
pub format: CameraFormat,
pub controls: CameraControls,
}
impl Default for CameraInitParams {
fn default() -> Self {
Self::new("0".to_string())
}
}
impl CameraInitParams {
pub fn new(device_id: String) -> Self {
Self {
device_id,
format: CameraFormat::standard(),
controls: CameraControls::default(),
}
}
#[must_use]
pub fn with_format(mut self, format: CameraFormat) -> Self {
self.format = format;
self
}
#[must_use]
pub fn with_controls(mut self, controls: CameraControls) -> Self {
self.controls = controls;
self
}
#[must_use]
pub fn with_auto_focus(mut self, enabled: bool) -> Self {
self.controls.auto_focus = Some(enabled);
self
}
#[must_use]
pub fn with_auto_exposure(mut self, enabled: bool) -> Self {
self.controls.auto_exposure = Some(enabled);
self
}
pub fn professional(device_id: String) -> Self {
Self {
device_id,
format: CameraFormat::new(2592, 1944, 15.0), controls: CameraControls::professional(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_platform_current_and_string() {
let platform = Platform::current();
assert_ne!(platform, Platform::Unknown);
assert!(["windows", "macos", "linux", "unknown"].contains(&platform.as_str()));
}
#[test]
fn test_camera_device_info_builders() {
let formats = vec![CameraFormat::standard(), CameraFormat::hd()];
let device = CameraDeviceInfo::new("0".to_string(), "Cam".to_string())
.with_description("Front camera".to_string())
.with_formats(formats.clone())
.with_availability(false);
assert_eq!(device.id, "0");
assert_eq!(device.name, "Cam");
assert_eq!(device.description.as_deref(), Some("Front camera"));
assert_eq!(device.supports_formats.len(), formats.len());
assert!(!device.is_available);
}
#[test]
fn test_camera_format_presets_and_builder() {
let hd = CameraFormat::hd();
let standard = CameraFormat::standard();
let low = CameraFormat::low();
assert!(hd.width >= standard.width);
assert!(standard.width >= low.width);
assert_eq!(CameraFormat::default(), standard);
let mjpeg = CameraFormat::new(800, 600, 24.0).with_format_type("MJPEG".to_string());
assert_eq!(mjpeg.width, 800);
assert_eq!(mjpeg.height, 600);
assert!((mjpeg.fps - 24.0).abs() < 1e-6);
assert_eq!(mjpeg.format_type, "MJPEG");
}
#[test]
fn test_camera_frame_methods() {
let data = vec![1, 2, 3, 4, 5, 6];
let frame = CameraFrame::new(data.clone(), 2, 1, "dev-0".to_string());
assert_eq!(frame.data, data);
assert_eq!(frame.size_bytes, 6);
assert_eq!(frame.width, 2);
assert_eq!(frame.height, 1);
assert_eq!(frame.device_id, "dev-0");
assert!(!frame.id.is_empty());
assert!(frame.is_valid());
assert!((frame.aspect_ratio() - 2.0).abs() < 1e-6);
let yuyv = frame.clone().with_format("YUYV".to_string());
assert_eq!(yuyv.format, "YUYV");
let invalid = CameraFrame::new(Vec::new(), 640, 480, "dev-1".to_string());
assert!(!invalid.is_valid());
}
#[test]
fn test_control_application_result_fully_applied() {
let ok = ControlApplicationResult {
applied: vec!["focus".to_string()],
rejected: Vec::new(),
};
assert!(ok.fully_applied());
let partial = ControlApplicationResult {
applied: vec!["focus".to_string()],
rejected: vec!["iso".to_string()],
};
assert!(!partial.fully_applied());
}
#[test]
fn test_camera_controls_defaults_and_professional_preset() {
let default_controls = CameraControls::default();
assert_eq!(default_controls.auto_focus, Some(true));
assert_eq!(default_controls.auto_exposure, Some(true));
assert_eq!(default_controls.iso_sensitivity, Some(400));
assert_eq!(default_controls.white_balance, Some(WhiteBalance::Auto));
let pro = CameraControls::professional();
assert_eq!(pro.auto_focus, Some(false));
assert_eq!(pro.auto_exposure, Some(false));
assert_eq!(pro.iso_sensitivity, Some(100));
assert_eq!(pro.white_balance, Some(WhiteBalance::Daylight));
assert!(matches!(pro.aperture, Some(v) if (v - 8.0).abs() < 1e-6));
}
#[test]
fn test_burst_and_capabilities_defaults() {
let burst = BurstConfig::hdr_burst();
assert_eq!(burst.count, 3);
assert_eq!(burst.interval_ms, 200);
assert!(burst.bracketing.is_some());
assert!(burst.auto_save);
assert_eq!(burst.save_directory.as_deref(), Some("hdr_captures"));
let bracketing = burst.bracketing.expect("hdr_burst should set bracketing");
assert_eq!(bracketing.stops.len(), 3);
assert!((bracketing.stops[0] - -1.0).abs() < 1e-6);
assert!((bracketing.stops[1] - 0.0).abs() < 1e-6);
assert!((bracketing.stops[2] - 1.0).abs() < 1e-6);
assert!(bracketing.base_exposure > 0.0);
let caps = CameraCapabilities::default();
assert!(caps.supports.auto_focus);
assert!(caps.supports.auto_exposure);
assert_eq!(caps.max_resolution, (1920, 1080));
assert!((caps.max_fps - 30.0).abs() < 1e-6);
}
#[test]
fn test_metadata_and_performance_defaults() {
let meta = FrameMetadata::default();
assert!(meta.exposure_time.is_none());
assert!(meta.iso_sensitivity.is_none());
assert!(meta.capture_settings.is_none());
let perf = CameraPerformanceMetrics::default();
assert!(perf.capture_latency_ms.abs() < 1e-6);
assert!(perf.processing_time_ms.abs() < 1e-6);
assert!(perf.memory_usage_mb.abs() < 1e-6);
assert!(perf.fps_actual.abs() < 1e-6);
assert!(perf.quality_score.abs() < 1e-6);
}
#[test]
fn test_camera_init_params_builders_and_professional() {
let default_params = CameraInitParams::default();
assert_eq!(default_params.device_id, "0");
let custom_format = CameraFormat::new(1024, 768, 25.0);
let custom_controls = CameraControls::default();
let built = CameraInitParams::new("2".to_string())
.with_format(custom_format.clone())
.with_controls(custom_controls.clone())
.with_auto_focus(false)
.with_auto_exposure(false);
assert_eq!(built.device_id, "2");
assert_eq!(built.format, custom_format);
assert_eq!(built.controls.auto_focus, Some(false));
assert_eq!(built.controls.auto_exposure, Some(false));
let pro = CameraInitParams::professional("9".to_string());
assert_eq!(pro.device_id, "9");
assert_eq!(pro.format.width, 2592);
assert_eq!(pro.format.height, 1944);
assert!((pro.format.fps - 15.0).abs() < 1e-6);
assert_eq!(pro.controls, CameraControls::professional());
}
}