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,
|
FujiDynamicRangePriority, FujiFilmSimulation, FujiGrainEffect, FujiHighISONR,
|
||||||
FujiHighlightTone, FujiImageQuality, FujiImageSize, FujiShadowTone, FujiSharpness,
|
FujiHighlightTone, FujiImageQuality, FujiImageSize, FujiShadowTone, FujiSharpness,
|
||||||
FujiSmoothSkinEffect, FujiWhiteBalance, FujiWhiteBalanceShift, FujiWhiteBalanceTemperature,
|
FujiSmoothSkinEffect, FujiWhiteBalance, FujiWhiteBalanceShift, FujiWhiteBalanceTemperature,
|
||||||
UsbMode,
|
ObjectFormat, UsbMode,
|
||||||
},
|
},
|
||||||
structs::DeviceInfo,
|
structs::{DeviceInfo, ObjectInfo},
|
||||||
};
|
};
|
||||||
use ptp_cursor::{PtpDeserialize, PtpSerialize};
|
use ptp_cursor::{PtpDeserialize, PtpSerialize};
|
||||||
use rusb::{GlobalContext, constants::LIBUSB_CLASS_IMAGE};
|
use rusb::{GlobalContext, constants::LIBUSB_CLASS_IMAGE};
|
||||||
@@ -27,25 +27,26 @@ use crate::usb::find_endpoint;
|
|||||||
const SESSION: u32 = 1;
|
const SESSION: u32 = 1;
|
||||||
|
|
||||||
pub struct Camera {
|
pub struct Camera {
|
||||||
r#impl: Box<dyn CameraImpl<GlobalContext>>,
|
pub r#impl: Box<dyn CameraImpl<GlobalContext>>,
|
||||||
ptp: Ptp,
|
pub ptp: Ptp,
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! camera_custom_settings {
|
macro_rules! camera_with_ptp {
|
||||||
($( $name:ident : $type:ty => $code:expr ),+ $(,)?) => {
|
($($fn_name:ident => $ret:ty),* $(,)?) => {
|
||||||
$(
|
$(
|
||||||
paste::paste! {
|
#[allow(dead_code)]
|
||||||
#[allow(dead_code)]
|
pub fn $fn_name(&mut self) -> anyhow::Result<$ret> {
|
||||||
pub fn [<get_ $name>](&mut self) -> anyhow::Result<$type> {
|
self.r#impl.$fn_name(&mut self.ptp)
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
)+
|
)*
|
||||||
|
};
|
||||||
|
($($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)
|
format!("{}.{}", self.ptp.bus, self.ptp.address)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_info(&mut self) -> anyhow::Result<DeviceInfo> {
|
camera_with_ptp! {
|
||||||
let info = self.r#impl.get_info(&mut self.ptp)?;
|
get_info => DeviceInfo,
|
||||||
Ok(info)
|
get_usb_mode => UsbMode,
|
||||||
|
get_battery_info => u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_usb_mode(&mut self) -> anyhow::Result<UsbMode> {
|
camera_with_ptp! {
|
||||||
let data = self
|
export_backup => Vec<u8>,
|
||||||
.r#impl
|
get_active_custom_setting => FujiCustomSetting,
|
||||||
.get_prop_value(&mut self.ptp, DevicePropCode::FujiUsbMode)?;
|
get_custom_setting_name => FujiCustomSettingName,
|
||||||
let result = UsbMode::try_from_ptp(&data)?;
|
get_image_size => FujiImageSize,
|
||||||
Ok(result)
|
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> {
|
camera_with_ptp! {
|
||||||
let data = self
|
import_backup(buffer: &[u8]) => (),
|
||||||
.r#impl
|
set_active_custom_setting(value: &FujiCustomSetting) => (),
|
||||||
.get_prop_value(&mut self.ptp, DevicePropCode::FujiBatteryInfo2)?;
|
set_custom_setting_name(value: &FujiCustomSettingName) => (),
|
||||||
|
set_image_size(value: &FujiImageSize) => (),
|
||||||
debug!("Raw battery data: {data:?}");
|
set_image_quality(value: &FujiImageQuality) => (),
|
||||||
|
set_dynamic_range(value: &FujiDynamicRange) => (),
|
||||||
let raw_string = String::try_from_ptp(&data)?;
|
set_dynamic_range_priority(value: &FujiDynamicRangePriority) => (),
|
||||||
debug!("Decoded raw string: {raw_string}");
|
set_film_simulation(value: &FujiFilmSimulation) => (),
|
||||||
|
set_grain_effect(value: &FujiGrainEffect) => (),
|
||||||
let percentage: u32 = raw_string
|
set_white_balance(value: &FujiWhiteBalance) => (),
|
||||||
.split(',')
|
set_high_iso_nr(value: &FujiHighISONR) => (),
|
||||||
.next()
|
set_highlight_tone(value: &FujiHighlightTone) => (),
|
||||||
.ok_or_else(|| anyhow!("Failed to parse battery percentage"))?
|
set_shadow_tone(value: &FujiShadowTone) => (),
|
||||||
.parse()?;
|
set_color(value: &FujiColor) => (),
|
||||||
|
set_sharpness(value: &FujiSharpness) => (),
|
||||||
Ok(percentage)
|
set_clarity(value: &FujiClarity) => (),
|
||||||
}
|
set_white_balance_shift_red(value: &FujiWhiteBalanceShift) => (),
|
||||||
|
set_white_balance_shift_blue(value: &FujiWhiteBalanceShift) => (),
|
||||||
pub fn export_backup(&mut self) -> anyhow::Result<Vec<u8>> {
|
set_white_balance_temperature(value: &FujiWhiteBalanceTemperature) => (),
|
||||||
self.r#impl.export_backup(&mut self.ptp)
|
set_color_chrome_effect(value: &FujiColorChromeEffect) => (),
|
||||||
}
|
set_color_chrome_fx_blue(value: &FujiColorChromeFXBlue) => (),
|
||||||
|
set_smooth_skin_effect(value: &FujiSmoothSkinEffect) => (),
|
||||||
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,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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.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}");
|
error!("Error closing session: {e}");
|
||||||
}
|
}
|
||||||
debug!("Session closed");
|
debug!("Session closed");
|
||||||
@@ -201,7 +194,7 @@ impl TryFrom<&rusb::Device<GlobalContext>> for Camera {
|
|||||||
};
|
};
|
||||||
|
|
||||||
debug!("Opening session");
|
debug!("Opening session");
|
||||||
let () = r#impl.open_session(&mut ptp, SESSION)?;
|
let () = ptp.open_session(SESSION, r#impl.timeout())?;
|
||||||
debug!("Session opened");
|
debug!("Session opened");
|
||||||
|
|
||||||
return Ok(Self { r#impl, ptp });
|
return Ok(Self { r#impl, ptp });
|
||||||
@@ -217,7 +210,7 @@ macro_rules! camera_impl_custom_settings {
|
|||||||
paste::paste! {
|
paste::paste! {
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
fn [<get_ $name>](&self, ptp: &mut Ptp) -> anyhow::Result<$type> {
|
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)?;
|
let result = <$type>::try_from_ptp(&bytes)?;
|
||||||
Ok(result)
|
Ok(result)
|
||||||
}
|
}
|
||||||
@@ -225,7 +218,7 @@ macro_rules! camera_impl_custom_settings {
|
|||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
fn [<set_ $name>](&self, ptp: &mut Ptp, value: &$type) -> anyhow::Result<()> {
|
fn [<set_ $name>](&self, ptp: &mut Ptp, value: &$type) -> anyhow::Result<()> {
|
||||||
let bytes = value.try_into_ptp()?;
|
let bytes = value.try_into_ptp()?;
|
||||||
self.set_prop_value(ptp, $code, &bytes)?;
|
ptp.set_prop_value($code, &bytes, self.timeout())?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -244,58 +237,32 @@ pub trait CameraImpl<P: rusb::UsbContext> {
|
|||||||
1024 * 1024
|
1024 * 1024
|
||||||
}
|
}
|
||||||
|
|
||||||
fn open_session(&self, ptp: &mut Ptp, session_id: u32) -> anyhow::Result<()> {
|
fn get_info(&mut self, ptp: &mut Ptp) -> anyhow::Result<DeviceInfo> {
|
||||||
debug!("Sending OpenSession command");
|
let info = ptp.get_info(self.timeout())?;
|
||||||
_ = 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)?;
|
|
||||||
Ok(info)
|
Ok(info)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_prop_value(&self, ptp: &mut Ptp, prop: DevicePropCode) -> anyhow::Result<Vec<u8>> {
|
fn get_usb_mode(&mut self, ptp: &mut Ptp) -> anyhow::Result<UsbMode> {
|
||||||
debug!("Sending GetDevicePropValue command for property {prop:?}");
|
let data = ptp.get_prop_value(DevicePropCode::FujiUsbMode, self.timeout())?;
|
||||||
let response = ptp.send(
|
let result = UsbMode::try_from_ptp(&data)?;
|
||||||
CommandCode::GetDevicePropValue,
|
Ok(result)
|
||||||
&[prop.into()],
|
|
||||||
None,
|
|
||||||
self.timeout(),
|
|
||||||
)?;
|
|
||||||
debug!("Received response with {} bytes", response.len());
|
|
||||||
Ok(response)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_prop_value(
|
fn get_battery_info(&mut self, ptp: &mut Ptp) -> anyhow::Result<u32> {
|
||||||
&self,
|
let data = ptp.get_prop_value(DevicePropCode::FujiBatteryInfo2, self.timeout())?;
|
||||||
ptp: &mut Ptp,
|
|
||||||
prop: DevicePropCode,
|
debug!("Raw battery data: {data:?}");
|
||||||
value: &[u8],
|
|
||||||
) -> anyhow::Result<Vec<u8>> {
|
let raw_string = String::try_from_ptp(&data)?;
|
||||||
debug!("Sending GetDevicePropValue command for property {prop:?}");
|
debug!("Decoded raw string: {raw_string}");
|
||||||
let response = ptp.send(
|
|
||||||
CommandCode::SetDevicePropValue,
|
let percentage: u32 = raw_string
|
||||||
&[prop.into()],
|
.split(',')
|
||||||
Some(value),
|
.next()
|
||||||
self.timeout(),
|
.ok_or_else(|| anyhow!("Failed to parse battery percentage"))?
|
||||||
)?;
|
.parse()?;
|
||||||
debug!("Received response with {} bytes", response.len());
|
|
||||||
Ok(response)
|
Ok(percentage)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn export_backup(&self, ptp: &mut Ptp) -> anyhow::Result<Vec<u8>> {
|
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");
|
debug!("Preparing ObjectInfo header for backup");
|
||||||
|
|
||||||
let mut header = Vec::with_capacity(1076);
|
let mut header = Vec::with_capacity(1076);
|
||||||
0x0u32.try_write_ptp(&mut header)?;
|
|
||||||
0x5000u16.try_write_ptp(&mut header)?;
|
let object_info = ObjectInfo {
|
||||||
0x0u16.try_write_ptp(&mut header)?;
|
object_format: ObjectFormat::FujiBackup,
|
||||||
u32::try_from(buffer.len())?.try_write_ptp(&mut header)?;
|
compressed_size: u32::try_from(buffer.len())?,
|
||||||
for _ in 0..1064 {
|
..Default::default()
|
||||||
|
};
|
||||||
|
object_info.try_write_ptp(&mut header)?;
|
||||||
|
// TODO: What is this?
|
||||||
|
for _ in 0..1020 {
|
||||||
0x0u8.try_write_ptp(&mut header)?;
|
0x0u8.try_write_ptp(&mut header)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -27,6 +27,8 @@ pub enum CommandCode {
|
|||||||
SendObject = 0x100D,
|
SendObject = 0x100D,
|
||||||
GetDevicePropValue = 0x1015,
|
GetDevicePropValue = 0x1015,
|
||||||
SetDevicePropValue = 0x1016,
|
SetDevicePropValue = 0x1016,
|
||||||
|
FujiSendObjectInfo = 0x900c,
|
||||||
|
FujiSendObject = 0x900d,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[repr(u16)]
|
#[repr(u16)]
|
||||||
@@ -133,6 +135,8 @@ impl PtpDeserialize for ContainerCode {
|
|||||||
)]
|
)]
|
||||||
pub enum DevicePropCode {
|
pub enum DevicePropCode {
|
||||||
FujiUsbMode = 0xd16e,
|
FujiUsbMode = 0xd16e,
|
||||||
|
FujiRawConversionRun = 0xD183,
|
||||||
|
FujiRawConversionSettings = 0xD185,
|
||||||
FujiStillCustomSetting = 0xD18C,
|
FujiStillCustomSetting = 0xD18C,
|
||||||
FujiStillCustomSettingName = 0xD18D,
|
FujiStillCustomSettingName = 0xD18D,
|
||||||
FujiStillCustomSettingImageSize = 0xD18E,
|
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)]
|
#[repr(u16)]
|
||||||
#[derive(
|
#[derive(
|
||||||
Debug,
|
Debug,
|
||||||
@@ -927,7 +947,6 @@ impl FromStr for FujiSmoothSkinEffect {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[repr(u16)]
|
#[repr(u16)]
|
||||||
#[derive(
|
#[derive(
|
||||||
Debug,
|
Debug,
|
||||||
|
@@ -5,11 +5,11 @@ pub mod structs;
|
|||||||
use std::{cmp::min, io::Cursor, time::Duration};
|
use std::{cmp::min, io::Cursor, time::Duration};
|
||||||
|
|
||||||
use anyhow::bail;
|
use anyhow::bail;
|
||||||
use hex::{CommandCode, ContainerCode, ContainerType, ResponseCode};
|
use hex::{CommandCode, ContainerCode, ContainerType, DevicePropCode, ResponseCode};
|
||||||
use log::{debug, error, trace, warn};
|
use log::{debug, error, trace, warn};
|
||||||
use ptp_cursor::{PtpDeserialize, PtpSerialize};
|
use ptp_cursor::{PtpDeserialize, PtpSerialize};
|
||||||
use rusb::GlobalContext;
|
use rusb::GlobalContext;
|
||||||
use structs::ContainerInfo;
|
use structs::{ContainerInfo, DeviceInfo};
|
||||||
|
|
||||||
pub struct Ptp {
|
pub struct Ptp {
|
||||||
pub bus: u8,
|
pub bus: u8,
|
||||||
@@ -40,6 +40,59 @@ impl Ptp {
|
|||||||
response
|
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(
|
fn send_header(
|
||||||
&self,
|
&self,
|
||||||
code: CommandCode,
|
code: CommandCode,
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
use ptp_macro::{PtpDeserialize, PtpSerialize};
|
use ptp_macro::{PtpDeserialize, PtpSerialize};
|
||||||
|
|
||||||
use super::hex::{CommandCode, ContainerCode, ContainerType};
|
use super::hex::{CommandCode, ContainerCode, ContainerType, ObjectFormat};
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
#[derive(Debug, PtpSerialize, PtpDeserialize)]
|
#[derive(Debug, PtpSerialize, PtpDeserialize)]
|
||||||
@@ -54,3 +54,26 @@ impl ContainerInfo {
|
|||||||
self.total_len as usize - Self::SIZE
|
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