chore: refactor for device support
Signed-off-by: Nikolaos Karaolidis <nick@karaolidis.com>
This commit is contained in:
18
Cargo.lock
generated
18
Cargo.lock
generated
@@ -210,6 +210,17 @@ version = "1.0.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "erased-serde"
|
||||||
|
version = "0.4.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "259d404d09818dec19332e31d94558aeb442fea04c817006456c24b5460bbd4b"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
"serde_core",
|
||||||
|
"typeid",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "errno"
|
name = "errno"
|
||||||
version = "0.3.14"
|
version = "0.3.14"
|
||||||
@@ -239,6 +250,7 @@ dependencies = [
|
|||||||
"anyhow",
|
"anyhow",
|
||||||
"byteorder",
|
"byteorder",
|
||||||
"clap",
|
"clap",
|
||||||
|
"erased-serde",
|
||||||
"log",
|
"log",
|
||||||
"log4rs",
|
"log4rs",
|
||||||
"num_enum",
|
"num_enum",
|
||||||
@@ -840,6 +852,12 @@ dependencies = [
|
|||||||
"winnow",
|
"winnow",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "typeid"
|
||||||
|
version = "1.0.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "typemap-ors"
|
name = "typemap-ors"
|
||||||
version = "1.0.0"
|
version = "1.0.0"
|
||||||
|
|||||||
@@ -29,3 +29,4 @@ ptp_cursor = { path = "crates/ptp/cursor" }
|
|||||||
strum = { version = "0.27.2", features = ["strum_macros"] }
|
strum = { version = "0.27.2", features = ["strum_macros"] }
|
||||||
strum_macros = "0.27.2"
|
strum_macros = "0.27.2"
|
||||||
paste = "1.0.15"
|
paste = "1.0.15"
|
||||||
|
erased-serde = "0.4.8"
|
||||||
|
|||||||
@@ -1,42 +1,225 @@
|
|||||||
use rusb::GlobalContext;
|
pub mod x_trans_v;
|
||||||
|
|
||||||
use super::CameraImpl;
|
use std::{
|
||||||
|
fmt,
|
||||||
|
io::{self, Write},
|
||||||
|
};
|
||||||
|
|
||||||
type ImplFactory<P> = fn() -> Box<dyn CameraImpl<P>>;
|
use anyhow::anyhow;
|
||||||
|
use log::debug;
|
||||||
|
use ptp_cursor::{PtpDeserialize, PtpSerialize};
|
||||||
|
use serde::Serialize;
|
||||||
|
use strum::IntoEnumIterator;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
use crate::{
|
||||||
pub struct SupportedCamera<P: rusb::UsbContext> {
|
camera::ptp::hex::CommandCode,
|
||||||
pub name: &'static str,
|
cli::{common::film::FilmSimulationOptions, simulation::SetFilmSimulationOptions},
|
||||||
pub vendor: u16,
|
};
|
||||||
pub product: u16,
|
|
||||||
pub impl_factory: ImplFactory<P>,
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! default_camera_impl {
|
use super::{
|
||||||
(
|
CameraResult, SupportedCamera,
|
||||||
$const_name:ident,
|
ptp::{
|
||||||
$struct_name:ident,
|
Ptp,
|
||||||
$vendor:expr,
|
hex::{DevicePropCode, FujiCustomSetting, ObjectFormat, UsbMode},
|
||||||
$product:expr,
|
structs::ObjectInfo,
|
||||||
$display_name:expr
|
},
|
||||||
) => {
|
};
|
||||||
pub const $const_name: SupportedCamera<GlobalContext> = SupportedCamera {
|
|
||||||
name: $display_name,
|
pub trait DeviceImpl<P: rusb::UsbContext> {
|
||||||
vendor: $vendor,
|
fn camera_definition(&self) -> &'static SupportedCamera<P>;
|
||||||
product: $product,
|
|
||||||
impl_factory: || Box::new($struct_name {}),
|
fn chunk_size(&self) -> usize {
|
||||||
|
// Default conservative estimate.
|
||||||
|
1024 * 1024
|
||||||
|
}
|
||||||
|
|
||||||
|
fn custom_settings_slots(&self) -> Vec<FujiCustomSetting> {
|
||||||
|
FujiCustomSetting::iter().collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn info_get(&self, ptp: &mut Ptp) -> anyhow::Result<Box<dyn CameraResult>> {
|
||||||
|
let info = ptp.get_info()?;
|
||||||
|
|
||||||
|
let bytes = ptp.get_prop_value(DevicePropCode::FujiUsbMode)?;
|
||||||
|
let mode = UsbMode::try_from_ptp(&bytes)?;
|
||||||
|
|
||||||
|
let bytes = ptp.get_prop_value(DevicePropCode::FujiBatteryInfo2)?;
|
||||||
|
debug!("Raw battery data: {bytes:?}");
|
||||||
|
|
||||||
|
let battery_string = String::try_from_ptp(&bytes)?;
|
||||||
|
debug!("Decoded raw string: {battery_string}");
|
||||||
|
|
||||||
|
let battery: u32 = battery_string
|
||||||
|
.split(',')
|
||||||
|
.next()
|
||||||
|
.ok_or_else(|| anyhow!("Failed to parse battery percentage"))?
|
||||||
|
.parse()?;
|
||||||
|
|
||||||
|
let repr = CameraInfo {
|
||||||
|
manufacturer: info.manufacturer.clone(),
|
||||||
|
model: info.model.clone(),
|
||||||
|
device_version: info.device_version.clone(),
|
||||||
|
serial_number: info.serial_number,
|
||||||
|
mode,
|
||||||
|
battery,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct $struct_name {}
|
let repr = Box::new(repr);
|
||||||
|
|
||||||
impl crate::camera::CameraImpl<GlobalContext> for $struct_name {
|
Ok(repr)
|
||||||
fn supported_camera(&self) -> &'static SupportedCamera<GlobalContext> {
|
}
|
||||||
&$const_name
|
|
||||||
}
|
fn backup_export(&self, ptp: &mut Ptp) -> anyhow::Result<Vec<u8>> {
|
||||||
|
const HANDLE: u32 = 0x0;
|
||||||
|
|
||||||
|
debug!("Sending GetObjectInfo command for backup");
|
||||||
|
let response = ptp.send(CommandCode::GetObjectInfo, &[HANDLE], None)?;
|
||||||
|
debug!("Received response with {} bytes", response.len());
|
||||||
|
|
||||||
|
debug!("Sending GetObject command for backup");
|
||||||
|
let response = ptp.send(CommandCode::GetObject, &[HANDLE], None)?;
|
||||||
|
debug!("Received response with {} bytes", response.len());
|
||||||
|
|
||||||
|
Ok(response)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn backup_import(&self, ptp: &mut Ptp, buffer: &[u8]) -> anyhow::Result<()> {
|
||||||
|
debug!("Sending SendObjectInfo command for backup");
|
||||||
|
let object_info = FujiBackupObjectInfo::new(buffer.len())?;
|
||||||
|
let response = ptp.send(
|
||||||
|
CommandCode::SendObjectInfo,
|
||||||
|
&[0x0, 0x0],
|
||||||
|
Some(&object_info.try_into_ptp()?),
|
||||||
|
)?;
|
||||||
|
debug!("Received response with {} bytes", response.len());
|
||||||
|
|
||||||
|
debug!("Sending SendObject command for backup");
|
||||||
|
let response = ptp.send(CommandCode::SendObject, &[0x0], Some(buffer))?;
|
||||||
|
debug!("Received response with {} bytes", response.len());
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Naively assuming that all cameras support getting basic info.
|
||||||
|
#[derive(Serialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct CameraInfo {
|
||||||
|
pub manufacturer: String,
|
||||||
|
pub model: String,
|
||||||
|
pub device_version: String,
|
||||||
|
pub serial_number: String,
|
||||||
|
pub mode: UsbMode,
|
||||||
|
pub battery: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for CameraInfo {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
writeln!(f, "Manufacturer: {}", self.manufacturer)?;
|
||||||
|
writeln!(f, "Model: {}", self.model)?;
|
||||||
|
writeln!(f, "Version: {}", self.device_version)?;
|
||||||
|
writeln!(f, "Serial Number: {}", self.serial_number)?;
|
||||||
|
writeln!(f, "Mode: {}", self.mode)?;
|
||||||
|
write!(f, "Battery: {}%", self.battery)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Naively assuming that all cameras support backup/restore
|
||||||
|
// using the same structs.
|
||||||
|
pub struct FujiBackupObjectInfo {
|
||||||
|
compressed_size: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FujiBackupObjectInfo {
|
||||||
|
pub fn new(buffer_len: usize) -> anyhow::Result<Self> {
|
||||||
|
Ok(Self {
|
||||||
|
compressed_size: u32::try_from(buffer_len)?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PtpSerialize for FujiBackupObjectInfo {
|
||||||
|
fn try_into_ptp(&self) -> io::Result<Vec<u8>> {
|
||||||
|
let mut buf = Vec::new();
|
||||||
|
self.try_write_ptp(&mut buf)?;
|
||||||
|
Ok(buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn try_write_ptp(&self, buf: &mut Vec<u8>) -> io::Result<()> {
|
||||||
|
let object_info = ObjectInfo {
|
||||||
|
object_format: ObjectFormat::FujiBackup,
|
||||||
|
compressed_size: self.compressed_size,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
object_info.try_write_ptp(buf)?;
|
||||||
|
|
||||||
|
// TODO: What is this?
|
||||||
|
buf.write_all(&[0x0u8; 1020])?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait SensorImpl<P: rusb::UsbContext> {
|
||||||
|
fn simulation_list(
|
||||||
|
&self,
|
||||||
|
ptp: &mut Ptp,
|
||||||
|
device: &dyn DeviceImpl<P>,
|
||||||
|
) -> anyhow::Result<Vec<Box<dyn CameraResult>>>;
|
||||||
|
|
||||||
|
fn simulation_get(
|
||||||
|
&self,
|
||||||
|
ptp: &mut Ptp,
|
||||||
|
device: &dyn DeviceImpl<P>,
|
||||||
|
slot: FujiCustomSetting,
|
||||||
|
) -> anyhow::Result<Box<dyn CameraResult>>;
|
||||||
|
|
||||||
|
fn simulation_set(
|
||||||
|
&self,
|
||||||
|
ptp: &mut Ptp,
|
||||||
|
device: &dyn DeviceImpl<P>,
|
||||||
|
slot: FujiCustomSetting,
|
||||||
|
set_options: &SetFilmSimulationOptions,
|
||||||
|
options: &FilmSimulationOptions,
|
||||||
|
) -> anyhow::Result<()>;
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! prop_getter {
|
||||||
|
($name:ident: $type:ty => $code:expr) => {
|
||||||
|
fn $name(&self, ptp: &mut crate::camera::ptp::Ptp) -> anyhow::Result<$type> {
|
||||||
|
use ptp_cursor::PtpDeserialize;
|
||||||
|
|
||||||
|
let bytes = ptp.get_prop_value($code)?;
|
||||||
|
let result = <$type>::try_from_ptp(&bytes)?;
|
||||||
|
Ok(result)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
default_camera_impl!(FUJIFILM_XT5, FujifilmXT5, 0x04cb, 0x02fc, "FUJIFILM XT-5");
|
macro_rules! prop_setter {
|
||||||
|
($name:ident: $type:ty => $code:expr) => {
|
||||||
|
fn $name(&self, ptp: &mut crate::camera::ptp::Ptp, value: &$type) -> anyhow::Result<()> {
|
||||||
|
use ptp_cursor::PtpSerialize;
|
||||||
|
|
||||||
pub const SUPPORTED: &[SupportedCamera<GlobalContext>] = &[FUJIFILM_XT5];
|
let bytes = value.try_into_ptp()?;
|
||||||
|
ptp.set_prop_value($code, &bytes)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! set_prop_if_some {
|
||||||
|
($self:ident, $ptp:ident, $options:ident,
|
||||||
|
$( $field:ident => $setter:ident ),* $(,)? ) => {
|
||||||
|
$(
|
||||||
|
if let Some(val) = &$options.$field {
|
||||||
|
$self.$setter($ptp, val)?;
|
||||||
|
}
|
||||||
|
)*
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) use prop_getter;
|
||||||
|
pub(crate) use prop_setter;
|
||||||
|
pub(crate) use set_prop_if_some;
|
||||||
|
|||||||
535
src/camera/devices/x_trans_v/mod.rs
Normal file
535
src/camera/devices/x_trans_v/mod.rs
Normal file
@@ -0,0 +1,535 @@
|
|||||||
|
pub mod x_t5;
|
||||||
|
|
||||||
|
use std::{
|
||||||
|
fmt,
|
||||||
|
io::{self, Cursor, Write},
|
||||||
|
};
|
||||||
|
|
||||||
|
use anyhow::bail;
|
||||||
|
use log::error;
|
||||||
|
use ptp_cursor::{PtpDeserialize, PtpSerialize};
|
||||||
|
use ptp_macro::{PtpDeserialize, PtpSerialize};
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
camera::{
|
||||||
|
CameraResult,
|
||||||
|
devices::set_prop_if_some,
|
||||||
|
ptp::{
|
||||||
|
Ptp,
|
||||||
|
hex::{
|
||||||
|
DevicePropCode, FujiClarity, FujiColor, FujiColorChromeEffect,
|
||||||
|
FujiColorChromeFXBlue, FujiColorSpace, FujiCustomSetting, FujiCustomSettingName,
|
||||||
|
FujiDynamicRange, FujiDynamicRangePriority, FujiFilmSimulation, FujiGrainEffect,
|
||||||
|
FujiHighISONR, FujiHighlightTone, FujiImageQuality, FujiImageSize,
|
||||||
|
FujiLensModulationOptimizer, FujiMonochromaticColorTemperature,
|
||||||
|
FujiMonochromaticColorTint, FujiShadowTone, FujiSharpness, FujiSmoothSkinEffect,
|
||||||
|
FujiWhiteBalance, FujiWhiteBalanceShift, FujiWhiteBalanceTemperature,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
cli::{common::film::FilmSimulationOptions, simulation::SetFilmSimulationOptions},
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::{DeviceImpl, SensorImpl, prop_getter, prop_setter};
|
||||||
|
|
||||||
|
pub struct XTransV;
|
||||||
|
|
||||||
|
impl XTransV {
|
||||||
|
prop_getter!(get_custom_setting_name: FujiCustomSettingName => DevicePropCode::FujiCustomSettingName);
|
||||||
|
prop_getter!(get_image_size: FujiImageSize => DevicePropCode::FujiCustomSettingImageSize);
|
||||||
|
prop_getter!(get_image_quality: FujiImageQuality => DevicePropCode::FujiCustomSettingImageQuality);
|
||||||
|
prop_getter!(get_dynamic_range: FujiDynamicRange => DevicePropCode::FujiCustomSettingDynamicRange);
|
||||||
|
prop_getter!(get_dynamic_range_priority: FujiDynamicRangePriority => DevicePropCode::FujiCustomSettingDynamicRangePriority);
|
||||||
|
prop_getter!(get_film_simulation: FujiFilmSimulation => DevicePropCode::FujiCustomSettingFilmSimulation);
|
||||||
|
prop_getter!(get_monochromatic_color_temperature: FujiMonochromaticColorTemperature => DevicePropCode::FujiCustomSettingMonochromaticColorTemperature);
|
||||||
|
prop_getter!(get_monochromatic_color_tint: FujiMonochromaticColorTint => DevicePropCode::FujiCustomSettingMonochromaticColorTint);
|
||||||
|
prop_getter!(get_grain_effect: FujiGrainEffect => DevicePropCode::FujiCustomSettingGrainEffect);
|
||||||
|
prop_getter!(get_white_balance: FujiWhiteBalance => DevicePropCode::FujiCustomSettingWhiteBalance);
|
||||||
|
prop_getter!(get_high_iso_nr: FujiHighISONR => DevicePropCode::FujiCustomSettingHighISONR);
|
||||||
|
prop_getter!(get_highlight_tone: FujiHighlightTone => DevicePropCode::FujiCustomSettingHighlightTone);
|
||||||
|
prop_getter!(get_shadow_tone: FujiShadowTone => DevicePropCode::FujiCustomSettingShadowTone);
|
||||||
|
prop_getter!(get_color: FujiColor => DevicePropCode::FujiCustomSettingColor);
|
||||||
|
prop_getter!(get_sharpness: FujiSharpness => DevicePropCode::FujiCustomSettingSharpness);
|
||||||
|
prop_getter!(get_clarity: FujiClarity => DevicePropCode::FujiCustomSettingClarity);
|
||||||
|
prop_getter!(get_white_balance_shift_red: FujiWhiteBalanceShift => DevicePropCode::FujiCustomSettingWhiteBalanceShiftRed);
|
||||||
|
prop_getter!(get_white_balance_shift_blue: FujiWhiteBalanceShift => DevicePropCode::FujiCustomSettingWhiteBalanceShiftBlue);
|
||||||
|
prop_getter!(get_white_balance_temperature: FujiWhiteBalanceTemperature => DevicePropCode::FujiCustomSettingWhiteBalanceTemperature);
|
||||||
|
prop_getter!(get_color_chrome_effect: FujiColorChromeEffect => DevicePropCode::FujiCustomSettingColorChromeEffect);
|
||||||
|
prop_getter!(get_color_chrome_fx_blue: FujiColorChromeFXBlue => DevicePropCode::FujiCustomSettingColorChromeFXBlue);
|
||||||
|
prop_getter!(get_smooth_skin_effect: FujiSmoothSkinEffect => DevicePropCode::FujiCustomSettingSmoothSkinEffect);
|
||||||
|
prop_getter!(get_lens_modulation_optimizer: FujiLensModulationOptimizer => DevicePropCode::FujiCustomSettingLensModulationOptimizer);
|
||||||
|
prop_getter!(get_color_space: FujiColorSpace => DevicePropCode::FujiCustomSettingColorSpace);
|
||||||
|
|
||||||
|
prop_setter!(set_active_custom_setting: FujiCustomSetting => DevicePropCode::FujiCustomSetting);
|
||||||
|
prop_setter!(set_custom_setting_name: FujiCustomSettingName => DevicePropCode::FujiCustomSettingName);
|
||||||
|
prop_setter!(set_image_size: FujiImageSize => DevicePropCode::FujiCustomSettingImageSize);
|
||||||
|
prop_setter!(set_image_quality: FujiImageQuality => DevicePropCode::FujiCustomSettingImageQuality);
|
||||||
|
prop_setter!(set_dynamic_range: FujiDynamicRange => DevicePropCode::FujiCustomSettingDynamicRange);
|
||||||
|
prop_setter!(set_dynamic_range_priority: FujiDynamicRangePriority => DevicePropCode::FujiCustomSettingDynamicRangePriority);
|
||||||
|
prop_setter!(set_film_simulation: FujiFilmSimulation => DevicePropCode::FujiCustomSettingFilmSimulation);
|
||||||
|
prop_setter!(set_monochromatic_color_temperature: FujiMonochromaticColorTemperature => DevicePropCode::FujiCustomSettingMonochromaticColorTemperature);
|
||||||
|
prop_setter!(set_monochromatic_color_tint: FujiMonochromaticColorTint => DevicePropCode::FujiCustomSettingMonochromaticColorTint);
|
||||||
|
prop_setter!(set_grain_effect: FujiGrainEffect => DevicePropCode::FujiCustomSettingGrainEffect);
|
||||||
|
prop_setter!(set_white_balance: FujiWhiteBalance => DevicePropCode::FujiCustomSettingWhiteBalance);
|
||||||
|
prop_setter!(set_high_iso_nr: FujiHighISONR => DevicePropCode::FujiCustomSettingHighISONR);
|
||||||
|
prop_setter!(set_highlight_tone: FujiHighlightTone => DevicePropCode::FujiCustomSettingHighlightTone);
|
||||||
|
prop_setter!(set_shadow_tone: FujiShadowTone => DevicePropCode::FujiCustomSettingShadowTone);
|
||||||
|
prop_setter!(set_color: FujiColor => DevicePropCode::FujiCustomSettingColor);
|
||||||
|
prop_setter!(set_sharpness: FujiSharpness => DevicePropCode::FujiCustomSettingSharpness);
|
||||||
|
prop_setter!(set_clarity: FujiClarity => DevicePropCode::FujiCustomSettingClarity);
|
||||||
|
prop_setter!(set_white_balance_shift_red: FujiWhiteBalanceShift => DevicePropCode::FujiCustomSettingWhiteBalanceShiftRed);
|
||||||
|
prop_setter!(set_white_balance_shift_blue: FujiWhiteBalanceShift => DevicePropCode::FujiCustomSettingWhiteBalanceShiftBlue);
|
||||||
|
prop_setter!(set_white_balance_temperature: FujiWhiteBalanceTemperature => DevicePropCode::FujiCustomSettingWhiteBalanceTemperature);
|
||||||
|
prop_setter!(set_color_chrome_effect: FujiColorChromeEffect => DevicePropCode::FujiCustomSettingColorChromeEffect);
|
||||||
|
prop_setter!(set_color_chrome_fx_blue: FujiColorChromeFXBlue => DevicePropCode::FujiCustomSettingColorChromeFXBlue);
|
||||||
|
prop_setter!(set_smooth_skin_effect: FujiSmoothSkinEffect => DevicePropCode::FujiCustomSettingSmoothSkinEffect);
|
||||||
|
prop_setter!(set_lens_modulation_optimizer: FujiLensModulationOptimizer => DevicePropCode::FujiCustomSettingLensModulationOptimizer);
|
||||||
|
prop_setter!(set_color_space: FujiColorSpace => DevicePropCode::FujiCustomSettingColorSpace);
|
||||||
|
|
||||||
|
fn validate_monochromatic(
|
||||||
|
final_options: &FilmSimulationOptions,
|
||||||
|
prev_simulation: FujiFilmSimulation,
|
||||||
|
) -> bool {
|
||||||
|
let mut fail = true;
|
||||||
|
|
||||||
|
if !matches!(
|
||||||
|
prev_simulation,
|
||||||
|
FujiFilmSimulation::Monochrome
|
||||||
|
| FujiFilmSimulation::MonochromeYe
|
||||||
|
| FujiFilmSimulation::MonochromeR
|
||||||
|
| FujiFilmSimulation::MonochromeG
|
||||||
|
| FujiFilmSimulation::AcrosSTD
|
||||||
|
| FujiFilmSimulation::AcrosYe
|
||||||
|
| FujiFilmSimulation::AcrosR
|
||||||
|
| FujiFilmSimulation::AcrosG
|
||||||
|
) && (final_options.monochromatic_color_temperature.is_some()
|
||||||
|
|| final_options.monochromatic_color_tint.is_some())
|
||||||
|
{
|
||||||
|
if final_options.monochromatic_color_temperature.is_some() {
|
||||||
|
error!(
|
||||||
|
"A B&W film simulation is not selected, refusing to set monochromatic color temperature"
|
||||||
|
);
|
||||||
|
fail = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if final_options.monochromatic_color_tint.is_some() {
|
||||||
|
error!(
|
||||||
|
"A B&W film simulation is not selected, refusing to set monochromatic color tint"
|
||||||
|
);
|
||||||
|
fail = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fail
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_white_balance_temperature(
|
||||||
|
final_options: &FilmSimulationOptions,
|
||||||
|
prev_white_balance: FujiWhiteBalance,
|
||||||
|
) -> bool {
|
||||||
|
if prev_white_balance != FujiWhiteBalance::Temperature
|
||||||
|
&& final_options.white_balance_temperature.is_some()
|
||||||
|
{
|
||||||
|
error!("White Balance mode is not set to 'Temperature', refusing to set temperature");
|
||||||
|
false
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_exposure(
|
||||||
|
final_options: &FilmSimulationOptions,
|
||||||
|
previous_dynamic_range_priority: FujiDynamicRangePriority,
|
||||||
|
) -> bool {
|
||||||
|
let mut fail = true;
|
||||||
|
|
||||||
|
if previous_dynamic_range_priority != FujiDynamicRangePriority::Off
|
||||||
|
&& (final_options.dynamic_range.is_some()
|
||||||
|
|| final_options.highlight.is_some()
|
||||||
|
|| final_options.shadow.is_some())
|
||||||
|
{
|
||||||
|
if final_options.dynamic_range.is_some() {
|
||||||
|
error!("Dynamic Range Priority is enabled, refusing to set dynamic range");
|
||||||
|
fail = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if final_options.highlight.is_some() {
|
||||||
|
error!("Dynamic Range Priority is enabled, refusing to set highlight tone");
|
||||||
|
fail = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if final_options.shadow.is_some() {
|
||||||
|
error!("Dynamic Range Priority is enabled, refusing to set shadow tone");
|
||||||
|
fail = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fail
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_simulation_set(
|
||||||
|
&self,
|
||||||
|
ptp: &mut Ptp,
|
||||||
|
options: &FilmSimulationOptions,
|
||||||
|
) -> Result<(), anyhow::Error> {
|
||||||
|
let prev_simulation = if let Some(simulation) = options.simulation {
|
||||||
|
simulation
|
||||||
|
} else {
|
||||||
|
self.get_film_simulation(ptp)?
|
||||||
|
};
|
||||||
|
|
||||||
|
let prev_white_balance = if let Some(white_balance) = options.white_balance {
|
||||||
|
white_balance
|
||||||
|
} else {
|
||||||
|
self.get_white_balance(ptp)?
|
||||||
|
};
|
||||||
|
|
||||||
|
let prev_dynamic_range_priority =
|
||||||
|
if let Some(dynamic_range_priority) = options.dynamic_range_priority {
|
||||||
|
dynamic_range_priority
|
||||||
|
} else {
|
||||||
|
self.get_dynamic_range_priority(ptp)?
|
||||||
|
};
|
||||||
|
|
||||||
|
if !Self::validate_monochromatic(options, prev_simulation)
|
||||||
|
|| !Self::validate_white_balance_temperature(options, prev_white_balance)
|
||||||
|
|| !Self::validate_exposure(options, prev_dynamic_range_priority)
|
||||||
|
{
|
||||||
|
bail!("Incompatible options detected")
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<P: rusb::UsbContext> SensorImpl<P> for XTransV {
|
||||||
|
fn simulation_list(
|
||||||
|
&self,
|
||||||
|
ptp: &mut Ptp,
|
||||||
|
device: &dyn DeviceImpl<P>,
|
||||||
|
) -> anyhow::Result<Vec<Box<dyn CameraResult>>> {
|
||||||
|
let mut slots = Vec::new();
|
||||||
|
|
||||||
|
for slot in device.custom_settings_slots() {
|
||||||
|
self.set_active_custom_setting(ptp, &slot)?;
|
||||||
|
let name = self.get_custom_setting_name(ptp)?;
|
||||||
|
let repr = SimulationListItem { slot, name };
|
||||||
|
let repr: Box<dyn CameraResult> = Box::new(repr);
|
||||||
|
slots.push(repr);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(slots)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn simulation_get(
|
||||||
|
&self,
|
||||||
|
ptp: &mut Ptp,
|
||||||
|
device: &dyn DeviceImpl<P>,
|
||||||
|
slot: FujiCustomSetting,
|
||||||
|
) -> anyhow::Result<Box<dyn CameraResult>> {
|
||||||
|
if !device.custom_settings_slots().contains(&slot) {
|
||||||
|
bail!("Unsupported custom setting slot '{slot}'")
|
||||||
|
}
|
||||||
|
|
||||||
|
self.set_active_custom_setting(ptp, &slot)?;
|
||||||
|
|
||||||
|
let repr = Simulation {
|
||||||
|
name: self.get_custom_setting_name(ptp)?,
|
||||||
|
size: self.get_image_size(ptp)?,
|
||||||
|
quality: self.get_image_quality(ptp)?,
|
||||||
|
simulation: self.get_film_simulation(ptp)?,
|
||||||
|
monochromatic_color_temperature: self.get_monochromatic_color_temperature(ptp)?,
|
||||||
|
monochromatic_color_tint: self.get_monochromatic_color_tint(ptp)?,
|
||||||
|
highlight: self.get_highlight_tone(ptp)?,
|
||||||
|
shadow: self.get_shadow_tone(ptp)?,
|
||||||
|
color: self.get_color(ptp)?,
|
||||||
|
sharpness: self.get_sharpness(ptp)?,
|
||||||
|
clarity: self.get_clarity(ptp)?,
|
||||||
|
noise_reduction: self.get_high_iso_nr(ptp)?,
|
||||||
|
grain: self.get_grain_effect(ptp)?,
|
||||||
|
color_chrome_effect: self.get_color_chrome_effect(ptp)?,
|
||||||
|
color_chrome_fx_blue: self.get_color_chrome_fx_blue(ptp)?,
|
||||||
|
smooth_skin_effect: self.get_smooth_skin_effect(ptp)?,
|
||||||
|
white_balance: self.get_white_balance(ptp)?,
|
||||||
|
white_balance_shift_red: self.get_white_balance_shift_red(ptp)?,
|
||||||
|
white_balance_shift_blue: self.get_white_balance_shift_blue(ptp)?,
|
||||||
|
white_balance_temperature: self.get_white_balance_temperature(ptp)?,
|
||||||
|
dynamic_range: self.get_dynamic_range(ptp)?,
|
||||||
|
dynamic_range_priority: self.get_dynamic_range_priority(ptp)?,
|
||||||
|
lens_modulation_optimizer: self.get_lens_modulation_optimizer(ptp)?,
|
||||||
|
color_space: self.get_color_space(ptp)?,
|
||||||
|
};
|
||||||
|
|
||||||
|
let repr = Box::new(repr);
|
||||||
|
|
||||||
|
Ok(repr)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn simulation_set(
|
||||||
|
&self,
|
||||||
|
ptp: &mut Ptp,
|
||||||
|
device: &dyn DeviceImpl<P>,
|
||||||
|
slot: FujiCustomSetting,
|
||||||
|
set_options: &SetFilmSimulationOptions,
|
||||||
|
options: &FilmSimulationOptions,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
if !device.custom_settings_slots().contains(&slot) {
|
||||||
|
bail!("Unsupported custom setting slot '{slot}'")
|
||||||
|
}
|
||||||
|
|
||||||
|
self.set_active_custom_setting(ptp, &slot)?;
|
||||||
|
|
||||||
|
self.validate_simulation_set(ptp, options)?;
|
||||||
|
|
||||||
|
set_prop_if_some!(self, ptp, set_options,
|
||||||
|
name => set_custom_setting_name,
|
||||||
|
);
|
||||||
|
|
||||||
|
set_prop_if_some!(self, ptp, options,
|
||||||
|
size => set_image_size,
|
||||||
|
quality => set_image_quality,
|
||||||
|
simulation => set_film_simulation,
|
||||||
|
monochromatic_color_temperature => set_monochromatic_color_temperature,
|
||||||
|
monochromatic_color_tint => set_monochromatic_color_tint,
|
||||||
|
color => set_color,
|
||||||
|
sharpness => set_sharpness,
|
||||||
|
clarity => set_clarity,
|
||||||
|
noise_reduction => set_high_iso_nr,
|
||||||
|
grain => set_grain_effect,
|
||||||
|
color_chrome_effect => set_color_chrome_effect,
|
||||||
|
color_chrome_fx_blue => set_color_chrome_fx_blue,
|
||||||
|
smooth_skin_effect => set_smooth_skin_effect,
|
||||||
|
white_balance => set_white_balance,
|
||||||
|
white_balance_temperature => set_white_balance_temperature,
|
||||||
|
white_balance_shift_red => set_white_balance_shift_red,
|
||||||
|
white_balance_shift_blue => set_white_balance_shift_blue,
|
||||||
|
dynamic_range_priority => set_dynamic_range_priority,
|
||||||
|
dynamic_range => set_dynamic_range,
|
||||||
|
highlight => set_highlight_tone,
|
||||||
|
shadow => set_shadow_tone,
|
||||||
|
lens_modulation_optimizer => set_lens_modulation_optimizer,
|
||||||
|
color_space => set_color_space,
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Naively assuming that all cameras using the same sensor
|
||||||
|
// also have the same simulation feature set.
|
||||||
|
#[derive(Debug, Serialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct SimulationListItem {
|
||||||
|
pub slot: FujiCustomSetting,
|
||||||
|
pub name: FujiCustomSettingName,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for SimulationListItem {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "{}: {}", self.slot, self.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct Simulation {
|
||||||
|
pub name: FujiCustomSettingName,
|
||||||
|
pub size: FujiImageSize,
|
||||||
|
pub quality: FujiImageQuality,
|
||||||
|
#[allow(clippy::struct_field_names)]
|
||||||
|
pub simulation: FujiFilmSimulation,
|
||||||
|
pub monochromatic_color_temperature: FujiMonochromaticColorTemperature,
|
||||||
|
pub monochromatic_color_tint: FujiMonochromaticColorTint,
|
||||||
|
pub highlight: FujiHighlightTone,
|
||||||
|
pub shadow: FujiShadowTone,
|
||||||
|
pub color: FujiColor,
|
||||||
|
pub sharpness: FujiSharpness,
|
||||||
|
pub clarity: FujiClarity,
|
||||||
|
pub noise_reduction: FujiHighISONR,
|
||||||
|
pub grain: FujiGrainEffect,
|
||||||
|
pub color_chrome_effect: FujiColorChromeEffect,
|
||||||
|
pub color_chrome_fx_blue: FujiColorChromeFXBlue,
|
||||||
|
pub smooth_skin_effect: FujiSmoothSkinEffect,
|
||||||
|
pub white_balance: FujiWhiteBalance,
|
||||||
|
pub white_balance_shift_red: FujiWhiteBalanceShift,
|
||||||
|
pub white_balance_shift_blue: FujiWhiteBalanceShift,
|
||||||
|
pub white_balance_temperature: FujiWhiteBalanceTemperature,
|
||||||
|
pub dynamic_range: FujiDynamicRange,
|
||||||
|
pub dynamic_range_priority: FujiDynamicRangePriority,
|
||||||
|
pub lens_modulation_optimizer: FujiLensModulationOptimizer,
|
||||||
|
pub color_space: FujiColorSpace,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Simulation {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
writeln!(f, "Name: {}", self.name)?;
|
||||||
|
writeln!(f, "Size: {}", self.size)?;
|
||||||
|
writeln!(f, "Quality: {}", self.quality)?;
|
||||||
|
|
||||||
|
writeln!(f, "Simulation: {}", self.simulation)?;
|
||||||
|
|
||||||
|
match self.simulation {
|
||||||
|
FujiFilmSimulation::Monochrome
|
||||||
|
| FujiFilmSimulation::MonochromeYe
|
||||||
|
| FujiFilmSimulation::MonochromeR
|
||||||
|
| FujiFilmSimulation::MonochromeG
|
||||||
|
| FujiFilmSimulation::AcrosSTD
|
||||||
|
| FujiFilmSimulation::AcrosYe
|
||||||
|
| FujiFilmSimulation::AcrosR
|
||||||
|
| FujiFilmSimulation::AcrosG => {
|
||||||
|
writeln!(
|
||||||
|
f,
|
||||||
|
"Monochromatic Color Temperature: {}",
|
||||||
|
self.monochromatic_color_temperature
|
||||||
|
)?;
|
||||||
|
writeln!(
|
||||||
|
f,
|
||||||
|
"Monochromatic Color Tint: {}",
|
||||||
|
self.monochromatic_color_tint
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.dynamic_range_priority == FujiDynamicRangePriority::Off {
|
||||||
|
writeln!(f, "Highlights: {}", self.highlight)?;
|
||||||
|
writeln!(f, "Shadows: {}", self.shadow)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
writeln!(f, "Color: {}", self.color)?;
|
||||||
|
writeln!(f, "Sharpness: {}", self.sharpness)?;
|
||||||
|
writeln!(f, "Clarity: {}", self.clarity)?;
|
||||||
|
writeln!(f, "Noise Reduction: {}", self.noise_reduction)?;
|
||||||
|
writeln!(f, "Grain: {}", self.grain)?;
|
||||||
|
writeln!(f, "Color Chrome Effect: {}", self.color_chrome_effect)?;
|
||||||
|
writeln!(f, "Color Chrome FX Blue: {}", self.color_chrome_fx_blue)?;
|
||||||
|
writeln!(f, "Smooth Skin Effect: {}", self.smooth_skin_effect)?;
|
||||||
|
|
||||||
|
writeln!(f, "White Balance: {}", self.white_balance)?;
|
||||||
|
writeln!(
|
||||||
|
f,
|
||||||
|
"White Balance Shift (R/B): {} / {}",
|
||||||
|
self.white_balance_shift_red, self.white_balance_shift_blue
|
||||||
|
)?;
|
||||||
|
|
||||||
|
if self.white_balance == FujiWhiteBalance::Temperature {
|
||||||
|
writeln!(
|
||||||
|
f,
|
||||||
|
"White Balance Temperature: {}K",
|
||||||
|
self.white_balance_temperature
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.dynamic_range_priority == FujiDynamicRangePriority::Off {
|
||||||
|
writeln!(f, "Dynamic Range: {}", self.dynamic_range)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
writeln!(f, "Dynamic Range Priority: {}", self.dynamic_range_priority)?;
|
||||||
|
|
||||||
|
writeln!(
|
||||||
|
f,
|
||||||
|
"Lens Modulation Optimizer: {}",
|
||||||
|
self.lens_modulation_optimizer
|
||||||
|
)?;
|
||||||
|
writeln!(f, "Color Space: {}", self.color_space)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PtpSerialize, PtpDeserialize)]
|
||||||
|
pub struct FujiConversionProfileContents {
|
||||||
|
// TODO: What is this, and why is it always 0x2?
|
||||||
|
pub unknown_0: i32,
|
||||||
|
pub file_type: u32,
|
||||||
|
pub size: u32,
|
||||||
|
pub quality: u32,
|
||||||
|
pub exposure_offset: i32,
|
||||||
|
pub dynamic_range: u32,
|
||||||
|
pub dynamic_range_priority: u32,
|
||||||
|
pub simulation: u32,
|
||||||
|
pub grain: u32,
|
||||||
|
pub color_chrome_effect: u32,
|
||||||
|
pub white_balance_as_shot: u32,
|
||||||
|
pub white_balance: u32,
|
||||||
|
pub white_balance_shift_red: i32,
|
||||||
|
pub white_balance_shift_blue: i32,
|
||||||
|
pub white_balance_temperature: i32,
|
||||||
|
pub highlight: i32,
|
||||||
|
pub shadow: i32,
|
||||||
|
pub color: i32,
|
||||||
|
pub sharpness: i32,
|
||||||
|
pub noise_reduction: u32,
|
||||||
|
pub lens_modulation_optimizer: u32,
|
||||||
|
pub color_space: u32,
|
||||||
|
pub monochromatic_color_temperature: i32,
|
||||||
|
pub smooth_skin_effect: u32,
|
||||||
|
pub color_chrome_fx_blue: u32,
|
||||||
|
pub monochromatic_color_tint: i32,
|
||||||
|
pub clarity: i32,
|
||||||
|
pub teleconverter: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct FujiConversionProfile {
|
||||||
|
pub contents: FujiConversionProfileContents,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FujiConversionProfile {
|
||||||
|
const EXPECTED_N_PROPS: i16 = 29;
|
||||||
|
const EXPECTED_PROFILE_CODE: &str = "FF17950";
|
||||||
|
const PADDING: usize = 0x1EE;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PtpDeserialize for FujiConversionProfile {
|
||||||
|
fn try_from_ptp(buf: &[u8]) -> io::Result<Self> {
|
||||||
|
let mut cur = Cursor::new(buf);
|
||||||
|
let value = Self::try_read_ptp(&mut cur)?;
|
||||||
|
Ok(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn try_read_ptp<R: ptp_cursor::Read>(cur: &mut R) -> io::Result<Self> {
|
||||||
|
let n_props = <i16>::try_read_ptp(cur)?;
|
||||||
|
if n_props != Self::EXPECTED_N_PROPS {
|
||||||
|
return Err(io::Error::new(
|
||||||
|
io::ErrorKind::InvalidData,
|
||||||
|
format!("Expected {} props, got {n_props}", Self::EXPECTED_N_PROPS),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let profile_code = String::try_read_ptp(cur)?;
|
||||||
|
if profile_code != Self::EXPECTED_PROFILE_CODE {
|
||||||
|
return Err(io::Error::new(
|
||||||
|
io::ErrorKind::InvalidData,
|
||||||
|
format!(
|
||||||
|
"Expected profile code '{}', got '{profile_code}'",
|
||||||
|
Self::EXPECTED_PROFILE_CODE
|
||||||
|
),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut padding = [0u8; Self::PADDING];
|
||||||
|
cur.read_exact(&mut padding)?;
|
||||||
|
|
||||||
|
let contents = FujiConversionProfileContents::try_read_ptp(cur)?;
|
||||||
|
Ok(Self { contents })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PtpSerialize for FujiConversionProfile {
|
||||||
|
fn try_into_ptp(&self) -> io::Result<Vec<u8>> {
|
||||||
|
let mut buf = Vec::new();
|
||||||
|
self.try_write_ptp(&mut buf)?;
|
||||||
|
Ok(buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn try_write_ptp(&self, buf: &mut Vec<u8>) -> io::Result<()> {
|
||||||
|
Self::EXPECTED_N_PROPS.try_write_ptp(buf)?;
|
||||||
|
Self::EXPECTED_PROFILE_CODE.try_write_ptp(buf)?;
|
||||||
|
|
||||||
|
let padding = [0u8; Self::PADDING];
|
||||||
|
buf.write_all(&padding)?;
|
||||||
|
|
||||||
|
self.contents.try_write_ptp(buf)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
26
src/camera/devices/x_trans_v/x_t5/mod.rs
Normal file
26
src/camera/devices/x_trans_v/x_t5/mod.rs
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
use rusb::GlobalContext;
|
||||||
|
|
||||||
|
use crate::camera::{DeviceImpl, devices::SupportedCamera};
|
||||||
|
|
||||||
|
use super::XTransV;
|
||||||
|
|
||||||
|
pub const FUJIFILM_XT5: SupportedCamera<GlobalContext> = SupportedCamera {
|
||||||
|
name: "FUJIFILM XT-5",
|
||||||
|
vendor: 0x04cb,
|
||||||
|
product: 0x02fc,
|
||||||
|
device_factory: || Box::new(FujifilmXT5 {}),
|
||||||
|
sensor_factory: || Box::new(XTransV {}),
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct FujifilmXT5 {}
|
||||||
|
|
||||||
|
impl DeviceImpl<GlobalContext> for FujifilmXT5 {
|
||||||
|
fn camera_definition(&self) -> &'static SupportedCamera<GlobalContext> {
|
||||||
|
&FUJIFILM_XT5
|
||||||
|
}
|
||||||
|
|
||||||
|
fn chunk_size(&self) -> usize {
|
||||||
|
// 15.75 * 1024^2
|
||||||
|
16128 * 1024
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,141 +2,91 @@ pub mod devices;
|
|||||||
pub mod error;
|
pub mod error;
|
||||||
pub mod ptp;
|
pub mod ptp;
|
||||||
|
|
||||||
use std::time::Duration;
|
use std::fmt;
|
||||||
|
|
||||||
use anyhow::{anyhow, bail};
|
use crate::{
|
||||||
use devices::SupportedCamera;
|
cli::common::film::FilmSimulationOptions, cli::simulation::SetFilmSimulationOptions,
|
||||||
use log::{debug, error};
|
usb::find_endpoint,
|
||||||
use ptp::{
|
|
||||||
Ptp,
|
|
||||||
hex::{
|
|
||||||
CommandCode, DevicePropCode, FujiClarity, FujiColor, FujiColorChromeEffect,
|
|
||||||
FujiColorChromeFXBlue, FujiColorSpace, FujiCustomSetting, FujiCustomSettingName,
|
|
||||||
FujiDynamicRange, FujiDynamicRangePriority, FujiFilmSimulation, FujiGrainEffect,
|
|
||||||
FujiHighISONR, FujiHighlightTone, FujiImageQuality, FujiImageSize,
|
|
||||||
FujiLensModulationOptimizer, FujiMonochromaticColorTemperature, FujiMonochromaticColorTint,
|
|
||||||
FujiShadowTone, FujiSharpness, FujiSmoothSkinEffect, FujiWhiteBalance,
|
|
||||||
FujiWhiteBalanceShift, FujiWhiteBalanceTemperature, ObjectFormat, UsbMode,
|
|
||||||
},
|
|
||||||
structs::{DeviceInfo, ObjectInfo},
|
|
||||||
};
|
};
|
||||||
use ptp_cursor::{PtpDeserialize, PtpSerialize};
|
use anyhow::bail;
|
||||||
|
use devices::{DeviceImpl, SensorImpl, x_trans_v};
|
||||||
|
use erased_serde::serialize_trait_object;
|
||||||
|
use log::{debug, error};
|
||||||
|
use ptp::{Ptp, hex::FujiCustomSetting};
|
||||||
use rusb::{GlobalContext, constants::LIBUSB_CLASS_IMAGE};
|
use rusb::{GlobalContext, constants::LIBUSB_CLASS_IMAGE};
|
||||||
|
use serde::Serialize;
|
||||||
use crate::usb::find_endpoint;
|
|
||||||
|
|
||||||
const SESSION: u32 = 1;
|
const SESSION: u32 = 1;
|
||||||
|
|
||||||
pub struct Camera {
|
pub struct Camera {
|
||||||
pub r#impl: Box<dyn CameraImpl<GlobalContext>>,
|
pub device: Box<dyn DeviceImpl<GlobalContext>>,
|
||||||
|
pub sensor: Box<dyn SensorImpl<GlobalContext>>,
|
||||||
pub ptp: Ptp,
|
pub ptp: Ptp,
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! camera_with_ptp {
|
macro_rules! camera_to_device {
|
||||||
($($fn_name:ident => $ret:ty),* $(,)?) => {
|
($name:ident -> $ret:ty) => {
|
||||||
$(
|
pub fn $name(&mut self) -> $ret {
|
||||||
#[allow(dead_code)]
|
self.device.$name(&mut self.ptp)
|
||||||
pub fn $fn_name(&mut self) -> anyhow::Result<$ret> {
|
}
|
||||||
self.r#impl.$fn_name(&mut self.ptp)
|
|
||||||
}
|
|
||||||
)*
|
|
||||||
};
|
};
|
||||||
($($fn_name:ident($($arg:ident : $arg_ty:ty),*) => $ret:ty),* $(,)?) => {
|
|
||||||
$(
|
($name:ident($($arg:ident: $arg_ty:ty),*) -> $ret:ty) => {
|
||||||
#[allow(dead_code)]
|
pub fn $name(&mut self, $($arg: $arg_ty),*) -> $ret {
|
||||||
pub fn $fn_name(&mut self, $($arg: $arg_ty),*) -> anyhow::Result<$ret> {
|
self.device.$name(&mut self.ptp, $($arg),*)
|
||||||
self.r#impl.$fn_name(&mut self.ptp, $($arg),*)
|
}
|
||||||
}
|
|
||||||
)*
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
macro_rules! camera_to_sensor {
|
||||||
|
($name:ident -> $ret:ty) => {
|
||||||
|
pub fn $name(&mut self) -> $ret {
|
||||||
|
self.sensor.$name(&mut self.ptp, &self.device)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
($name:ident($($arg:ident: $arg_ty:ty),*) -> $ret:ty) => {
|
||||||
|
pub fn $name(&mut self, $($arg: $arg_ty),*) -> $ret {
|
||||||
|
self.sensor.$name(&mut self.ptp, &*self.device, $($arg),*)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait CameraResult: fmt::Display + erased_serde::Serialize {}
|
||||||
|
impl<T: fmt::Display + serde::Serialize> CameraResult for T {}
|
||||||
|
serialize_trait_object!(CameraResult);
|
||||||
|
|
||||||
impl Camera {
|
impl Camera {
|
||||||
pub fn name(&self) -> &'static str {
|
pub fn name(&self) -> &'static str {
|
||||||
self.r#impl.supported_camera().name
|
self.device.camera_definition().name
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn vendor_id(&self) -> u16 {
|
pub fn vendor_id(&self) -> u16 {
|
||||||
self.r#impl.supported_camera().vendor
|
self.device.camera_definition().vendor
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn product_id(&self) -> u16 {
|
pub fn product_id(&self) -> u16 {
|
||||||
self.r#impl.supported_camera().product
|
self.device.camera_definition().product
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn connected_usb_id(&self) -> String {
|
pub fn connected_usb_id(&self) -> String {
|
||||||
format!("{}.{}", self.ptp.bus, self.ptp.address)
|
format!("{}.{}", self.ptp.bus, self.ptp.address)
|
||||||
}
|
}
|
||||||
|
|
||||||
camera_with_ptp! {
|
camera_to_device!(info_get() -> anyhow::Result<Box<dyn CameraResult>>);
|
||||||
get_info => DeviceInfo,
|
|
||||||
get_usb_mode => UsbMode,
|
|
||||||
get_battery_info => u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
camera_with_ptp! {
|
camera_to_device!(backup_export -> anyhow::Result<Vec<u8>>);
|
||||||
export_backup => Vec<u8>,
|
camera_to_device!(backup_import(buffer: &[u8]) -> anyhow::Result<()>);
|
||||||
get_active_custom_setting => FujiCustomSetting,
|
|
||||||
get_custom_setting_name => FujiCustomSettingName,
|
|
||||||
get_image_size => FujiImageSize,
|
|
||||||
get_image_quality => FujiImageQuality,
|
|
||||||
get_dynamic_range => FujiDynamicRange,
|
|
||||||
get_dynamic_range_priority => FujiDynamicRangePriority,
|
|
||||||
get_film_simulation => FujiFilmSimulation,
|
|
||||||
get_monochromatic_color_temperature => FujiMonochromaticColorTemperature,
|
|
||||||
get_monochromatic_color_tint => FujiMonochromaticColorTint,
|
|
||||||
get_grain_effect => FujiGrainEffect,
|
|
||||||
get_white_balance => FujiWhiteBalance,
|
|
||||||
get_high_iso_nr => FujiHighISONR,
|
|
||||||
get_highlight_tone => FujiHighlightTone,
|
|
||||||
get_shadow_tone => FujiShadowTone,
|
|
||||||
get_color => FujiColor,
|
|
||||||
get_sharpness => FujiSharpness,
|
|
||||||
get_clarity => FujiClarity,
|
|
||||||
get_white_balance_shift_red => FujiWhiteBalanceShift,
|
|
||||||
get_white_balance_shift_blue => FujiWhiteBalanceShift,
|
|
||||||
get_white_balance_temperature => FujiWhiteBalanceTemperature,
|
|
||||||
get_color_chrome_effect => FujiColorChromeEffect,
|
|
||||||
get_color_chrome_fx_blue => FujiColorChromeFXBlue,
|
|
||||||
get_smooth_skin_effect => FujiSmoothSkinEffect,
|
|
||||||
get_lens_modulation_optimizer => FujiLensModulationOptimizer,
|
|
||||||
get_color_space => FujiColorSpace,
|
|
||||||
}
|
|
||||||
|
|
||||||
camera_with_ptp! {
|
camera_to_sensor!(simulation_list() -> anyhow::Result<Vec<Box<dyn CameraResult>>>);
|
||||||
import_backup(buffer: &[u8]) => (),
|
camera_to_sensor!(simulation_get(slot: FujiCustomSetting) -> anyhow::Result<Box<dyn CameraResult>>);
|
||||||
set_active_custom_setting(value: &FujiCustomSetting) => (),
|
camera_to_sensor!(simulation_set(slot: FujiCustomSetting, set_options: &SetFilmSimulationOptions, options: &FilmSimulationOptions) -> anyhow::Result<()>);
|
||||||
set_custom_setting_name(value: &FujiCustomSettingName) => (),
|
|
||||||
set_image_size(value: &FujiImageSize) => (),
|
|
||||||
set_image_quality(value: &FujiImageQuality) => (),
|
|
||||||
set_dynamic_range(value: &FujiDynamicRange) => (),
|
|
||||||
set_dynamic_range_priority(value: &FujiDynamicRangePriority) => (),
|
|
||||||
set_film_simulation(value: &FujiFilmSimulation) => (),
|
|
||||||
set_monochromatic_color_temperature(value: &FujiMonochromaticColorTemperature) => (),
|
|
||||||
set_monochromatic_color_tint(value: &FujiMonochromaticColorTint) => (),
|
|
||||||
set_grain_effect(value: &FujiGrainEffect) => (),
|
|
||||||
set_white_balance(value: &FujiWhiteBalance) => (),
|
|
||||||
set_high_iso_nr(value: &FujiHighISONR) => (),
|
|
||||||
set_highlight_tone(value: &FujiHighlightTone) => (),
|
|
||||||
set_shadow_tone(value: &FujiShadowTone) => (),
|
|
||||||
set_color(value: &FujiColor) => (),
|
|
||||||
set_sharpness(value: &FujiSharpness) => (),
|
|
||||||
set_clarity(value: &FujiClarity) => (),
|
|
||||||
set_white_balance_shift_red(value: &FujiWhiteBalanceShift) => (),
|
|
||||||
set_white_balance_shift_blue(value: &FujiWhiteBalanceShift) => (),
|
|
||||||
set_white_balance_temperature(value: &FujiWhiteBalanceTemperature) => (),
|
|
||||||
set_color_chrome_effect(value: &FujiColorChromeEffect) => (),
|
|
||||||
set_color_chrome_fx_blue(value: &FujiColorChromeFXBlue) => (),
|
|
||||||
set_smooth_skin_effect(value: &FujiSmoothSkinEffect) => (),
|
|
||||||
set_lens_modulation_optimizer(value: &FujiLensModulationOptimizer) => (),
|
|
||||||
set_color_space(value: &FujiColorSpace) => (),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for Camera {
|
impl Drop for Camera {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
debug!("Closing session");
|
debug!("Closing session");
|
||||||
if let Err(e) = self.ptp.close_session(SESSION, self.r#impl.timeout()) {
|
if let Err(e) = self.ptp.close_session(SESSION) {
|
||||||
error!("Error closing session: {e}");
|
error!("Error closing session: {e}");
|
||||||
}
|
}
|
||||||
debug!("Session closed");
|
debug!("Session closed");
|
||||||
@@ -152,12 +102,13 @@ impl TryFrom<&rusb::Device<GlobalContext>> for Camera {
|
|||||||
let vendor = descriptor.vendor_id();
|
let vendor = descriptor.vendor_id();
|
||||||
let product = descriptor.product_id();
|
let product = descriptor.product_id();
|
||||||
|
|
||||||
for supported_camera in devices::SUPPORTED {
|
for supported_camera in SUPPORTED {
|
||||||
if vendor != supported_camera.vendor || product != supported_camera.product {
|
if vendor != supported_camera.vendor || product != supported_camera.product {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let r#impl = (supported_camera.impl_factory)();
|
let device_impl = (supported_camera.device_factory)();
|
||||||
|
let sensor_impl = (supported_camera.sensor_factory)();
|
||||||
|
|
||||||
let bus = device.bus_number();
|
let bus = device.bus_number();
|
||||||
let address = device.address();
|
let address = device.address();
|
||||||
@@ -189,7 +140,7 @@ impl TryFrom<&rusb::Device<GlobalContext>> for Camera {
|
|||||||
|
|
||||||
let transaction_id = 0;
|
let transaction_id = 0;
|
||||||
|
|
||||||
let chunk_size = r#impl.chunk_size();
|
let chunk_size = device_impl.chunk_size();
|
||||||
|
|
||||||
let mut ptp = Ptp {
|
let mut ptp = Ptp {
|
||||||
bus,
|
bus,
|
||||||
@@ -203,154 +154,60 @@ impl TryFrom<&rusb::Device<GlobalContext>> for Camera {
|
|||||||
};
|
};
|
||||||
|
|
||||||
debug!("Opening session");
|
debug!("Opening session");
|
||||||
let () = ptp.open_session(SESSION, r#impl.timeout())?;
|
let () = ptp.open_session(SESSION)?;
|
||||||
debug!("Session opened");
|
debug!("Session opened");
|
||||||
|
|
||||||
return Ok(Self { r#impl, ptp });
|
return Ok(Self {
|
||||||
|
ptp,
|
||||||
|
device: device_impl,
|
||||||
|
sensor: sensor_impl,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
bail!("Device not supported");
|
bail!("Device not supported");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! camera_impl_custom_settings {
|
type DeviceFactory<P> = fn() -> Box<dyn DeviceImpl<P>>;
|
||||||
($( $name:ident : $type:ty => $code:expr ),+ $(,)?) => {
|
type SensorFactory<P> = fn() -> Box<dyn SensorImpl<P>>;
|
||||||
$(
|
|
||||||
paste::paste! {
|
|
||||||
#[allow(dead_code)]
|
|
||||||
fn [<get_ $name>](&self, ptp: &mut Ptp) -> anyhow::Result<$type> {
|
|
||||||
let bytes = ptp.get_prop_value($code, self.timeout())?;
|
|
||||||
let result = <$type>::try_from_ptp(&bytes)?;
|
|
||||||
Ok(result)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[derive(Serialize)]
|
||||||
fn [<set_ $name>](&self, ptp: &mut Ptp, value: &$type) -> anyhow::Result<()> {
|
#[serde(rename_all = "camelCase")]
|
||||||
let bytes = value.try_into_ptp()?;
|
pub struct CameraInfoListItem {
|
||||||
ptp.set_prop_value($code, &bytes, self.timeout())?;
|
pub name: &'static str,
|
||||||
Ok(())
|
pub usb_id: String,
|
||||||
}
|
pub vendor_id: String,
|
||||||
}
|
pub product_id: String,
|
||||||
)+
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait CameraImpl<P: rusb::UsbContext> {
|
impl From<&Camera> for CameraInfoListItem {
|
||||||
fn supported_camera(&self) -> &'static SupportedCamera<P>;
|
fn from(camera: &Camera) -> Self {
|
||||||
|
Self {
|
||||||
fn timeout(&self) -> Duration {
|
name: camera.name(),
|
||||||
Duration::default()
|
usb_id: camera.connected_usb_id(),
|
||||||
}
|
vendor_id: format!("0x{:04x}", camera.vendor_id()),
|
||||||
|
product_id: format!("0x{:04x}", camera.product_id()),
|
||||||
fn chunk_size(&self) -> usize {
|
|
||||||
// Conservative estimate. Could go up to 15.75 * 1024^2 on the X-T5 but only gained 200ms.
|
|
||||||
1024 * 1024
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_info(&mut self, ptp: &mut Ptp) -> anyhow::Result<DeviceInfo> {
|
|
||||||
let info = ptp.get_info(self.timeout())?;
|
|
||||||
Ok(info)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_usb_mode(&mut self, ptp: &mut Ptp) -> anyhow::Result<UsbMode> {
|
|
||||||
let data = ptp.get_prop_value(DevicePropCode::FujiUsbMode, self.timeout())?;
|
|
||||||
let result = UsbMode::try_from_ptp(&data)?;
|
|
||||||
Ok(result)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_battery_info(&mut self, ptp: &mut Ptp) -> anyhow::Result<u32> {
|
|
||||||
let data = ptp.get_prop_value(DevicePropCode::FujiBatteryInfo2, self.timeout())?;
|
|
||||||
|
|
||||||
debug!("Raw battery data: {data:?}");
|
|
||||||
|
|
||||||
let raw_string = String::try_from_ptp(&data)?;
|
|
||||||
debug!("Decoded raw string: {raw_string}");
|
|
||||||
|
|
||||||
let percentage: u32 = raw_string
|
|
||||||
.split(',')
|
|
||||||
.next()
|
|
||||||
.ok_or_else(|| anyhow!("Failed to parse battery percentage"))?
|
|
||||||
.parse()?;
|
|
||||||
|
|
||||||
Ok(percentage)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn export_backup(&self, ptp: &mut Ptp) -> anyhow::Result<Vec<u8>> {
|
|
||||||
const HANDLE: u32 = 0x0;
|
|
||||||
|
|
||||||
debug!("Sending GetObjectInfo command for backup");
|
|
||||||
let response = ptp.send(CommandCode::GetObjectInfo, &[HANDLE], None, self.timeout())?;
|
|
||||||
debug!("Received response with {} bytes", response.len());
|
|
||||||
|
|
||||||
debug!("Sending GetObject command for backup");
|
|
||||||
let response = ptp.send(CommandCode::GetObject, &[HANDLE], None, self.timeout())?;
|
|
||||||
debug!("Received response with {} bytes", response.len());
|
|
||||||
|
|
||||||
Ok(response)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn import_backup(&self, ptp: &mut Ptp, buffer: &[u8]) -> anyhow::Result<()> {
|
|
||||||
debug!("Preparing ObjectInfo header for backup");
|
|
||||||
|
|
||||||
let mut header = Vec::with_capacity(1076);
|
|
||||||
|
|
||||||
let object_info = ObjectInfo {
|
|
||||||
object_format: ObjectFormat::FujiBackup,
|
|
||||||
compressed_size: u32::try_from(buffer.len())?,
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
object_info.try_write_ptp(&mut header)?;
|
|
||||||
// TODO: What is this?
|
|
||||||
for _ in 0..1020 {
|
|
||||||
0x0u8.try_write_ptp(&mut header)?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
debug!("Sending SendObjectInfo command for backup");
|
|
||||||
let response = ptp.send(
|
|
||||||
CommandCode::SendObjectInfo,
|
|
||||||
&[0x0, 0x0],
|
|
||||||
Some(&header),
|
|
||||||
self.timeout(),
|
|
||||||
)?;
|
|
||||||
debug!("Received response with {} bytes", response.len());
|
|
||||||
|
|
||||||
debug!("Sending SendObject command for backup");
|
|
||||||
let response = ptp.send(
|
|
||||||
CommandCode::SendObject,
|
|
||||||
&[0x0],
|
|
||||||
Some(buffer),
|
|
||||||
self.timeout(),
|
|
||||||
)?;
|
|
||||||
debug!("Received response with {} bytes", response.len());
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
camera_impl_custom_settings! {
|
|
||||||
active_custom_setting: FujiCustomSetting => DevicePropCode::FujiCustomSetting,
|
|
||||||
custom_setting_name: FujiCustomSettingName => DevicePropCode::FujiCustomSettingName,
|
|
||||||
image_size: FujiImageSize => DevicePropCode::FujiCustomSettingImageSize,
|
|
||||||
image_quality: FujiImageQuality => DevicePropCode::FujiCustomSettingImageQuality,
|
|
||||||
dynamic_range: FujiDynamicRange => DevicePropCode::FujiCustomSettingDynamicRange,
|
|
||||||
dynamic_range_priority: FujiDynamicRangePriority => DevicePropCode::FujiCustomSettingDynamicRangePriority,
|
|
||||||
film_simulation: FujiFilmSimulation => DevicePropCode::FujiCustomSettingFilmSimulation,
|
|
||||||
monochromatic_color_temperature: FujiMonochromaticColorTemperature => DevicePropCode::FujiCustomSettingMonochromaticColorTemperature,
|
|
||||||
monochromatic_color_tint: FujiMonochromaticColorTint => DevicePropCode::FujiCustomSettingMonochromaticColorTint,
|
|
||||||
grain_effect: FujiGrainEffect => DevicePropCode::FujiCustomSettingGrainEffect,
|
|
||||||
white_balance: FujiWhiteBalance => DevicePropCode::FujiCustomSettingWhiteBalance,
|
|
||||||
high_iso_nr: FujiHighISONR => DevicePropCode::FujiCustomSettingHighISONR,
|
|
||||||
highlight_tone: FujiHighlightTone => DevicePropCode::FujiCustomSettingHighlightTone,
|
|
||||||
shadow_tone: FujiShadowTone => DevicePropCode::FujiCustomSettingShadowTone,
|
|
||||||
color: FujiColor => DevicePropCode::FujiCustomSettingColor,
|
|
||||||
sharpness: FujiSharpness => DevicePropCode::FujiCustomSettingSharpness,
|
|
||||||
clarity: FujiClarity => DevicePropCode::FujiCustomSettingClarity,
|
|
||||||
white_balance_shift_red: FujiWhiteBalanceShift => DevicePropCode::FujiCustomSettingWhiteBalanceShiftRed,
|
|
||||||
white_balance_shift_blue: FujiWhiteBalanceShift => DevicePropCode::FujiCustomSettingWhiteBalanceShiftBlue,
|
|
||||||
white_balance_temperature: FujiWhiteBalanceTemperature => DevicePropCode::FujiCustomSettingWhiteBalanceTemperature,
|
|
||||||
color_chrome_effect: FujiColorChromeEffect => DevicePropCode::FujiCustomSettingColorChromeEffect,
|
|
||||||
color_chrome_fx_blue: FujiColorChromeFXBlue => DevicePropCode::FujiCustomSettingColorChromeFXBlue,
|
|
||||||
smooth_skin_effect: FujiSmoothSkinEffect => DevicePropCode::FujiCustomSettingSmoothSkinEffect,
|
|
||||||
lens_modulation_optimizer: FujiLensModulationOptimizer => DevicePropCode::FujiCustomSettingLensModulationOptimizer,
|
|
||||||
color_space: FujiColorSpace => DevicePropCode::FujiCustomSettingColorSpace,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for CameraInfoListItem {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"{} ({}:{}) (USB ID: {})",
|
||||||
|
self.name, self.vendor_id, self.product_id, self.usb_id
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub struct SupportedCamera<P: rusb::UsbContext> {
|
||||||
|
pub name: &'static str,
|
||||||
|
pub vendor: u16,
|
||||||
|
pub product: u16,
|
||||||
|
pub device_factory: DeviceFactory<P>,
|
||||||
|
pub sensor_factory: SensorFactory<P>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const SUPPORTED: &[SupportedCamera<GlobalContext>] = &[x_trans_v::x_t5::FUJIFILM_XT5];
|
||||||
|
|||||||
@@ -18,8 +18,10 @@ pub enum CommandCode {
|
|||||||
GetDeviceInfo = 0x1001,
|
GetDeviceInfo = 0x1001,
|
||||||
OpenSession = 0x1002,
|
OpenSession = 0x1002,
|
||||||
CloseSession = 0x1003,
|
CloseSession = 0x1003,
|
||||||
|
GetObjectHandles = 0x1007,
|
||||||
GetObjectInfo = 0x1008,
|
GetObjectInfo = 0x1008,
|
||||||
GetObject = 0x1009,
|
GetObject = 0x1009,
|
||||||
|
DeleteObject = 0x100B,
|
||||||
SendObjectInfo = 0x100C,
|
SendObjectInfo = 0x100C,
|
||||||
SendObject = 0x100D,
|
SendObject = 0x100D,
|
||||||
GetDevicePropValue = 0x1015,
|
GetDevicePropValue = 0x1015,
|
||||||
@@ -309,6 +311,8 @@ pub enum FujiDynamicRange {
|
|||||||
HDR100 = 0x64,
|
HDR100 = 0x64,
|
||||||
HDR200 = 0xc8,
|
HDR200 = 0xc8,
|
||||||
HDR400 = 0x190,
|
HDR400 = 0x190,
|
||||||
|
// TODO: Limit this when setting film sim
|
||||||
|
HDR800 = 0x320,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[repr(u16)]
|
#[repr(u16)]
|
||||||
@@ -327,6 +331,8 @@ pub enum FujiDynamicRange {
|
|||||||
)]
|
)]
|
||||||
pub enum FujiDynamicRangePriority {
|
pub enum FujiDynamicRangePriority {
|
||||||
Auto = 0x8000,
|
Auto = 0x8000,
|
||||||
|
// TODO: Limit this, used in conjuction with HDR800
|
||||||
|
Plus = 0x3,
|
||||||
Strong = 0x2,
|
Strong = 0x2,
|
||||||
Weak = 0x1,
|
Weak = 0x1,
|
||||||
Off = 0x0,
|
Off = 0x0,
|
||||||
@@ -369,6 +375,27 @@ pub enum FujiFilmSimulation {
|
|||||||
RealaAce = 0x14,
|
RealaAce = 0x14,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[repr(u16)]
|
||||||
|
#[derive(
|
||||||
|
Debug,
|
||||||
|
Clone,
|
||||||
|
Copy,
|
||||||
|
PartialEq,
|
||||||
|
Eq,
|
||||||
|
Serialize,
|
||||||
|
IntoPrimitive,
|
||||||
|
TryFromPrimitive,
|
||||||
|
PtpSerialize,
|
||||||
|
PtpDeserialize,
|
||||||
|
EnumIter,
|
||||||
|
)]
|
||||||
|
pub enum FujiFileType {
|
||||||
|
Jpeg = 0x7,
|
||||||
|
Heif = 0x12,
|
||||||
|
Tiff8 = 0x9,
|
||||||
|
Tiff10 = 0xb,
|
||||||
|
}
|
||||||
|
|
||||||
#[repr(u16)]
|
#[repr(u16)]
|
||||||
#[derive(
|
#[derive(
|
||||||
Debug,
|
Debug,
|
||||||
@@ -388,7 +415,10 @@ pub enum FujiGrainEffect {
|
|||||||
WeakLarge = 0x4,
|
WeakLarge = 0x4,
|
||||||
StrongSmall = 0x3,
|
StrongSmall = 0x3,
|
||||||
WeakSmall = 0x2,
|
WeakSmall = 0x2,
|
||||||
Off = 0x6,
|
// TODO: God knows what the fuck is happening here.
|
||||||
|
// If I do Set 0x1 and immediately Get, camera returns 0x6 or 0x7.
|
||||||
|
#[num_enum(alternatives = [0x6, 0x7])]
|
||||||
|
Off = 0x1,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[repr(u16)]
|
#[repr(u16)]
|
||||||
@@ -451,6 +481,25 @@ pub enum FujiSmoothSkinEffect {
|
|||||||
Off = 0x1,
|
Off = 0x1,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[repr(u16)]
|
||||||
|
#[derive(
|
||||||
|
Debug,
|
||||||
|
Clone,
|
||||||
|
Copy,
|
||||||
|
PartialEq,
|
||||||
|
Eq,
|
||||||
|
Serialize,
|
||||||
|
IntoPrimitive,
|
||||||
|
TryFromPrimitive,
|
||||||
|
PtpSerialize,
|
||||||
|
PtpDeserialize,
|
||||||
|
EnumIter,
|
||||||
|
)]
|
||||||
|
pub enum FujiWhiteBalanceAsShot {
|
||||||
|
False = 0x2,
|
||||||
|
True = 0x1,
|
||||||
|
}
|
||||||
|
|
||||||
#[repr(u16)]
|
#[repr(u16)]
|
||||||
#[derive(
|
#[derive(
|
||||||
Debug,
|
Debug,
|
||||||
@@ -466,7 +515,6 @@ pub enum FujiSmoothSkinEffect {
|
|||||||
EnumIter,
|
EnumIter,
|
||||||
)]
|
)]
|
||||||
pub enum FujiWhiteBalance {
|
pub enum FujiWhiteBalance {
|
||||||
AsShot = 0x1,
|
|
||||||
WhitePriority = 0x8020,
|
WhitePriority = 0x8020,
|
||||||
Auto = 0x2,
|
Auto = 0x2,
|
||||||
AmbiencePriority = 0x8021,
|
AmbiencePriority = 0x8021,
|
||||||
@@ -538,6 +586,25 @@ pub enum FujiColorSpace {
|
|||||||
AdobeRGB = 0x1,
|
AdobeRGB = 0x1,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[repr(u16)]
|
||||||
|
#[derive(
|
||||||
|
Debug,
|
||||||
|
Clone,
|
||||||
|
Copy,
|
||||||
|
PartialEq,
|
||||||
|
Eq,
|
||||||
|
Serialize,
|
||||||
|
IntoPrimitive,
|
||||||
|
TryFromPrimitive,
|
||||||
|
PtpSerialize,
|
||||||
|
PtpDeserialize,
|
||||||
|
EnumIter,
|
||||||
|
)]
|
||||||
|
pub enum FujiTeleconverter {
|
||||||
|
Off = 0x2,
|
||||||
|
On = 0x1,
|
||||||
|
}
|
||||||
|
|
||||||
macro_rules! fuji_i16 {
|
macro_rules! fuji_i16 {
|
||||||
($name:ident, $min:expr, $max:expr, $step:expr, $scale:literal) => {
|
($name:ident, $min:expr, $max:expr, $step:expr, $scale:literal) => {
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PtpSerialize, PtpDeserialize)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, PtpSerialize, PtpDeserialize)]
|
||||||
@@ -589,6 +656,12 @@ macro_rules! fuji_i16 {
|
|||||||
Ok(Self(value))
|
Ok(Self(value))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl std::convert::From<$name> for i16 {
|
||||||
|
fn from(value: $name) -> i16 {
|
||||||
|
*value.deref()
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -602,6 +675,30 @@ fuji_i16!(FujiColor, -4.0, 4.0, 1.0, 10i16);
|
|||||||
fuji_i16!(FujiSharpness, -4.0, 4.0, 1.0, 10i16);
|
fuji_i16!(FujiSharpness, -4.0, 4.0, 1.0, 10i16);
|
||||||
fuji_i16!(FujiClarity, -5.0, 5.0, 1.0, 10i16);
|
fuji_i16!(FujiClarity, -5.0, 5.0, 1.0, 10i16);
|
||||||
|
|
||||||
|
#[repr(i16)]
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, IntoPrimitive, TryFromPrimitive, EnumIter)]
|
||||||
|
pub enum FujiExposureOffset {
|
||||||
|
Plus3 = 3000,
|
||||||
|
Plus2_7 = 2667,
|
||||||
|
Plus2_3 = 2333,
|
||||||
|
Plus2 = 2000,
|
||||||
|
Plus1_7 = 1667,
|
||||||
|
Plus1_3 = 1333,
|
||||||
|
Plus1 = 1000,
|
||||||
|
Plus0_7 = 667,
|
||||||
|
Plus0_3 = 333,
|
||||||
|
Zero = 0,
|
||||||
|
Minus0_3 = -333,
|
||||||
|
Minus0_7 = -667,
|
||||||
|
Minus1 = -1000,
|
||||||
|
Minus1_3 = -1333,
|
||||||
|
Minus1_7 = -1667,
|
||||||
|
Minus2 = -2000,
|
||||||
|
Minus2_3 = -2333,
|
||||||
|
Minus2_7 = -2667,
|
||||||
|
Minus3 = -3000,
|
||||||
|
}
|
||||||
|
|
||||||
#[repr(u16)]
|
#[repr(u16)]
|
||||||
#[derive(
|
#[derive(
|
||||||
Debug, Clone, Copy, Serialize, PartialEq, Eq, IntoPrimitive, TryFromPrimitive, PtpDeserialize,
|
Debug, Clone, Copy, Serialize, PartialEq, Eq, IntoPrimitive, TryFromPrimitive, PtpDeserialize,
|
||||||
|
|||||||
@@ -28,50 +28,40 @@ impl Ptp {
|
|||||||
code: CommandCode,
|
code: CommandCode,
|
||||||
params: &[u32],
|
params: &[u32],
|
||||||
data: Option<&[u8]>,
|
data: Option<&[u8]>,
|
||||||
timeout: Duration,
|
|
||||||
) -> anyhow::Result<Vec<u8>> {
|
) -> anyhow::Result<Vec<u8>> {
|
||||||
let transaction_id = self.transaction_id;
|
let transaction_id = self.transaction_id;
|
||||||
self.send_header(code, params, transaction_id, timeout)?;
|
self.send_header(code, params, transaction_id)?;
|
||||||
if let Some(data) = data {
|
if let Some(data) = data {
|
||||||
self.write(ContainerType::Data, code, data, transaction_id, timeout)?;
|
self.write(ContainerType::Data, code, data, transaction_id)?;
|
||||||
}
|
}
|
||||||
let response = self.receive_response(timeout);
|
let response = self.receive_response();
|
||||||
self.transaction_id += 1;
|
self.transaction_id += 1;
|
||||||
response
|
response
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn open_session(&mut self, session_id: u32, timeout: Duration) -> anyhow::Result<()> {
|
pub fn open_session(&mut self, session_id: u32) -> anyhow::Result<()> {
|
||||||
debug!("Sending OpenSession command");
|
debug!("Sending OpenSession command");
|
||||||
self.send(CommandCode::OpenSession, &[session_id], None, timeout)?;
|
self.send(CommandCode::OpenSession, &[session_id], None)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn close_session(&mut self, _: u32, timeout: Duration) -> anyhow::Result<()> {
|
pub fn close_session(&mut self, _: u32) -> anyhow::Result<()> {
|
||||||
debug!("Sending CloseSession command");
|
debug!("Sending CloseSession command");
|
||||||
self.send(CommandCode::CloseSession, &[], None, timeout)?;
|
self.send(CommandCode::CloseSession, &[], None)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_info(&mut self, timeout: Duration) -> anyhow::Result<DeviceInfo> {
|
pub fn get_info(&mut self) -> anyhow::Result<DeviceInfo> {
|
||||||
debug!("Sending GetDeviceInfo command");
|
debug!("Sending GetDeviceInfo command");
|
||||||
let response = self.send(CommandCode::GetDeviceInfo, &[], None, timeout)?;
|
let response = self.send(CommandCode::GetDeviceInfo, &[], None)?;
|
||||||
debug!("Received response with {} bytes", response.len());
|
debug!("Received response with {} bytes", response.len());
|
||||||
let info = DeviceInfo::try_from_ptp(&response)?;
|
let info = DeviceInfo::try_from_ptp(&response)?;
|
||||||
Ok(info)
|
Ok(info)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_prop_value(
|
pub fn get_prop_value(&mut self, prop: DevicePropCode) -> anyhow::Result<Vec<u8>> {
|
||||||
&mut self,
|
|
||||||
prop: DevicePropCode,
|
|
||||||
timeout: Duration,
|
|
||||||
) -> anyhow::Result<Vec<u8>> {
|
|
||||||
debug!("Sending GetDevicePropValue command for property {prop:?}");
|
debug!("Sending GetDevicePropValue command for property {prop:?}");
|
||||||
let response = self.send(
|
let response = self.send(CommandCode::GetDevicePropValue, &[prop.into()], None)?;
|
||||||
CommandCode::GetDevicePropValue,
|
|
||||||
&[prop.into()],
|
|
||||||
None,
|
|
||||||
timeout,
|
|
||||||
)?;
|
|
||||||
debug!("Received response with {} bytes", response.len());
|
debug!("Received response with {} bytes", response.len());
|
||||||
Ok(response)
|
Ok(response)
|
||||||
}
|
}
|
||||||
@@ -80,15 +70,9 @@ impl Ptp {
|
|||||||
&mut self,
|
&mut self,
|
||||||
prop: DevicePropCode,
|
prop: DevicePropCode,
|
||||||
value: &[u8],
|
value: &[u8],
|
||||||
timeout: Duration,
|
|
||||||
) -> anyhow::Result<Vec<u8>> {
|
) -> anyhow::Result<Vec<u8>> {
|
||||||
debug!("Sending GetDevicePropValue command for property {prop:?}");
|
debug!("Sending GetDevicePropValue command for property {prop:?}");
|
||||||
let response = self.send(
|
let response = self.send(CommandCode::SetDevicePropValue, &[prop.into()], Some(value))?;
|
||||||
CommandCode::SetDevicePropValue,
|
|
||||||
&[prop.into()],
|
|
||||||
Some(value),
|
|
||||||
timeout,
|
|
||||||
)?;
|
|
||||||
debug!("Received response with {} bytes", response.len());
|
debug!("Received response with {} bytes", response.len());
|
||||||
Ok(response)
|
Ok(response)
|
||||||
}
|
}
|
||||||
@@ -98,7 +82,6 @@ impl Ptp {
|
|||||||
code: CommandCode,
|
code: CommandCode,
|
||||||
params: &[u32],
|
params: &[u32],
|
||||||
transaction_id: u32,
|
transaction_id: u32,
|
||||||
timeout: Duration,
|
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
let mut payload = Vec::with_capacity(params.len() * 4);
|
let mut payload = Vec::with_capacity(params.len() * 4);
|
||||||
for p in params {
|
for p in params {
|
||||||
@@ -112,21 +95,15 @@ impl Ptp {
|
|||||||
payload.len(),
|
payload.len(),
|
||||||
payload,
|
payload,
|
||||||
);
|
);
|
||||||
self.write(
|
self.write(ContainerType::Command, code, &payload, transaction_id)?;
|
||||||
ContainerType::Command,
|
|
||||||
code,
|
|
||||||
&payload,
|
|
||||||
transaction_id,
|
|
||||||
timeout,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn receive_response(&self, timeout: Duration) -> anyhow::Result<Vec<u8>> {
|
fn receive_response(&self) -> anyhow::Result<Vec<u8>> {
|
||||||
let mut response = Vec::new();
|
let mut response = Vec::new();
|
||||||
loop {
|
loop {
|
||||||
let (container, payload) = self.read(timeout)?;
|
let (container, payload) = self.read()?;
|
||||||
match container.kind {
|
match container.kind {
|
||||||
ContainerType::Data => {
|
ContainerType::Data => {
|
||||||
trace!("Response received: data ({} bytes)", payload.len());
|
trace!("Response received: data ({} bytes)", payload.len());
|
||||||
@@ -168,7 +145,6 @@ impl Ptp {
|
|||||||
code: CommandCode,
|
code: CommandCode,
|
||||||
payload: &[u8],
|
payload: &[u8],
|
||||||
transaction_id: u32,
|
transaction_id: u32,
|
||||||
timeout: Duration,
|
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
let container_info = ContainerInfo::new(kind, code, transaction_id, payload.len())?;
|
let container_info = ContainerInfo::new(kind, code, transaction_id, payload.len())?;
|
||||||
let mut buffer: Vec<u8> = container_info.try_into_ptp()?;
|
let mut buffer: Vec<u8> = container_info.try_into_ptp()?;
|
||||||
@@ -179,11 +155,13 @@ impl Ptp {
|
|||||||
trace!(
|
trace!(
|
||||||
"Writing PTP {kind:?} container, code: {code:?}, transaction: {transaction_id:?}, first payload chunk ({first_chunk_len} bytes)",
|
"Writing PTP {kind:?} container, code: {code:?}, transaction: {transaction_id:?}, first payload chunk ({first_chunk_len} bytes)",
|
||||||
);
|
);
|
||||||
self.handle.write_bulk(self.bulk_out, &buffer, timeout)?;
|
self.handle
|
||||||
|
.write_bulk(self.bulk_out, &buffer, Duration::ZERO)?;
|
||||||
|
|
||||||
for chunk in payload[first_chunk_len..].chunks(self.chunk_size) {
|
for chunk in payload[first_chunk_len..].chunks(self.chunk_size) {
|
||||||
trace!("Writing additional payload chunk ({} bytes)", chunk.len(),);
|
trace!("Writing additional payload chunk ({} bytes)", chunk.len(),);
|
||||||
self.handle.write_bulk(self.bulk_out, chunk, timeout)?;
|
self.handle
|
||||||
|
.write_bulk(self.bulk_out, chunk, Duration::ZERO)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
trace!(
|
trace!(
|
||||||
@@ -194,12 +172,12 @@ impl Ptp {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read(&self, timeout: Duration) -> anyhow::Result<(ContainerInfo, Vec<u8>)> {
|
fn read(&self) -> anyhow::Result<(ContainerInfo, Vec<u8>)> {
|
||||||
let mut stack_buf = [0u8; 8 * 1024];
|
let mut stack_buf = [0u8; 8 * 1024];
|
||||||
|
|
||||||
let n = self
|
let n = self
|
||||||
.handle
|
.handle
|
||||||
.read_bulk(self.bulk_in, &mut stack_buf, timeout)?;
|
.read_bulk(self.bulk_in, &mut stack_buf, Duration::ZERO)?;
|
||||||
let buf = &stack_buf[..n];
|
let buf = &stack_buf[..n];
|
||||||
trace!("Read chunk ({n} bytes)");
|
trace!("Read chunk ({n} bytes)");
|
||||||
|
|
||||||
@@ -220,7 +198,9 @@ impl Ptp {
|
|||||||
while payload.len() < payload_len {
|
while payload.len() < payload_len {
|
||||||
let remaining = payload_len - payload.len();
|
let remaining = payload_len - payload.len();
|
||||||
let mut chunk = vec![0u8; min(remaining, self.chunk_size)];
|
let mut chunk = vec![0u8; min(remaining, self.chunk_size)];
|
||||||
let n = self.handle.read_bulk(self.bulk_in, &mut chunk, timeout)?;
|
let n = self
|
||||||
|
.handle
|
||||||
|
.read_bulk(self.bulk_in, &mut chunk, Duration::ZERO)?;
|
||||||
trace!("Read additional chunk ({n} bytes)");
|
trace!("Read additional chunk ({n} bytes)");
|
||||||
if n == 0 {
|
if n == 0 {
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ use ptp_macro::{PtpDeserialize, PtpSerialize};
|
|||||||
|
|
||||||
use super::hex::{CommandCode, ContainerCode, ContainerType, ObjectFormat};
|
use super::hex::{CommandCode, ContainerCode, ContainerType, ObjectFormat};
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
#[derive(Debug, PtpSerialize, PtpDeserialize)]
|
#[derive(Debug, PtpSerialize, PtpDeserialize)]
|
||||||
pub struct DeviceInfo {
|
pub struct DeviceInfo {
|
||||||
pub version: u16,
|
pub version: u16,
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ fn handle_export(device_id: Option<&str>, output: &Output) -> anyhow::Result<()>
|
|||||||
let mut camera = usb::get_camera(device_id)?;
|
let mut camera = usb::get_camera(device_id)?;
|
||||||
|
|
||||||
let mut writer = output.get_writer()?;
|
let mut writer = output.get_writer()?;
|
||||||
let backup = camera.export_backup()?;
|
let backup = camera.backup_export()?;
|
||||||
writer.write_all(&backup)?;
|
writer.write_all(&backup)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -36,7 +36,7 @@ fn handle_import(device_id: Option<&str>, input: &Input) -> anyhow::Result<()> {
|
|||||||
let mut reader = input.get_reader()?;
|
let mut reader = input.get_reader()?;
|
||||||
let mut backup = Vec::new();
|
let mut backup = Vec::new();
|
||||||
reader.read_to_end(&mut backup)?;
|
reader.read_to_end(&mut backup)?;
|
||||||
camera.import_backup(&backup)?;
|
camera.backup_import(&backup)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,10 +9,11 @@ use crate::{
|
|||||||
camera::ptp::hex::{
|
camera::ptp::hex::{
|
||||||
FujiClarity, FujiColor, FujiColorChromeEffect, FujiColorChromeFXBlue, FujiColorSpace,
|
FujiClarity, FujiColor, FujiColorChromeEffect, FujiColorChromeFXBlue, FujiColorSpace,
|
||||||
FujiCustomSetting, FujiCustomSettingName, FujiDynamicRange, FujiDynamicRangePriority,
|
FujiCustomSetting, FujiCustomSettingName, FujiDynamicRange, FujiDynamicRangePriority,
|
||||||
FujiFilmSimulation, FujiGrainEffect, FujiHighISONR, FujiHighlightTone, FujiImageQuality,
|
FujiExposureOffset, FujiFilmSimulation, FujiGrainEffect, FujiHighISONR, FujiHighlightTone,
|
||||||
FujiImageSize, FujiLensModulationOptimizer, FujiMonochromaticColorTemperature,
|
FujiImageQuality, FujiImageSize, FujiLensModulationOptimizer,
|
||||||
FujiMonochromaticColorTint, FujiShadowTone, FujiSharpness, FujiSmoothSkinEffect,
|
FujiMonochromaticColorTemperature, FujiMonochromaticColorTint, FujiShadowTone,
|
||||||
FujiWhiteBalance, FujiWhiteBalanceShift, FujiWhiteBalanceTemperature, UsbMode,
|
FujiSharpness, FujiSmoothSkinEffect, FujiWhiteBalance, FujiWhiteBalanceShift,
|
||||||
|
FujiWhiteBalanceTemperature, UsbMode,
|
||||||
},
|
},
|
||||||
cli::common::suggest::get_closest,
|
cli::common::suggest::get_closest,
|
||||||
};
|
};
|
||||||
@@ -291,6 +292,7 @@ impl fmt::Display for FujiDynamicRange {
|
|||||||
Self::HDR100 => write!(f, "HDR100"),
|
Self::HDR100 => write!(f, "HDR100"),
|
||||||
Self::HDR200 => write!(f, "HDR200"),
|
Self::HDR200 => write!(f, "HDR200"),
|
||||||
Self::HDR400 => write!(f, "HDR400"),
|
Self::HDR400 => write!(f, "HDR400"),
|
||||||
|
Self::HDR800 => write!(f, "HDR800"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -306,6 +308,7 @@ impl FromStr for FujiDynamicRange {
|
|||||||
"100" | "hdr100" | "dr100" => return Ok(Self::HDR100),
|
"100" | "hdr100" | "dr100" => return Ok(Self::HDR100),
|
||||||
"200" | "hdr200" | "dr200" => return Ok(Self::HDR200),
|
"200" | "hdr200" | "dr200" => return Ok(Self::HDR200),
|
||||||
"400" | "hdr400" | "dr400" => return Ok(Self::HDR400),
|
"400" | "hdr400" | "dr400" => return Ok(Self::HDR400),
|
||||||
|
"800" | "hdr800" | "dr800" => return Ok(Self::HDR800),
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -325,6 +328,7 @@ impl fmt::Display for FujiDynamicRangePriority {
|
|||||||
Self::Strong => write!(f, "Strong"),
|
Self::Strong => write!(f, "Strong"),
|
||||||
Self::Weak => write!(f, "Weak"),
|
Self::Weak => write!(f, "Weak"),
|
||||||
Self::Off => write!(f, "Off"),
|
Self::Off => write!(f, "Off"),
|
||||||
|
Self::Plus => writeln!(f, "Plus"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -340,6 +344,7 @@ impl FromStr for FujiDynamicRangePriority {
|
|||||||
"strong" | "drpstrong" => return Ok(Self::Strong),
|
"strong" | "drpstrong" => return Ok(Self::Strong),
|
||||||
"weak" | "drpweak" => return Ok(Self::Weak),
|
"weak" | "drpweak" => return Ok(Self::Weak),
|
||||||
"off" | "drpoff" => return Ok(Self::Off),
|
"off" | "drpoff" => return Ok(Self::Off),
|
||||||
|
"plus" => return Ok(Self::Plus),
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -584,7 +589,6 @@ impl FromStr for FujiSmoothSkinEffect {
|
|||||||
impl fmt::Display for FujiWhiteBalance {
|
impl fmt::Display for FujiWhiteBalance {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
Self::AsShot => write!(f, "As Shot"),
|
|
||||||
Self::WhitePriority => write!(f, "White Priority"),
|
Self::WhitePriority => write!(f, "White Priority"),
|
||||||
Self::Auto => write!(f, "Auto"),
|
Self::Auto => write!(f, "Auto"),
|
||||||
Self::AmbiencePriority => write!(f, "Ambience Priority"),
|
Self::AmbiencePriority => write!(f, "Ambience Priority"),
|
||||||
@@ -611,8 +615,7 @@ impl FromStr for FujiWhiteBalance {
|
|||||||
|
|
||||||
match input.as_str() {
|
match input.as_str() {
|
||||||
"whitepriority" | "white" => return Ok(Self::WhitePriority),
|
"whitepriority" | "white" => return Ok(Self::WhitePriority),
|
||||||
// We can't set a film simulation to be "As Shot", so silently parse it to Auto
|
"auto" => return Ok(Self::Auto),
|
||||||
"auto" | "shot" | "asshot" | "original" => return Ok(Self::Auto),
|
|
||||||
"ambiencepriority" | "ambience" | "ambient" => {
|
"ambiencepriority" | "ambience" | "ambient" => {
|
||||||
return Ok(Self::AmbiencePriority);
|
return Ok(Self::AmbiencePriority);
|
||||||
}
|
}
|
||||||
@@ -764,6 +767,108 @@ impl FromStr for FujiColorSpace {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl FromStr for FujiExposureOffset {
|
||||||
|
type Err = anyhow::Error;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> anyhow::Result<Self> {
|
||||||
|
let input = s
|
||||||
|
.trim()
|
||||||
|
.parse::<f32>()
|
||||||
|
.with_context(|| format!("Invalid numeric value '{s}'"))?;
|
||||||
|
|
||||||
|
let round = (input * 10.0).round() / 10.0;
|
||||||
|
|
||||||
|
match round {
|
||||||
|
3.0 => return Ok(Self::Plus3),
|
||||||
|
2.7 => return Ok(Self::Plus2_7),
|
||||||
|
2.3 => return Ok(Self::Plus2_3),
|
||||||
|
2.0 => return Ok(Self::Plus2),
|
||||||
|
1.7 => return Ok(Self::Plus1_7),
|
||||||
|
1.3 => return Ok(Self::Plus1_3),
|
||||||
|
1.0 => return Ok(Self::Plus1),
|
||||||
|
0.7 => return Ok(Self::Plus0_7),
|
||||||
|
0.3 => return Ok(Self::Plus0_3),
|
||||||
|
0.0 => return Ok(Self::Zero),
|
||||||
|
-0.3 => return Ok(Self::Minus0_3),
|
||||||
|
-0.7 => return Ok(Self::Minus0_7),
|
||||||
|
-1.0 => return Ok(Self::Minus1),
|
||||||
|
-1.3 => return Ok(Self::Minus1_3),
|
||||||
|
-1.7 => return Ok(Self::Minus1_7),
|
||||||
|
-2.0 => return Ok(Self::Minus2),
|
||||||
|
-2.3 => return Ok(Self::Minus2_3),
|
||||||
|
-2.7 => return Ok(Self::Minus2_7),
|
||||||
|
-3.0 => return Ok(Self::Minus3),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
let choices: Vec<String> = Self::iter().map(|v| v.to_string()).collect();
|
||||||
|
if let Some(best) = get_closest(s, &choices) {
|
||||||
|
bail!("Unknown exposure offset '{s}'. Did you mean '{best}'?");
|
||||||
|
}
|
||||||
|
|
||||||
|
bail!("Unknown exposure offset '{s}'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for FujiExposureOffset {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
let val = match self {
|
||||||
|
Self::Minus3 => -3.0,
|
||||||
|
Self::Minus2_7 => -2.7,
|
||||||
|
Self::Minus2_3 => -2.3,
|
||||||
|
Self::Minus2 => -2.0,
|
||||||
|
Self::Minus1_7 => -1.7,
|
||||||
|
Self::Minus1_3 => -1.3,
|
||||||
|
Self::Minus1 => -1.0,
|
||||||
|
Self::Minus0_7 => -0.7,
|
||||||
|
Self::Minus0_3 => -0.3,
|
||||||
|
Self::Zero => 0.0,
|
||||||
|
Self::Plus0_3 => 0.3,
|
||||||
|
Self::Plus0_7 => 0.7,
|
||||||
|
Self::Plus1 => 1.0,
|
||||||
|
Self::Plus1_3 => 1.3,
|
||||||
|
Self::Plus1_7 => 1.7,
|
||||||
|
Self::Plus2 => 2.0,
|
||||||
|
Self::Plus2_3 => 2.3,
|
||||||
|
Self::Plus2_7 => 2.7,
|
||||||
|
Self::Plus3 => 3.0,
|
||||||
|
};
|
||||||
|
|
||||||
|
write!(f, "{val}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Serialize for FujiExposureOffset {
|
||||||
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: Serializer,
|
||||||
|
{
|
||||||
|
let val = match self {
|
||||||
|
Self::Minus3 => -3.0,
|
||||||
|
Self::Minus2_7 => -2.7,
|
||||||
|
Self::Minus2_3 => -2.3,
|
||||||
|
Self::Minus2 => -2.0,
|
||||||
|
Self::Minus1_7 => -1.7,
|
||||||
|
Self::Minus1_3 => -1.3,
|
||||||
|
Self::Minus1 => -1.0,
|
||||||
|
Self::Minus0_7 => -0.7,
|
||||||
|
Self::Minus0_3 => -0.3,
|
||||||
|
Self::Zero => 0.0,
|
||||||
|
Self::Plus0_3 => 0.3,
|
||||||
|
Self::Plus0_7 => 0.7,
|
||||||
|
Self::Plus1 => 1.0,
|
||||||
|
Self::Plus1_3 => 1.3,
|
||||||
|
Self::Plus1_7 => 1.7,
|
||||||
|
Self::Plus2 => 2.0,
|
||||||
|
Self::Plus2_3 => 2.3,
|
||||||
|
Self::Plus2_7 => 2.7,
|
||||||
|
Self::Plus3 => 3.0,
|
||||||
|
};
|
||||||
|
|
||||||
|
serializer.serialize_f32(val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
macro_rules! fuji_i16_cli {
|
macro_rules! fuji_i16_cli {
|
||||||
($name:ident) => {
|
($name:ident) => {
|
||||||
impl std::str::FromStr for $name {
|
impl std::str::FromStr for $name {
|
||||||
|
|||||||
@@ -1,12 +1,6 @@
|
|||||||
use std::fmt;
|
|
||||||
|
|
||||||
use clap::Subcommand;
|
use clap::Subcommand;
|
||||||
use serde::Serialize;
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{camera::CameraInfoListItem, usb};
|
||||||
camera::{Camera, ptp::hex::UsbMode},
|
|
||||||
usb,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Subcommand, Debug, Clone, Copy)]
|
#[derive(Subcommand, Debug, Clone, Copy)]
|
||||||
pub enum DeviceCmd {
|
pub enum DeviceCmd {
|
||||||
@@ -19,38 +13,8 @@ pub enum DeviceCmd {
|
|||||||
Info,
|
Info,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct CameraItemRepr {
|
|
||||||
pub name: &'static str,
|
|
||||||
pub usb_id: String,
|
|
||||||
pub vendor_id: String,
|
|
||||||
pub product_id: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<&Camera> for CameraItemRepr {
|
|
||||||
fn from(camera: &Camera) -> Self {
|
|
||||||
Self {
|
|
||||||
name: camera.name(),
|
|
||||||
usb_id: camera.connected_usb_id(),
|
|
||||||
vendor_id: format!("0x{:04x}", camera.vendor_id()),
|
|
||||||
product_id: format!("0x{:04x}", camera.product_id()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for CameraItemRepr {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
write!(
|
|
||||||
f,
|
|
||||||
"{} ({}:{}) (USB ID: {})",
|
|
||||||
self.name, self.vendor_id, self.product_id, self.usb_id
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_list(json: bool) -> anyhow::Result<()> {
|
fn handle_list(json: bool) -> anyhow::Result<()> {
|
||||||
let cameras: Vec<CameraItemRepr> = usb::get_connected_cameras()?
|
let cameras: Vec<CameraInfoListItem> = usb::get_connected_cameras()?
|
||||||
.iter()
|
.iter()
|
||||||
.map(std::convert::Into::into)
|
.map(std::convert::Into::into)
|
||||||
.collect();
|
.collect();
|
||||||
@@ -65,7 +29,6 @@ fn handle_list(json: bool) -> anyhow::Result<()> {
|
|||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
println!("Connected Cameras:");
|
|
||||||
for d in cameras {
|
for d in cameras {
|
||||||
println!("- {d}");
|
println!("- {d}");
|
||||||
}
|
}
|
||||||
@@ -73,54 +36,10 @@ fn handle_list(json: bool) -> anyhow::Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct CameraRepr {
|
|
||||||
#[serde(flatten)]
|
|
||||||
pub device: CameraItemRepr,
|
|
||||||
|
|
||||||
pub manufacturer: String,
|
|
||||||
pub model: String,
|
|
||||||
pub device_version: String,
|
|
||||||
pub serial_number: String,
|
|
||||||
pub mode: UsbMode,
|
|
||||||
pub battery: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for CameraRepr {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
writeln!(f, "Name: {}", self.device.name)?;
|
|
||||||
writeln!(f, "USB ID: {}", self.device.usb_id)?;
|
|
||||||
writeln!(
|
|
||||||
f,
|
|
||||||
"Vendor ID: {}, Product ID: {}",
|
|
||||||
self.device.vendor_id, self.device.product_id
|
|
||||||
)?;
|
|
||||||
writeln!(f, "Manufacturer: {}", self.manufacturer)?;
|
|
||||||
writeln!(f, "Model: {}", self.model)?;
|
|
||||||
writeln!(f, "Version: {}", self.device_version)?;
|
|
||||||
writeln!(f, "Serial Number: {}", self.serial_number)?;
|
|
||||||
writeln!(f, "Mode: {}", self.mode)?;
|
|
||||||
write!(f, "Battery: {}%", self.battery)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_info(json: bool, device_id: Option<&str>) -> anyhow::Result<()> {
|
fn handle_info(json: bool, device_id: Option<&str>) -> anyhow::Result<()> {
|
||||||
let mut camera = usb::get_camera(device_id)?;
|
let mut camera = usb::get_camera(device_id)?;
|
||||||
|
|
||||||
let info = camera.get_info()?;
|
let repr = camera.info_get()?;
|
||||||
let mode = camera.get_usb_mode()?;
|
|
||||||
let battery = camera.get_battery_info()?;
|
|
||||||
|
|
||||||
let repr = CameraRepr {
|
|
||||||
device: (&camera).into(),
|
|
||||||
manufacturer: info.manufacturer.clone(),
|
|
||||||
model: info.model.clone(),
|
|
||||||
device_version: info.device_version.clone(),
|
|
||||||
serial_number: info.serial_number,
|
|
||||||
mode,
|
|
||||||
battery,
|
|
||||||
};
|
|
||||||
|
|
||||||
if json {
|
if json {
|
||||||
println!("{}", serde_json::to_string_pretty(&repr)?);
|
println!("{}", serde_json::to_string_pretty(&repr)?);
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
mod common;
|
|
||||||
|
|
||||||
pub mod backup;
|
pub mod backup;
|
||||||
|
pub mod common;
|
||||||
pub mod device;
|
pub mod device;
|
||||||
pub mod render;
|
pub mod render;
|
||||||
pub mod simulation;
|
pub mod simulation;
|
||||||
|
|||||||
@@ -1,14 +1,5 @@
|
|||||||
use std::fmt;
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
camera::ptp::hex::{
|
camera::ptp::hex::{FujiCustomSetting, FujiCustomSettingName},
|
||||||
FujiClarity, FujiColor, FujiColorChromeEffect, FujiColorChromeFXBlue, FujiColorSpace,
|
|
||||||
FujiCustomSetting, FujiCustomSettingName, FujiDynamicRange, FujiDynamicRangePriority,
|
|
||||||
FujiFilmSimulation, FujiGrainEffect, FujiHighISONR, FujiHighlightTone, FujiImageQuality,
|
|
||||||
FujiImageSize, FujiLensModulationOptimizer, FujiMonochromaticColorTemperature,
|
|
||||||
FujiMonochromaticColorTint, FujiShadowTone, FujiSharpness, FujiSmoothSkinEffect,
|
|
||||||
FujiWhiteBalance, FujiWhiteBalanceShift, FujiWhiteBalanceTemperature,
|
|
||||||
},
|
|
||||||
usb,
|
usb,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -17,9 +8,6 @@ use super::common::{
|
|||||||
film::FilmSimulationOptions,
|
film::FilmSimulationOptions,
|
||||||
};
|
};
|
||||||
use clap::{Args, Subcommand};
|
use clap::{Args, Subcommand};
|
||||||
use log::warn;
|
|
||||||
use serde::Serialize;
|
|
||||||
use strum::IntoEnumIterator;
|
|
||||||
|
|
||||||
#[derive(Subcommand, Debug)]
|
#[derive(Subcommand, Debug)]
|
||||||
pub enum SimulationCmd {
|
pub enum SimulationCmd {
|
||||||
@@ -72,173 +60,27 @@ pub enum SimulationCmd {
|
|||||||
pub struct SetFilmSimulationOptions {
|
pub struct SetFilmSimulationOptions {
|
||||||
/// The name of the slot
|
/// The name of the slot
|
||||||
#[clap(long)]
|
#[clap(long)]
|
||||||
name: Option<FujiCustomSettingName>,
|
pub name: Option<FujiCustomSettingName>,
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct CustomSettingRepr {
|
|
||||||
pub slot: FujiCustomSetting,
|
|
||||||
pub name: FujiCustomSettingName,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_list(json: bool, device_id: Option<&str>) -> anyhow::Result<()> {
|
fn handle_list(json: bool, device_id: Option<&str>) -> anyhow::Result<()> {
|
||||||
let mut camera = usb::get_camera(device_id)?;
|
let mut camera = usb::get_camera(device_id)?;
|
||||||
|
let slots = camera.simulation_list()?;
|
||||||
let mut slots = Vec::new();
|
|
||||||
|
|
||||||
for slot in FujiCustomSetting::iter() {
|
|
||||||
camera.set_active_custom_setting(&slot)?;
|
|
||||||
let name = camera.get_custom_setting_name()?;
|
|
||||||
slots.push(CustomSettingRepr { slot, name });
|
|
||||||
}
|
|
||||||
|
|
||||||
if json {
|
if json {
|
||||||
println!("{}", serde_json::to_string_pretty(&slots)?);
|
println!("{}", serde_json::to_string_pretty(&slots)?);
|
||||||
} else {
|
} else {
|
||||||
println!("Film Simulations:");
|
for repr in slots {
|
||||||
for slot in slots {
|
println!("- {repr}");
|
||||||
println!("- {}: {}", slot.slot, slot.name);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct FilmSimulationRepr {
|
|
||||||
pub name: FujiCustomSettingName,
|
|
||||||
pub size: FujiImageSize,
|
|
||||||
pub quality: FujiImageQuality,
|
|
||||||
pub simulation: FujiFilmSimulation,
|
|
||||||
pub monochromatic_color_temperature: FujiMonochromaticColorTemperature,
|
|
||||||
pub monochromatic_color_tint: FujiMonochromaticColorTint,
|
|
||||||
pub highlight: FujiHighlightTone,
|
|
||||||
pub shadow: FujiShadowTone,
|
|
||||||
pub color: FujiColor,
|
|
||||||
pub sharpness: FujiSharpness,
|
|
||||||
pub clarity: FujiClarity,
|
|
||||||
pub noise_reduction: FujiHighISONR,
|
|
||||||
pub grain: FujiGrainEffect,
|
|
||||||
pub color_chrome_effect: FujiColorChromeEffect,
|
|
||||||
pub color_chrome_fx_blue: FujiColorChromeFXBlue,
|
|
||||||
pub smooth_skin_effect: FujiSmoothSkinEffect,
|
|
||||||
pub white_balance: FujiWhiteBalance,
|
|
||||||
pub white_balance_shift_red: FujiWhiteBalanceShift,
|
|
||||||
pub white_balance_shift_blue: FujiWhiteBalanceShift,
|
|
||||||
pub white_balance_temperature: FujiWhiteBalanceTemperature,
|
|
||||||
pub dynamic_range: FujiDynamicRange,
|
|
||||||
pub dynamic_range_priority: FujiDynamicRangePriority,
|
|
||||||
pub lens_modulation_optimizer: FujiLensModulationOptimizer,
|
|
||||||
pub color_space: FujiColorSpace,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for FilmSimulationRepr {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
writeln!(f, "Name: {}", self.name)?;
|
|
||||||
writeln!(f, "Size: {}", self.size)?;
|
|
||||||
writeln!(f, "Quality: {}", self.quality)?;
|
|
||||||
|
|
||||||
writeln!(f, "Simulation: {}", self.simulation)?;
|
|
||||||
|
|
||||||
match self.simulation {
|
|
||||||
FujiFilmSimulation::Monochrome
|
|
||||||
| FujiFilmSimulation::MonochromeYe
|
|
||||||
| FujiFilmSimulation::MonochromeR
|
|
||||||
| FujiFilmSimulation::MonochromeG
|
|
||||||
| FujiFilmSimulation::AcrosSTD
|
|
||||||
| FujiFilmSimulation::AcrosYe
|
|
||||||
| FujiFilmSimulation::AcrosR
|
|
||||||
| FujiFilmSimulation::AcrosG => {
|
|
||||||
writeln!(
|
|
||||||
f,
|
|
||||||
"Monochromatic Color Temperature: {}",
|
|
||||||
self.monochromatic_color_temperature
|
|
||||||
)?;
|
|
||||||
writeln!(
|
|
||||||
f,
|
|
||||||
"Monochromatic Color Tint: {}",
|
|
||||||
self.monochromatic_color_tint
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.dynamic_range_priority == FujiDynamicRangePriority::Off {
|
|
||||||
writeln!(f, "Highlights: {}", self.highlight)?;
|
|
||||||
writeln!(f, "Shadows: {}", self.shadow)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
writeln!(f, "Color: {}", self.color)?;
|
|
||||||
writeln!(f, "Sharpness: {}", self.sharpness)?;
|
|
||||||
writeln!(f, "Clarity: {}", self.clarity)?;
|
|
||||||
writeln!(f, "Noise Reduction: {}", self.noise_reduction)?;
|
|
||||||
writeln!(f, "Grain: {}", self.grain)?;
|
|
||||||
writeln!(f, "Color Chrome Effect: {}", self.color_chrome_effect)?;
|
|
||||||
writeln!(f, "Color Chrome FX Blue: {}", self.color_chrome_fx_blue)?;
|
|
||||||
writeln!(f, "Smooth Skin Effect: {}", self.smooth_skin_effect)?;
|
|
||||||
|
|
||||||
writeln!(f, "White Balance: {}", self.white_balance)?;
|
|
||||||
writeln!(
|
|
||||||
f,
|
|
||||||
"White Balance Shift (R/B): {} / {}",
|
|
||||||
self.white_balance_shift_red, self.white_balance_shift_blue
|
|
||||||
)?;
|
|
||||||
|
|
||||||
if self.white_balance == FujiWhiteBalance::Temperature {
|
|
||||||
writeln!(
|
|
||||||
f,
|
|
||||||
"White Balance Temperature: {}K",
|
|
||||||
self.white_balance_temperature
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.dynamic_range_priority == FujiDynamicRangePriority::Off {
|
|
||||||
writeln!(f, "Dynamic Range: {}", self.dynamic_range)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
writeln!(f, "Dynamic Range Priority: {}", self.dynamic_range_priority)?;
|
|
||||||
|
|
||||||
writeln!(
|
|
||||||
f,
|
|
||||||
"Lens Modulation Optimizer: {}",
|
|
||||||
self.lens_modulation_optimizer
|
|
||||||
)?;
|
|
||||||
writeln!(f, "Color Space: {}", self.color_space)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_get(json: bool, device_id: Option<&str>, slot: FujiCustomSetting) -> anyhow::Result<()> {
|
fn handle_get(json: bool, device_id: Option<&str>, slot: FujiCustomSetting) -> anyhow::Result<()> {
|
||||||
let mut camera = usb::get_camera(device_id)?;
|
let mut camera = usb::get_camera(device_id)?;
|
||||||
camera.set_active_custom_setting(&slot)?;
|
let repr = camera.simulation_get(slot)?;
|
||||||
|
|
||||||
let repr = FilmSimulationRepr {
|
|
||||||
name: camera.get_custom_setting_name()?,
|
|
||||||
size: camera.get_image_size()?,
|
|
||||||
quality: camera.get_image_quality()?,
|
|
||||||
simulation: camera.get_film_simulation()?,
|
|
||||||
monochromatic_color_temperature: camera.get_monochromatic_color_temperature()?,
|
|
||||||
monochromatic_color_tint: camera.get_monochromatic_color_tint()?,
|
|
||||||
highlight: camera.get_highlight_tone()?,
|
|
||||||
shadow: camera.get_shadow_tone()?,
|
|
||||||
color: camera.get_color()?,
|
|
||||||
sharpness: camera.get_sharpness()?,
|
|
||||||
clarity: camera.get_clarity()?,
|
|
||||||
noise_reduction: camera.get_high_iso_nr()?,
|
|
||||||
grain: camera.get_grain_effect()?,
|
|
||||||
color_chrome_effect: camera.get_color_chrome_effect()?,
|
|
||||||
color_chrome_fx_blue: camera.get_color_chrome_fx_blue()?,
|
|
||||||
smooth_skin_effect: camera.get_smooth_skin_effect()?,
|
|
||||||
white_balance: camera.get_white_balance()?,
|
|
||||||
white_balance_shift_red: camera.get_white_balance_shift_red()?,
|
|
||||||
white_balance_shift_blue: camera.get_white_balance_shift_blue()?,
|
|
||||||
white_balance_temperature: camera.get_white_balance_temperature()?,
|
|
||||||
dynamic_range: camera.get_dynamic_range()?,
|
|
||||||
dynamic_range_priority: camera.get_dynamic_range_priority()?,
|
|
||||||
lens_modulation_optimizer: camera.get_lens_modulation_optimizer()?,
|
|
||||||
color_space: camera.get_color_space()?,
|
|
||||||
};
|
|
||||||
|
|
||||||
if json {
|
if json {
|
||||||
println!("{}", serde_json::to_string_pretty(&repr)?);
|
println!("{}", serde_json::to_string_pretty(&repr)?);
|
||||||
@@ -258,176 +100,7 @@ fn handle_set(
|
|||||||
options: &FilmSimulationOptions,
|
options: &FilmSimulationOptions,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
let mut camera = usb::get_camera(device_id)?;
|
let mut camera = usb::get_camera(device_id)?;
|
||||||
camera.set_active_custom_setting(&slot)?;
|
camera.simulation_set(slot, set_options, options)?;
|
||||||
|
|
||||||
// General
|
|
||||||
if let Some(name) = &set_options.name {
|
|
||||||
camera.set_custom_setting_name(name)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(size) = &options.size {
|
|
||||||
camera.set_image_size(size)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(quality) = &options.quality {
|
|
||||||
camera.set_image_quality(quality)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Style
|
|
||||||
if let Some(simulation) = &options.simulation {
|
|
||||||
camera.set_film_simulation(simulation)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
if options.monochromatic_color_temperature.is_some()
|
|
||||||
|| options.monochromatic_color_tint.is_some()
|
|
||||||
{
|
|
||||||
let simulation = if let Some(simulation) = &options.simulation {
|
|
||||||
simulation
|
|
||||||
} else {
|
|
||||||
&camera.get_film_simulation()?
|
|
||||||
};
|
|
||||||
|
|
||||||
let is_bnw = matches!(
|
|
||||||
*simulation,
|
|
||||||
FujiFilmSimulation::Monochrome
|
|
||||||
| FujiFilmSimulation::MonochromeYe
|
|
||||||
| FujiFilmSimulation::MonochromeR
|
|
||||||
| FujiFilmSimulation::MonochromeG
|
|
||||||
| FujiFilmSimulation::AcrosSTD
|
|
||||||
| FujiFilmSimulation::AcrosYe
|
|
||||||
| FujiFilmSimulation::AcrosR
|
|
||||||
| FujiFilmSimulation::AcrosG
|
|
||||||
);
|
|
||||||
|
|
||||||
if let Some(monochromatic_color_temperature) = &options.monochromatic_color_temperature {
|
|
||||||
if is_bnw {
|
|
||||||
camera.set_monochromatic_color_temperature(monochromatic_color_temperature)?;
|
|
||||||
} else {
|
|
||||||
warn!(
|
|
||||||
"A B&W film simulation is not selected, refusing to set monochromatic color temperature"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(monochromatic_color_tint) = &options.monochromatic_color_tint {
|
|
||||||
if is_bnw {
|
|
||||||
camera.set_monochromatic_color_tint(monochromatic_color_tint)?;
|
|
||||||
} else {
|
|
||||||
warn!(
|
|
||||||
"A B&W film simulation is not selected, refusing to set monochromatic color tint"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(color) = &options.color {
|
|
||||||
camera.set_color(color)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(sharpness) = &options.sharpness {
|
|
||||||
camera.set_sharpness(sharpness)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(clarity) = &options.clarity {
|
|
||||||
camera.set_clarity(clarity)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(noise_reduction) = &options.noise_reduction {
|
|
||||||
camera.set_high_iso_nr(noise_reduction)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(grain) = &options.grain {
|
|
||||||
camera.set_grain_effect(grain)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(color_chrome_effect) = &options.color_chrome_effect {
|
|
||||||
camera.set_color_chrome_effect(color_chrome_effect)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(color_chrome_fx_blue) = &options.color_chrome_fx_blue {
|
|
||||||
camera.set_color_chrome_fx_blue(color_chrome_fx_blue)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(smooth_skin_effect) = &options.smooth_skin_effect {
|
|
||||||
camera.set_smooth_skin_effect(smooth_skin_effect)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 {
|
|
||||||
camera.set_white_balance_temperature(temperature)?;
|
|
||||||
} else {
|
|
||||||
warn!("White Balance mode is not set to 'Temperature', refusing to set temperature");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(shift_red) = &options.white_balance_shift_red {
|
|
||||||
camera.set_white_balance_shift_red(shift_red)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(shift_blue) = &options.white_balance_shift_blue {
|
|
||||||
camera.set_white_balance_shift_blue(shift_blue)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Exposure
|
|
||||||
if let Some(dynamic_range_priority) = &options.dynamic_range_priority {
|
|
||||||
camera.set_dynamic_range_priority(dynamic_range_priority)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
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()?
|
|
||||||
};
|
|
||||||
|
|
||||||
let is_drp_off = *dynamic_range_priority == FujiDynamicRangePriority::Off;
|
|
||||||
|
|
||||||
if let Some(dynamic_range) = &options.dynamic_range {
|
|
||||||
if is_drp_off {
|
|
||||||
camera.set_dynamic_range(dynamic_range)?;
|
|
||||||
} else {
|
|
||||||
warn!("Dynamic Range Priority is enabled, refusing to set dynamic range");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(highlights) = &options.highlight {
|
|
||||||
if is_drp_off {
|
|
||||||
camera.set_highlight_tone(highlights)?;
|
|
||||||
} else {
|
|
||||||
warn!("Dynamic Range Priority is enabled, refusing to set highlight tone");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(shadows) = &options.shadow {
|
|
||||||
if is_drp_off {
|
|
||||||
camera.set_shadow_tone(shadows)?;
|
|
||||||
} else {
|
|
||||||
warn!("Dynamic Range Priority is enabled, refusing to set shadow tone");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extras
|
|
||||||
if let Some(lens_modulation_optimizer) = &options.lens_modulation_optimizer {
|
|
||||||
camera.set_lens_modulation_optimizer(lens_modulation_optimizer)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(color_space) = &options.color_space {
|
|
||||||
camera.set_color_space(color_space)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user