From 6b0753b072a28beb70b4b5c03a535e26521f15ad Mon Sep 17 00:00:00 2001 From: Nikolaos Karaolidis Date: Sat, 18 Oct 2025 23:46:14 +0100 Subject: [PATCH] chore: refactor camera trait, optimize setter Signed-off-by: Nikolaos Karaolidis --- src/camera/devices/mod.rs | 21 ------ src/camera/mod.rs | 141 +++++++++++++++++++------------------- src/camera/ptp/hex.rs | 6 +- src/cli/common/file.rs | 2 + src/cli/simulation/mod.rs | 76 ++++++++++---------- src/usb/mod.rs | 16 ++++- 6 files changed, 132 insertions(+), 130 deletions(-) diff --git a/src/camera/devices/mod.rs b/src/camera/devices/mod.rs index 4513ff4..7a80d30 100644 --- a/src/camera/devices/mod.rs +++ b/src/camera/devices/mod.rs @@ -1,4 +1,3 @@ -use anyhow::bail; use rusb::GlobalContext; use super::CameraImpl; @@ -13,26 +12,6 @@ pub struct SupportedCamera { pub impl_factory: ImplFactory

, } -impl SupportedCamera

{ - pub fn new_camera(&self, device: &rusb::Device

) -> anyhow::Result>> { - let descriptor = device.device_descriptor()?; - - let matches = - descriptor.vendor_id() == self.vendor && descriptor.product_id() == self.product; - - if !matches { - bail!( - "Device with vendor {:04x} and product {:04x} does not match {}", - descriptor.vendor_id(), - descriptor.product_id(), - self.name - ); - } - - Ok((self.impl_factory)()) - } -} - macro_rules! default_camera_impl { ( $const_name:ident, diff --git a/src/camera/mod.rs b/src/camera/mod.rs index 428bdd3..b5c87e7 100644 --- a/src/camera/mod.rs +++ b/src/camera/mod.rs @@ -21,6 +21,8 @@ use ptp::{ use ptp_cursor::{PtpDeserialize, PtpSerialize}; use rusb::{GlobalContext, constants::LIBUSB_CLASS_IMAGE}; +use crate::usb::find_endpoint; + const SESSION: u32 = 1; pub struct Camera { @@ -29,75 +31,6 @@ pub struct Camera { } impl Camera { - pub fn from_device(device: &rusb::Device) -> anyhow::Result { - for supported_camera in devices::SUPPORTED { - if let Ok(r#impl) = supported_camera.new_camera(device) { - let bus = device.bus_number(); - let address = device.address(); - - let config_descriptor = device.active_config_descriptor()?; - - let interface_descriptor = config_descriptor - .interfaces() - .flat_map(|i| i.descriptors()) - .find(|x| x.class_code() == LIBUSB_CLASS_IMAGE) - .ok_or(rusb::Error::NotFound)?; - - let interface = interface_descriptor.interface_number(); - debug!("Found interface {interface}"); - - let handle = device.open()?; - handle.claim_interface(interface)?; - - let bulk_in = Self::find_endpoint( - &interface_descriptor, - rusb::Direction::In, - rusb::TransferType::Bulk, - )?; - let bulk_out = Self::find_endpoint( - &interface_descriptor, - rusb::Direction::Out, - rusb::TransferType::Bulk, - )?; - - let transaction_id = 0; - - let chunk_size = r#impl.chunk_size(); - - let mut ptp = Ptp { - bus, - address, - interface, - bulk_in, - bulk_out, - handle, - transaction_id, - chunk_size, - }; - - debug!("Opening session"); - let () = r#impl.open_session(&mut ptp, SESSION)?; - debug!("Session opened"); - - return Ok(Self { r#impl, ptp }); - } - } - - bail!("Device not supported"); - } - - fn find_endpoint( - interface_descriptor: &rusb::InterfaceDescriptor<'_>, - direction: rusb::Direction, - transfer_type: rusb::TransferType, - ) -> Result { - interface_descriptor - .endpoint_descriptors() - .find(|ep| ep.direction() == direction && ep.transfer_type() == transfer_type) - .map(|x| x.address()) - .ok_or(rusb::Error::NotFound) - } - pub fn name(&self) -> &'static str { self.r#impl.supported_camera().name } @@ -336,6 +269,76 @@ impl Drop for Camera { } } +impl TryFrom<&rusb::Device> for Camera { + type Error = anyhow::Error; + + fn try_from(device: &rusb::Device) -> anyhow::Result { + let descriptor = device.device_descriptor()?; + + let vendor = descriptor.vendor_id(); + let product = descriptor.product_id(); + + for supported_camera in devices::SUPPORTED { + if vendor != supported_camera.vendor || product != supported_camera.product { + continue; + } + + let r#impl = (supported_camera.impl_factory)(); + + let bus = device.bus_number(); + let address = device.address(); + + let config_descriptor = device.active_config_descriptor()?; + + let interface_descriptor = config_descriptor + .interfaces() + .flat_map(|i| i.descriptors()) + .find(|x| x.class_code() == LIBUSB_CLASS_IMAGE) + .ok_or(rusb::Error::NotFound)?; + + let interface = interface_descriptor.interface_number(); + debug!("Found interface {interface}"); + + let handle = device.open()?; + handle.claim_interface(interface)?; + + let bulk_in = find_endpoint( + &interface_descriptor, + rusb::Direction::In, + rusb::TransferType::Bulk, + )?; + let bulk_out = find_endpoint( + &interface_descriptor, + rusb::Direction::Out, + rusb::TransferType::Bulk, + )?; + + let transaction_id = 0; + + let chunk_size = r#impl.chunk_size(); + + let mut ptp = Ptp { + bus, + address, + interface, + bulk_in, + bulk_out, + handle, + transaction_id, + chunk_size, + }; + + debug!("Opening session"); + let () = r#impl.open_session(&mut ptp, SESSION)?; + debug!("Session opened"); + + return Ok(Self { r#impl, ptp }); + } + + bail!("Device not supported"); + } +} + pub trait CameraImpl { fn supported_camera(&self) -> &'static SupportedCamera

; diff --git a/src/camera/ptp/hex.rs b/src/camera/ptp/hex.rs index c5982bb..f4bec34 100644 --- a/src/camera/ptp/hex.rs +++ b/src/camera/ptp/hex.rs @@ -279,7 +279,8 @@ impl DerefMut for FujiCustomSettingName { impl TryFrom for FujiCustomSettingName { type Error = anyhow::Error; - fn try_from(value: String) -> Result { + + fn try_from(value: String) -> anyhow::Result { if value.len() > Self::MAX_LEN { bail!("Value '{}' exceeds max length of {}", value, Self::MAX_LEN); } @@ -289,7 +290,8 @@ impl TryFrom for FujiCustomSettingName { impl FromStr for FujiCustomSettingName { type Err = anyhow::Error; - fn from_str(s: &str) -> Result { + + fn from_str(s: &str) -> anyhow::Result { if s.len() > Self::MAX_LEN { bail!("Value '{}' exceeds max length of {}", s, Self::MAX_LEN); } diff --git a/src/cli/common/file.rs b/src/cli/common/file.rs index b502aec..8e96d6c 100644 --- a/src/cli/common/file.rs +++ b/src/cli/common/file.rs @@ -8,6 +8,7 @@ pub enum Input { impl FromStr for Input { type Err = anyhow::Error; + fn from_str(s: &str) -> Result { if s == "-" { Ok(Self::Stdin) @@ -34,6 +35,7 @@ pub enum Output { impl FromStr for Output { type Err = anyhow::Error; + fn from_str(s: &str) -> Result { if s == "-" { Ok(Self::Stdout) diff --git a/src/cli/simulation/mod.rs b/src/cli/simulation/mod.rs index 92fa5dd..089ad80 100644 --- a/src/cli/simulation/mod.rs +++ b/src/cli/simulation/mod.rs @@ -97,7 +97,6 @@ fn handle_list(json: bool, device_id: Option<&str>) -> anyhow::Result<()> { #[derive(Serialize)] #[serde(rename_all = "camelCase")] pub struct FilmSimulationRepr { - pub slot: FujiCustomSetting, pub name: FujiCustomSettingName, pub simulation: FujiFilmSimulation, pub size: FujiImageSize, @@ -121,7 +120,6 @@ pub struct FilmSimulationRepr { impl fmt::Display for FilmSimulationRepr { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - writeln!(f, "Slot: {}", self.slot)?; writeln!(f, "Name: {}", self.name)?; writeln!(f, "Simulation: {}", self.simulation)?; writeln!(f, "Size: {}", self.size)?; @@ -156,7 +154,6 @@ fn handle_get(json: bool, device_id: Option<&str>, slot: FujiCustomSetting) -> a camera.set_active_custom_setting(slot)?; let repr = FilmSimulationRepr { - slot, name: camera.get_custom_setting_name()?, simulation: camera.get_film_simulation()?, size: camera.get_image_size()?, @@ -242,15 +239,17 @@ fn handle_set( } // White Balance - let white_balance = match &options.white_balance { - Some(white_balance) => { - camera.set_white_balance(*white_balance)?; - white_balance - } - None => &camera.get_white_balance()?, - }; + if let Some(white_balance) = &options.white_balance { + camera.set_white_balance(*white_balance)?; + } if let Some(temperature) = &options.white_balance_temperature { + let white_balance = if let Some(white_balance) = &options.white_balance { + white_balance + } else { + &camera.get_white_balance()? + }; + if *white_balance != FujiWhiteBalance::Temperature { warn!("White Balance mode is not set to 'Temperature', refusing to set temperature") } else { @@ -267,35 +266,40 @@ fn handle_set( } // Exposure - let dynamic_range_priority = match &options.dynamic_range_priority { - Some(dynamic_range_priority) => { - camera.set_dynamic_range_priority(*dynamic_range_priority)?; - dynamic_range_priority - } - None => &camera.get_dynamic_range_priority()?, - }; - - if let Some(dynamic_range) = &options.dynamic_range { - if *dynamic_range_priority != FujiDynamicRangePriority::Off { - warn!("Dynamic Range Priority is enabled, refusing to set dynamic range") - } else { - camera.set_dynamic_range(*dynamic_range)?; - } + if let Some(dynamic_range_priority) = &options.dynamic_range_priority { + camera.set_dynamic_range_priority(*dynamic_range_priority)?; } - if let Some(highlights) = &options.highlight { - if *dynamic_range_priority != FujiDynamicRangePriority::Off { - warn!("Dynamic Range Priority is enabled, refusing to set highlight tone") - } else { - camera.set_highlight_tone(*highlights)?; - } - } + if options.dynamic_range.is_some() || options.highlight.is_some() || options.shadow.is_some() { + let dynamic_range_priority = + if let Some(dynamic_range_priority) = &options.dynamic_range_priority { + dynamic_range_priority + } else { + &camera.get_dynamic_range_priority()? + }; - if let Some(shadows) = &options.shadow { - if *dynamic_range_priority != FujiDynamicRangePriority::Off { - warn!("Dynamic Range Priority is enabled, refusing to set shadow tone") - } else { - camera.set_shadow_tone(*shadows)?; + if let Some(dynamic_range) = &options.dynamic_range { + if *dynamic_range_priority != FujiDynamicRangePriority::Off { + warn!("Dynamic Range Priority is enabled, refusing to set dynamic range") + } else { + camera.set_dynamic_range(*dynamic_range)?; + } + } + + if let Some(highlights) = &options.highlight { + if *dynamic_range_priority != FujiDynamicRangePriority::Off { + warn!("Dynamic Range Priority is enabled, refusing to set highlight tone") + } else { + camera.set_highlight_tone(*highlights)?; + } + } + + if let Some(shadows) = &options.shadow { + if *dynamic_range_priority != FujiDynamicRangePriority::Off { + warn!("Dynamic Range Priority is enabled, refusing to set shadow tone") + } else { + camera.set_shadow_tone(*shadows)?; + } } } diff --git a/src/usb/mod.rs b/src/usb/mod.rs index d6dd30b..54451c0 100644 --- a/src/usb/mod.rs +++ b/src/usb/mod.rs @@ -2,11 +2,23 @@ use anyhow::{anyhow, bail}; use crate::camera::Camera; +pub fn find_endpoint( + interface_descriptor: &rusb::InterfaceDescriptor<'_>, + direction: rusb::Direction, + transfer_type: rusb::TransferType, +) -> Result { + interface_descriptor + .endpoint_descriptors() + .find(|ep| ep.direction() == direction && ep.transfer_type() == transfer_type) + .map(|x| x.address()) + .ok_or(rusb::Error::NotFound) +} + pub fn get_connected_cameras() -> anyhow::Result> { let mut connected_cameras = Vec::new(); for device in rusb::devices()?.iter() { - if let Ok(camera) = Camera::from_device(&device) { + if let Ok(camera) = Camera::try_from(&device) { connected_cameras.push(camera); } } @@ -25,7 +37,7 @@ pub fn get_connected_camera_by_id(id: &str) -> anyhow::Result { for device in rusb::devices()?.iter() { if device.bus_number() == bus && device.address() == address { - return Camera::from_device(&device); + return Camera::try_from(&device); } }