summaryrefslogtreecommitdiff
path: root/src/main.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/main.rs')
-rw-r--r--src/main.rs207
1 files changed, 207 insertions, 0 deletions
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<dyn std::error::Error>> {
+ // 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<dyn std::error::Error>> {
+ 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, Box<dyn std::error::Error>> {
+ 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}");
+ }
+ }
+}