crabcamera 0.9.2

Advanced cross-platform camera integration for Tauri applications
Documentation
use crate::types::{CameraDeviceInfo, CameraFormat};

/// Device information alias
pub type DeviceInfo = CameraDeviceInfo;
/// Format information alias
pub type FormatInfo = CameraFormat;

/// Buffer management policy for captured frames
#[derive(Debug, Clone)]
pub enum BufferPolicy {
    /// Drop the oldest frame when buffer is full
    DropOldest {
        /// Maximum number of frames to hold
        capacity: usize,
    },
}

/// Audio capture mode configuration
#[derive(Debug, Clone)]
pub enum AudioMode {
    /// Audio capture disabled
    Disabled,
    /// Audio capture enabled
    Enabled,
}

/// Configuration for headless capture session
#[derive(Debug, Clone)]
pub struct CaptureConfig {
    /// Camera device ID to capture from
    pub device_id: String,
    /// Desired camera format (resolution, fps)
    pub format: CameraFormat,
    /// Buffer management policy
    pub buffer_policy: BufferPolicy,
    /// Audio capture mode
    pub audio_mode: AudioMode,
    /// Optional specific audio device ID
    pub audio_device_id: Option<String>,
}

impl CaptureConfig {
    /// Create new capture configuration with defaults
    pub fn new(device_id: String, format: CameraFormat) -> Self {
        Self {
            device_id,
            format,
            buffer_policy: BufferPolicy::DropOldest { capacity: 2 },
            audio_mode: AudioMode::Disabled,
            audio_device_id: None,
        }
    }
}

/// A captured video frame in headless mode
#[derive(Debug, Clone, serde::Serialize)]
pub struct Frame {
    /// Monotonically increasing sequence number
    pub sequence: u64,
    /// Timestamp in microseconds
    pub timestamp_us: u64,
    /// Frame width in pixels
    pub width: u32,
    /// Frame height in pixels
    pub height: u32,
    /// Pixel format string (e.g. "RGB8", "YUYV")
    pub format: String,
    /// Source device ID
    pub device_id: String,
    /// Raw frame data
    pub data: Vec<u8>,
}

/// A captured audio packet
#[derive(Debug, Clone)]
pub struct AudioPacket {
    /// Monotonically increasing sequence number
    pub sequence: u64,
    /// Timestamp in microseconds
    pub timestamp_us: u64,
    /// Sample rate in Hz
    pub sample_rate: u32,
    /// Number of audio channels
    pub channels: u16,
    /// Audio format string (e.g., "F32", "I16")
    pub format: String,
    /// Raw audio data
    pub data: Vec<u8>,
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_capture_config_defaults() {
        let format = CameraFormat::new(640, 480, 30.0);
        let cfg = CaptureConfig::new("dev-1".to_string(), format.clone());

        assert_eq!(cfg.device_id, "dev-1");
        assert_eq!(cfg.format, format);
        match cfg.buffer_policy {
            BufferPolicy::DropOldest { capacity } => assert_eq!(capacity, 2),
        }
        assert!(matches!(cfg.audio_mode, AudioMode::Disabled));
        assert!(cfg.audio_device_id.is_none());
    }

    #[test]
    fn test_frame_serialization_shape() {
        let frame = Frame {
            sequence: 7,
            timestamp_us: 123,
            width: 320,
            height: 240,
            format: "RGB".to_string(),
            device_id: "dev".to_string(),
            data: vec![1, 2, 3],
        };

        let json = serde_json::to_string(&frame).expect("frame should serialize");
        assert!(json.contains("\"sequence\":7"));
        assert!(json.contains("\"width\":320"));
        assert!(json.contains("\"format\":\"RGB\""));
    }

    #[test]
    fn test_audio_packet_clone_and_fields() {
        let pkt = AudioPacket {
            sequence: 3,
            timestamp_us: 1000,
            sample_rate: 48_000,
            channels: 2,
            format: "F32".to_string(),
            data: vec![0, 1, 2, 3],
        };
        let cloned = pkt.clone();

        assert_eq!(cloned.sequence, 3);
        assert_eq!(cloned.sample_rate, 48_000);
        assert_eq!(cloned.channels, 2);
        assert_eq!(cloned.format, "F32");
        assert_eq!(cloned.data.len(), 4);
    }
}