chore: partially demystify restore header
Signed-off-by: Nikolaos Karaolidis <nick@karaolidis.com>
This commit is contained in:
@@ -15,9 +15,9 @@ use ptp::{
|
||||
FujiDynamicRangePriority, FujiFilmSimulation, FujiGrainEffect, FujiHighISONR,
|
||||
FujiHighlightTone, FujiImageQuality, FujiImageSize, FujiShadowTone, FujiSharpness,
|
||||
FujiSmoothSkinEffect, FujiWhiteBalance, FujiWhiteBalanceShift, FujiWhiteBalanceTemperature,
|
||||
UsbMode,
|
||||
ObjectFormat, UsbMode,
|
||||
},
|
||||
structs::DeviceInfo,
|
||||
structs::{DeviceInfo, ObjectInfo},
|
||||
};
|
||||
use ptp_cursor::{PtpDeserialize, PtpSerialize};
|
||||
use rusb::{GlobalContext, constants::LIBUSB_CLASS_IMAGE};
|
||||
@@ -27,25 +27,26 @@ use crate::usb::find_endpoint;
|
||||
const SESSION: u32 = 1;
|
||||
|
||||
pub struct Camera {
|
||||
r#impl: Box<dyn CameraImpl<GlobalContext>>,
|
||||
ptp: Ptp,
|
||||
pub r#impl: Box<dyn CameraImpl<GlobalContext>>,
|
||||
pub ptp: Ptp,
|
||||
}
|
||||
|
||||
macro_rules! camera_custom_settings {
|
||||
($( $name:ident : $type:ty => $code:expr ),+ $(,)?) => {
|
||||
macro_rules! camera_with_ptp {
|
||||
($($fn_name:ident => $ret:ty),* $(,)?) => {
|
||||
$(
|
||||
paste::paste! {
|
||||
#[allow(dead_code)]
|
||||
pub fn [<get_ $name>](&mut self) -> anyhow::Result<$type> {
|
||||
self.r#impl.[<get_ $name>](&mut self.ptp)
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn [<set_ $name>](&mut self, value: &$type) -> anyhow::Result<()> {
|
||||
self.r#impl.[<set_ $name>](&mut self.ptp, value)
|
||||
}
|
||||
#[allow(dead_code)]
|
||||
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),* $(,)?) => {
|
||||
$(
|
||||
#[allow(dead_code)]
|
||||
pub fn $fn_name(&mut self, $($arg: $arg_ty),*) -> anyhow::Result<$ret> {
|
||||
self.r#impl.$fn_name(&mut self.ptp, $($arg),*)
|
||||
}
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
||||
@@ -66,75 +67,67 @@ impl Camera {
|
||||
format!("{}.{}", self.ptp.bus, self.ptp.address)
|
||||
}
|
||||
|
||||
pub fn get_info(&mut self) -> anyhow::Result<DeviceInfo> {
|
||||
let info = self.r#impl.get_info(&mut self.ptp)?;
|
||||
Ok(info)
|
||||
camera_with_ptp! {
|
||||
get_info => DeviceInfo,
|
||||
get_usb_mode => UsbMode,
|
||||
get_battery_info => u32,
|
||||
}
|
||||
|
||||
pub fn get_usb_mode(&mut self) -> anyhow::Result<UsbMode> {
|
||||
let data = self
|
||||
.r#impl
|
||||
.get_prop_value(&mut self.ptp, DevicePropCode::FujiUsbMode)?;
|
||||
let result = UsbMode::try_from_ptp(&data)?;
|
||||
Ok(result)
|
||||
camera_with_ptp! {
|
||||
export_backup => Vec<u8>,
|
||||
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_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,
|
||||
}
|
||||
|
||||
pub fn get_battery_info(&mut self) -> anyhow::Result<u32> {
|
||||
let data = self
|
||||
.r#impl
|
||||
.get_prop_value(&mut self.ptp, DevicePropCode::FujiBatteryInfo2)?;
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
pub fn export_backup(&mut self) -> anyhow::Result<Vec<u8>> {
|
||||
self.r#impl.export_backup(&mut self.ptp)
|
||||
}
|
||||
|
||||
pub fn import_backup(&mut self, backup: &[u8]) -> anyhow::Result<()> {
|
||||
self.r#impl.import_backup(&mut self.ptp, backup)
|
||||
}
|
||||
|
||||
camera_custom_settings! {
|
||||
active_custom_setting: FujiCustomSetting => DevicePropCode::FujiStillCustomSetting,
|
||||
custom_setting_name: FujiCustomSettingName => DevicePropCode::FujiStillCustomSettingName,
|
||||
image_size: FujiImageSize => DevicePropCode::FujiStillCustomSettingImageSize,
|
||||
image_quality: FujiImageQuality => DevicePropCode::FujiStillCustomSettingImageQuality,
|
||||
dynamic_range: FujiDynamicRange => DevicePropCode::FujiStillCustomSettingDynamicRange,
|
||||
dynamic_range_priority: FujiDynamicRangePriority => DevicePropCode::FujiStillCustomSettingDynamicRangePriority,
|
||||
film_simulation: FujiFilmSimulation => DevicePropCode::FujiStillCustomSettingFilmSimulation,
|
||||
grain_effect: FujiGrainEffect => DevicePropCode::FujiStillCustomSettingGrainEffect,
|
||||
white_balance: FujiWhiteBalance => DevicePropCode::FujiStillCustomSettingWhiteBalance,
|
||||
high_iso_nr: FujiHighISONR => DevicePropCode::FujiStillCustomSettingHighISONR,
|
||||
highlight_tone: FujiHighlightTone => DevicePropCode::FujiStillCustomSettingHighlightTone,
|
||||
shadow_tone: FujiShadowTone => DevicePropCode::FujiStillCustomSettingShadowTone,
|
||||
color: FujiColor => DevicePropCode::FujiStillCustomSettingColor,
|
||||
sharpness: FujiSharpness => DevicePropCode::FujiStillCustomSettingSharpness,
|
||||
clarity: FujiClarity => DevicePropCode::FujiStillCustomSettingClarity,
|
||||
white_balance_shift_red: FujiWhiteBalanceShift => DevicePropCode::FujiStillCustomSettingWhiteBalanceShiftRed,
|
||||
white_balance_shift_blue: FujiWhiteBalanceShift => DevicePropCode::FujiStillCustomSettingWhiteBalanceShiftBlue,
|
||||
white_balance_temperature: FujiWhiteBalanceTemperature => DevicePropCode::FujiStillCustomSettingWhiteBalanceTemperature,
|
||||
color_chrome_effect: FujiColorChromeEffect => DevicePropCode::FujiStillCustomSettingColorChromeEffect,
|
||||
color_chrome_fx_blue: FujiColorChromeFXBlue => DevicePropCode::FujiStillCustomSettingColorChromeFXBlue,
|
||||
smooth_skin_effect: FujiSmoothSkinEffect => DevicePropCode::FujiStillCustomSettingSmoothSkinEffect,
|
||||
camera_with_ptp! {
|
||||
import_backup(buffer: &[u8]) => (),
|
||||
set_active_custom_setting(value: &FujiCustomSetting) => (),
|
||||
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_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) => (),
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Camera {
|
||||
fn drop(&mut self) {
|
||||
debug!("Closing session");
|
||||
if let Err(e) = self.r#impl.close_session(&mut self.ptp, SESSION) {
|
||||
if let Err(e) = self.ptp.close_session(SESSION, self.r#impl.timeout()) {
|
||||
error!("Error closing session: {e}");
|
||||
}
|
||||
debug!("Session closed");
|
||||
@@ -201,7 +194,7 @@ impl TryFrom<&rusb::Device<GlobalContext>> for Camera {
|
||||
};
|
||||
|
||||
debug!("Opening session");
|
||||
let () = r#impl.open_session(&mut ptp, SESSION)?;
|
||||
let () = ptp.open_session(SESSION, r#impl.timeout())?;
|
||||
debug!("Session opened");
|
||||
|
||||
return Ok(Self { r#impl, ptp });
|
||||
@@ -217,7 +210,7 @@ macro_rules! camera_impl_custom_settings {
|
||||
paste::paste! {
|
||||
#[allow(dead_code)]
|
||||
fn [<get_ $name>](&self, ptp: &mut Ptp) -> anyhow::Result<$type> {
|
||||
let bytes = self.get_prop_value(ptp, $code)?;
|
||||
let bytes = ptp.get_prop_value($code, self.timeout())?;
|
||||
let result = <$type>::try_from_ptp(&bytes)?;
|
||||
Ok(result)
|
||||
}
|
||||
@@ -225,7 +218,7 @@ macro_rules! camera_impl_custom_settings {
|
||||
#[allow(dead_code)]
|
||||
fn [<set_ $name>](&self, ptp: &mut Ptp, value: &$type) -> anyhow::Result<()> {
|
||||
let bytes = value.try_into_ptp()?;
|
||||
self.set_prop_value(ptp, $code, &bytes)?;
|
||||
ptp.set_prop_value($code, &bytes, self.timeout())?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -244,58 +237,32 @@ pub trait CameraImpl<P: rusb::UsbContext> {
|
||||
1024 * 1024
|
||||
}
|
||||
|
||||
fn open_session(&self, ptp: &mut Ptp, session_id: u32) -> anyhow::Result<()> {
|
||||
debug!("Sending OpenSession command");
|
||||
_ = ptp.send(
|
||||
CommandCode::OpenSession,
|
||||
&[session_id],
|
||||
None,
|
||||
self.timeout(),
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn close_session(&self, ptp: &mut Ptp, _: u32) -> anyhow::Result<()> {
|
||||
debug!("Sending CloseSession command");
|
||||
_ = ptp.send(CommandCode::CloseSession, &[], None, self.timeout())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_info(&self, ptp: &mut Ptp) -> anyhow::Result<DeviceInfo> {
|
||||
debug!("Sending GetDeviceInfo command");
|
||||
let response = ptp.send(CommandCode::GetDeviceInfo, &[], None, self.timeout())?;
|
||||
debug!("Received response with {} bytes", response.len());
|
||||
let info = DeviceInfo::try_from_ptp(&response)?;
|
||||
fn get_info(&mut self, ptp: &mut Ptp) -> anyhow::Result<DeviceInfo> {
|
||||
let info = ptp.get_info(self.timeout())?;
|
||||
Ok(info)
|
||||
}
|
||||
|
||||
fn get_prop_value(&self, ptp: &mut Ptp, prop: DevicePropCode) -> anyhow::Result<Vec<u8>> {
|
||||
debug!("Sending GetDevicePropValue command for property {prop:?}");
|
||||
let response = ptp.send(
|
||||
CommandCode::GetDevicePropValue,
|
||||
&[prop.into()],
|
||||
None,
|
||||
self.timeout(),
|
||||
)?;
|
||||
debug!("Received response with {} bytes", response.len());
|
||||
Ok(response)
|
||||
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 set_prop_value(
|
||||
&self,
|
||||
ptp: &mut Ptp,
|
||||
prop: DevicePropCode,
|
||||
value: &[u8],
|
||||
) -> anyhow::Result<Vec<u8>> {
|
||||
debug!("Sending GetDevicePropValue command for property {prop:?}");
|
||||
let response = ptp.send(
|
||||
CommandCode::SetDevicePropValue,
|
||||
&[prop.into()],
|
||||
Some(value),
|
||||
self.timeout(),
|
||||
)?;
|
||||
debug!("Received response with {} bytes", response.len());
|
||||
Ok(response)
|
||||
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>> {
|
||||
@@ -316,11 +283,15 @@ pub trait CameraImpl<P: rusb::UsbContext> {
|
||||
debug!("Preparing ObjectInfo header for backup");
|
||||
|
||||
let mut header = Vec::with_capacity(1076);
|
||||
0x0u32.try_write_ptp(&mut header)?;
|
||||
0x5000u16.try_write_ptp(&mut header)?;
|
||||
0x0u16.try_write_ptp(&mut header)?;
|
||||
u32::try_from(buffer.len())?.try_write_ptp(&mut header)?;
|
||||
for _ in 0..1064 {
|
||||
|
||||
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)?;
|
||||
}
|
||||
|
||||
|
@@ -27,6 +27,8 @@ pub enum CommandCode {
|
||||
SendObject = 0x100D,
|
||||
GetDevicePropValue = 0x1015,
|
||||
SetDevicePropValue = 0x1016,
|
||||
FujiSendObjectInfo = 0x900c,
|
||||
FujiSendObject = 0x900d,
|
||||
}
|
||||
|
||||
#[repr(u16)]
|
||||
@@ -133,6 +135,8 @@ impl PtpDeserialize for ContainerCode {
|
||||
)]
|
||||
pub enum DevicePropCode {
|
||||
FujiUsbMode = 0xd16e,
|
||||
FujiRawConversionRun = 0xD183,
|
||||
FujiRawConversionSettings = 0xD185,
|
||||
FujiStillCustomSetting = 0xD18C,
|
||||
FujiStillCustomSettingName = 0xD18D,
|
||||
FujiStillCustomSettingImageSize = 0xD18E,
|
||||
@@ -190,6 +194,22 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(u16)]
|
||||
#[derive(
|
||||
Debug, Clone, Copy, PartialEq, Eq, IntoPrimitive, TryFromPrimitive, PtpSerialize, PtpDeserialize,
|
||||
)]
|
||||
pub enum ObjectFormat {
|
||||
None = 0x0,
|
||||
FujiBackup = 0x5000,
|
||||
FujiRAF = 0xf802,
|
||||
}
|
||||
|
||||
impl Default for ObjectFormat {
|
||||
fn default() -> Self {
|
||||
Self::None
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(u16)]
|
||||
#[derive(
|
||||
Debug,
|
||||
@@ -927,7 +947,6 @@ impl FromStr for FujiSmoothSkinEffect {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[repr(u16)]
|
||||
#[derive(
|
||||
Debug,
|
||||
|
@@ -5,11 +5,11 @@ pub mod structs;
|
||||
use std::{cmp::min, io::Cursor, time::Duration};
|
||||
|
||||
use anyhow::bail;
|
||||
use hex::{CommandCode, ContainerCode, ContainerType, ResponseCode};
|
||||
use hex::{CommandCode, ContainerCode, ContainerType, DevicePropCode, ResponseCode};
|
||||
use log::{debug, error, trace, warn};
|
||||
use ptp_cursor::{PtpDeserialize, PtpSerialize};
|
||||
use rusb::GlobalContext;
|
||||
use structs::ContainerInfo;
|
||||
use structs::{ContainerInfo, DeviceInfo};
|
||||
|
||||
pub struct Ptp {
|
||||
pub bus: u8,
|
||||
@@ -40,6 +40,59 @@ impl Ptp {
|
||||
response
|
||||
}
|
||||
|
||||
pub fn open_session(&mut self, session_id: u32, timeout: Duration) -> anyhow::Result<()> {
|
||||
debug!("Sending OpenSession command");
|
||||
self.send(CommandCode::OpenSession, &[session_id], None, timeout)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn close_session(&mut self, _: u32, timeout: Duration) -> anyhow::Result<()> {
|
||||
debug!("Sending CloseSession command");
|
||||
self.send(CommandCode::CloseSession, &[], None, timeout)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_info(&mut self, timeout: Duration) -> anyhow::Result<DeviceInfo> {
|
||||
debug!("Sending GetDeviceInfo command");
|
||||
let response = self.send(CommandCode::GetDeviceInfo, &[], None, timeout)?;
|
||||
debug!("Received response with {} bytes", response.len());
|
||||
let info = DeviceInfo::try_from_ptp(&response)?;
|
||||
Ok(info)
|
||||
}
|
||||
|
||||
pub fn get_prop_value(
|
||||
&mut self,
|
||||
prop: DevicePropCode,
|
||||
timeout: Duration,
|
||||
) -> anyhow::Result<Vec<u8>> {
|
||||
debug!("Sending GetDevicePropValue command for property {prop:?}");
|
||||
let response = self.send(
|
||||
CommandCode::GetDevicePropValue,
|
||||
&[prop.into()],
|
||||
None,
|
||||
timeout,
|
||||
)?;
|
||||
debug!("Received response with {} bytes", response.len());
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
pub fn set_prop_value(
|
||||
&mut self,
|
||||
prop: DevicePropCode,
|
||||
value: &[u8],
|
||||
timeout: Duration,
|
||||
) -> anyhow::Result<Vec<u8>> {
|
||||
debug!("Sending GetDevicePropValue command for property {prop:?}");
|
||||
let response = self.send(
|
||||
CommandCode::SetDevicePropValue,
|
||||
&[prop.into()],
|
||||
Some(value),
|
||||
timeout,
|
||||
)?;
|
||||
debug!("Received response with {} bytes", response.len());
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
fn send_header(
|
||||
&self,
|
||||
code: CommandCode,
|
||||
|
@@ -1,6 +1,6 @@
|
||||
use ptp_macro::{PtpDeserialize, PtpSerialize};
|
||||
|
||||
use super::hex::{CommandCode, ContainerCode, ContainerType};
|
||||
use super::hex::{CommandCode, ContainerCode, ContainerType, ObjectFormat};
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug, PtpSerialize, PtpDeserialize)]
|
||||
@@ -54,3 +54,26 @@ impl ContainerInfo {
|
||||
self.total_len as usize - Self::SIZE
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, PtpSerialize, PtpDeserialize)]
|
||||
pub struct ObjectInfo {
|
||||
pub storage_id: u32,
|
||||
pub object_format: ObjectFormat,
|
||||
pub protection_status: u16,
|
||||
pub compressed_size: u32,
|
||||
pub thumb_format: u16,
|
||||
pub thumb_compressed_size: u32,
|
||||
pub thumb_width: u32,
|
||||
pub thumb_height: u32,
|
||||
pub image_width: u32,
|
||||
pub image_height: u32,
|
||||
pub image_bit_depth: u32,
|
||||
pub parent_object: u32,
|
||||
pub association_type: u16,
|
||||
pub association_desc: u32,
|
||||
pub sequence_number: u32,
|
||||
pub filename: String,
|
||||
pub date_created: String,
|
||||
pub date_modified: String,
|
||||
pub keywords: String,
|
||||
}
|
||||
|
Reference in New Issue
Block a user