//! Main binary for the Geek szitman supercamera use clap::Parser; use geek_szitman_supercamera::{Error, SuperCamera, video}; use tracing::{error, info, warn}; use tracing_subscriber::{fmt, EnvFilter}; #[derive(Parser)] #[command( name = "geek-szitman-supercamera", about = "Rust implementation of Geek szitman supercamera endoscope viewer", version, author )] struct Cli { /// Enable debug logging #[arg(short, long)] debug: bool, /// Enable verbose logging #[arg(short, long)] verbose: bool, /// Video backend to use #[arg(short, long, value_enum, default_value = "pipewire")] backend: BackendChoice, /// Output directory for saved frames #[arg(short, long, default_value = "pics")] output_dir: String, /// Frame rate hint #[arg(short, long, default_value = "30")] fps: u32, /// Exit automatically after N seconds (0 = run until Ctrl+C) #[arg(long, default_value = "0")] timeout_seconds: u64, } #[derive(Clone, clap::ValueEnum)] enum BackendChoice { PipeWire, V4L2, Stdout, } impl std::fmt::Display for BackendChoice { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { BackendChoice::PipeWire => write!(f, "pipewire"), BackendChoice::V4L2 => write!(f, "v4l2"), BackendChoice::Stdout => write!(f, "stdout"), } } } fn main() -> Result<(), Box> { // Parse command line arguments let cli = Cli::parse(); // Initialize logging init_logging(cli.debug, cli.verbose)?; info!("Starting Geek szitman supercamera viewer"); info!("Backend: {}", cli.backend); info!("Output directory: {}", cli.output_dir); info!("Frame rate hint: {} fps", cli.fps); // Create output directory std::fs::create_dir_all(&cli.output_dir)?; // Set up signal handling let signal_handler = setup_signal_handling()?; // Create camera instance with selected backend let camera = match cli.backend { BackendChoice::PipeWire => SuperCamera::with_backend(crate::video::VideoBackendType::PipeWire), BackendChoice::V4L2 => SuperCamera::with_backend(crate::video::VideoBackendType::V4L2), BackendChoice::Stdout => SuperCamera::with_backend(crate::video::VideoBackendType::Stdout), }; let camera = match camera { Ok(camera) => camera, Err(e) => { error!("Failed to create camera: {}", e); return Err(e.into()); } }; // Start camera stream if let Err(e) = camera.start_stream() { error!("Failed to start camera stream: {}", e); return Err(e.into()); } info!("Camera stream started successfully"); info!("Press Ctrl+C to stop"); // Wait for shutdown signal or optional timeout if cli.timeout_seconds > 0 { let timed_out = !signal_handler .wait_for_shutdown_with_timeout(std::time::Duration::from_secs(cli.timeout_seconds)); if timed_out { info!("Timeout reached ({}s), initiating shutdown...", cli.timeout_seconds); } } else { signal_handler.wait_for_shutdown(); } info!("Shutting down..."); // Stop camera stream if let Err(e) = camera.stop_stream() { warn!("Error stopping camera stream: {}", e); } info!("Shutdown complete"); Ok(()) } /// Initialize logging system fn init_logging(debug: bool, verbose: bool) -> Result<(), Box> { let filter = if verbose { "geek_szitman_supercamera=trace" } else if debug { "geek_szitman_supercamera=debug" } else { "geek_szitman_supercamera=info" }; let env_filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new(filter)); fmt::Subscriber::builder() .with_env_filter(env_filter) .with_target(false) .with_thread_ids(false) .with_thread_names(false) .with_file(false) .with_line_number(false) .init(); Ok(()) } /// Set up signal handling for graceful shutdown fn setup_signal_handling( ) -> Result> { geek_szitman_supercamera::utils::SignalHandler::new() } /// Handle errors gracefully fn handle_error(error: Error) { match error { Error::Usb(e) => { error!("USB error: {}", e); match e { geek_szitman_supercamera::error::UsbError::DeviceNotFound => { eprintln!("Device not found. Please check that the camera is connected."); } geek_szitman_supercamera::error::UsbError::PermissionDenied => { eprintln!("Permission denied. Try running with sudo or add udev rules."); } geek_szitman_supercamera::error::UsbError::DeviceDisconnected => { eprintln!("Device disconnected."); } _ => { eprintln!("USB error: {e}"); } } } Error::Video(e) => { error!("Video backend error: {}", e); match e { geek_szitman_supercamera::error::VideoError::PipeWire(msg) => { eprintln!("PipeWire error: {msg}. Make sure PipeWire is running."); } geek_szitman_supercamera::error::VideoError::V4L2(msg) => { eprintln!("V4L2 error: {msg}. Make sure v4l2loopback is loaded."); } geek_szitman_supercamera::error::VideoError::Stdout(msg) => { eprintln!("Stdout error: {msg}. Check if stdout is writable."); } _ => { eprintln!("Video error: {e}"); } } } Error::Protocol(e) => { error!("Protocol error: {}", e); eprintln!("Protocol error: {e}. Check camera firmware version."); } Error::Jpeg(e) => { error!("JPEG error: {}", e); eprintln!("JPEG processing error: {e}"); } Error::System(e) => { error!("System error: {}", e); eprintln!("System error: {e}"); } Error::Generic(msg) => { error!("Generic error: {}", msg); eprintln!("Error: {msg}"); } } }