From 0c20fb86633104744dbccf30ad732296694fff1b Mon Sep 17 00:00:00 2001 From: Dawid Rycerz Date: Sun, 8 Feb 2026 12:44:10 +0100 Subject: Initial pipewire --- src/main.rs | 207 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 207 insertions(+) create mode 100644 src/main.rs (limited to 'src/main.rs') diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..153b4e9 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,207 @@ +//! 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}"); + } + } +} -- cgit v1.2.3