feat: custom option getter
Signed-off-by: Nikolaos Karaolidis <nick@karaolidis.com>
This commit is contained in:
115
Cargo.lock
generated
115
Cargo.lock
generated
@@ -241,9 +241,15 @@ dependencies = [
|
|||||||
"clap",
|
"clap",
|
||||||
"log",
|
"log",
|
||||||
"log4rs",
|
"log4rs",
|
||||||
|
"num_enum",
|
||||||
|
"ptp_cursor",
|
||||||
|
"ptp_macro",
|
||||||
"rusb",
|
"rusb",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
"strsim",
|
||||||
|
"strum",
|
||||||
|
"strum_macros",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -430,6 +436,28 @@ dependencies = [
|
|||||||
"autocfg",
|
"autocfg",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num_enum"
|
||||||
|
version = "0.7.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a973b4e44ce6cad84ce69d797acf9a044532e4184c4f267913d1b546a0727b7a"
|
||||||
|
dependencies = [
|
||||||
|
"num_enum_derive",
|
||||||
|
"rustversion",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num_enum_derive"
|
||||||
|
version = "0.7.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "77e878c846a8abae00dd069496dbe8751b16ac1c3d6bd2a7283a938e8228f90d"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro-crate",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "once_cell"
|
name = "once_cell"
|
||||||
version = "1.21.3"
|
version = "1.21.3"
|
||||||
@@ -489,6 +517,15 @@ dependencies = [
|
|||||||
"zerocopy",
|
"zerocopy",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro-crate"
|
||||||
|
version = "3.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983"
|
||||||
|
dependencies = [
|
||||||
|
"toml_edit",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.101"
|
version = "1.0.101"
|
||||||
@@ -498,6 +535,24 @@ dependencies = [
|
|||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ptp_cursor"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"byteorder",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ptp_macro"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"byteorder",
|
||||||
|
"proc-macro2",
|
||||||
|
"ptp_cursor",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quote"
|
name = "quote"
|
||||||
version = "1.0.41"
|
version = "1.0.41"
|
||||||
@@ -676,6 +731,27 @@ version = "0.11.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "strum"
|
||||||
|
version = "0.27.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf"
|
||||||
|
dependencies = [
|
||||||
|
"strum_macros",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "strum_macros"
|
||||||
|
version = "0.27.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7"
|
||||||
|
dependencies = [
|
||||||
|
"heck",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "2.0.106"
|
version = "2.0.106"
|
||||||
@@ -727,6 +803,36 @@ dependencies = [
|
|||||||
"windows-sys 0.59.0",
|
"windows-sys 0.59.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "toml_datetime"
|
||||||
|
version = "0.7.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f2cdb639ebbc97961c51720f858597f7f24c4fc295327923af55b74c3c724533"
|
||||||
|
dependencies = [
|
||||||
|
"serde_core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "toml_edit"
|
||||||
|
version = "0.23.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6485ef6d0d9b5d0ec17244ff7eb05310113c3f316f2d14200d4de56b3cb98f8d"
|
||||||
|
dependencies = [
|
||||||
|
"indexmap",
|
||||||
|
"toml_datetime",
|
||||||
|
"toml_parser",
|
||||||
|
"winnow",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "toml_parser"
|
||||||
|
version = "1.0.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e"
|
||||||
|
dependencies = [
|
||||||
|
"winnow",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "typemap-ors"
|
name = "typemap-ors"
|
||||||
version = "1.0.0"
|
version = "1.0.0"
|
||||||
@@ -1095,6 +1201,15 @@ version = "0.53.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650"
|
checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winnow"
|
||||||
|
version = "0.7.13"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wit-bindgen"
|
name = "wit-bindgen"
|
||||||
version = "0.46.0"
|
version = "0.46.0"
|
||||||
|
@@ -19,6 +19,12 @@ byteorder = "1.5.0"
|
|||||||
clap = { version = "4.5.48", features = ["derive", "wrap_help"] }
|
clap = { version = "4.5.48", features = ["derive", "wrap_help"] }
|
||||||
log = "0.4.28"
|
log = "0.4.28"
|
||||||
log4rs = "1.4.0"
|
log4rs = "1.4.0"
|
||||||
|
num_enum = "0.7.4"
|
||||||
rusb = "0.9.4"
|
rusb = "0.9.4"
|
||||||
serde = { version = "1.0.228", features = ["derive"] }
|
serde = { version = "1.0.228", features = ["derive"] }
|
||||||
serde_json = "1.0.145"
|
serde_json = "1.0.145"
|
||||||
|
strsim = "0.11.1"
|
||||||
|
ptp_macro = { path = "crates/ptp/macro" }
|
||||||
|
ptp_cursor = { path = "crates/ptp/cursor" }
|
||||||
|
strum = { version = "0.27.2", features = ["strum_macros"] }
|
||||||
|
strum_macros = "0.27.2"
|
||||||
|
16
crates/ptp/cursor/Cargo.lock
generated
Normal file
16
crates/ptp/cursor/Cargo.lock
generated
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
version = 4
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "byteorder"
|
||||||
|
version = "1.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ptp_cursor"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"byteorder",
|
||||||
|
]
|
7
crates/ptp/cursor/Cargo.toml
Normal file
7
crates/ptp/cursor/Cargo.toml
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
[package]
|
||||||
|
name = "ptp_cursor"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
byteorder = { version = "1.5.0" }
|
264
crates/ptp/cursor/src/lib.rs
Normal file
264
crates/ptp/cursor/src/lib.rs
Normal file
@@ -0,0 +1,264 @@
|
|||||||
|
#![allow(dead_code)]
|
||||||
|
#![allow(clippy::redundant_closure_for_method_calls)]
|
||||||
|
|
||||||
|
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
|
||||||
|
use std::io::{self, Cursor};
|
||||||
|
|
||||||
|
pub trait Read: ReadBytesExt {
|
||||||
|
fn read_ptp_u8(&mut self) -> io::Result<u8> {
|
||||||
|
self.read_u8()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_ptp_i8(&mut self) -> io::Result<i8> {
|
||||||
|
self.read_i8()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_ptp_u16(&mut self) -> io::Result<u16> {
|
||||||
|
self.read_u16::<LittleEndian>()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_ptp_i16(&mut self) -> io::Result<i16> {
|
||||||
|
self.read_i16::<LittleEndian>()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_ptp_u32(&mut self) -> io::Result<u32> {
|
||||||
|
self.read_u32::<LittleEndian>()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_ptp_i32(&mut self) -> io::Result<i32> {
|
||||||
|
self.read_i32::<LittleEndian>()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_ptp_u64(&mut self) -> io::Result<u64> {
|
||||||
|
self.read_u64::<LittleEndian>()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_ptp_i64(&mut self) -> io::Result<i64> {
|
||||||
|
self.read_i64::<LittleEndian>()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_ptp_vec<T, F>(&mut self, func: F) -> io::Result<Vec<T>>
|
||||||
|
where
|
||||||
|
F: Fn(&mut Self) -> io::Result<T>,
|
||||||
|
{
|
||||||
|
let len = self.read_u32::<LittleEndian>()? as usize;
|
||||||
|
(0..len).map(|_| func(self)).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_ptp_u8_vec(&mut self) -> io::Result<Vec<u8>> {
|
||||||
|
self.read_ptp_vec(|cur| cur.read_ptp_u8())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_ptp_i8_vec(&mut self) -> io::Result<Vec<i8>> {
|
||||||
|
self.read_ptp_vec(|cur| cur.read_ptp_i8())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_ptp_u16_vec(&mut self) -> io::Result<Vec<u16>> {
|
||||||
|
self.read_ptp_vec(|cur| cur.read_ptp_u16())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_ptp_i16_vec(&mut self) -> io::Result<Vec<i16>> {
|
||||||
|
self.read_ptp_vec(|cur| cur.read_ptp_i16())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_ptp_u32_vec(&mut self) -> io::Result<Vec<u32>> {
|
||||||
|
self.read_ptp_vec(|cur| cur.read_ptp_u32())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_ptp_i32_vec(&mut self) -> io::Result<Vec<i32>> {
|
||||||
|
self.read_ptp_vec(|cur| cur.read_ptp_i32())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_ptp_u64_vec(&mut self) -> io::Result<Vec<u64>> {
|
||||||
|
self.read_ptp_vec(|cur| cur.read_ptp_u64())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_ptp_i64_vec(&mut self) -> io::Result<Vec<i64>> {
|
||||||
|
self.read_ptp_vec(|cur| cur.read_ptp_i64())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_ptp_str(&mut self) -> io::Result<String> {
|
||||||
|
let len = self.read_u8()?;
|
||||||
|
if len == 0 {
|
||||||
|
return Ok(String::new());
|
||||||
|
}
|
||||||
|
|
||||||
|
let data: Vec<u16> = (0..(len - 1))
|
||||||
|
.map(|_| self.read_u16::<LittleEndian>())
|
||||||
|
.collect::<io::Result<_>>()?;
|
||||||
|
self.read_u16::<LittleEndian>()?;
|
||||||
|
String::from_utf16(&data)
|
||||||
|
.map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "Invalid UTF-16"))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expect_end(&mut self) -> io::Result<()>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: AsRef<[u8]>> Read for Cursor<T> {
|
||||||
|
fn expect_end(&mut self) -> io::Result<()> {
|
||||||
|
let len = self.get_ref().as_ref().len();
|
||||||
|
if len as u64 != self.position() {
|
||||||
|
return Err(io::Error::new(
|
||||||
|
io::ErrorKind::UnexpectedEof,
|
||||||
|
format!(
|
||||||
|
"Buffer contained {} bytes, expected {} bytes",
|
||||||
|
len,
|
||||||
|
self.position()
|
||||||
|
),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait Write: WriteBytesExt {
|
||||||
|
fn write_ptp_u8(&mut self, v: &u8) -> io::Result<()> {
|
||||||
|
self.write_u8(*v)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_ptp_i8(&mut self, v: &i8) -> io::Result<()> {
|
||||||
|
self.write_i8(*v)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_ptp_u16(&mut self, v: &u16) -> io::Result<()> {
|
||||||
|
self.write_u16::<LittleEndian>(*v)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_ptp_i16(&mut self, v: &i16) -> io::Result<()> {
|
||||||
|
self.write_i16::<LittleEndian>(*v)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_ptp_u32(&mut self, v: &u32) -> io::Result<()> {
|
||||||
|
self.write_u32::<LittleEndian>(*v)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_ptp_i32(&mut self, v: &i32) -> io::Result<()> {
|
||||||
|
self.write_i32::<LittleEndian>(*v)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_ptp_u64(&mut self, v: &u64) -> io::Result<()> {
|
||||||
|
self.write_u64::<LittleEndian>(*v)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_ptp_i64(&mut self, v: &i64) -> io::Result<()> {
|
||||||
|
self.write_i64::<LittleEndian>(*v)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_ptp_vec<T, F>(&mut self, vec: &[T], func: F) -> io::Result<()>
|
||||||
|
where
|
||||||
|
F: Fn(&mut Self, &T) -> io::Result<()>,
|
||||||
|
{
|
||||||
|
self.write_u32::<LittleEndian>(vec.len() as u32)?;
|
||||||
|
for v in vec {
|
||||||
|
func(self, v)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_ptp_u8_vec(&mut self, vec: &[u8]) -> io::Result<()> {
|
||||||
|
self.write_ptp_vec(vec, |cur, v| cur.write_ptp_u8(v))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_ptp_i8_vec(&mut self, vec: &[i8]) -> io::Result<()> {
|
||||||
|
self.write_ptp_vec(vec, |cur, v| cur.write_ptp_i8(v))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_ptp_u16_vec(&mut self, vec: &[u16]) -> io::Result<()> {
|
||||||
|
self.write_ptp_vec(vec, |cur, v| cur.write_ptp_u16(v))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_ptp_i16_vec(&mut self, vec: &[i16]) -> io::Result<()> {
|
||||||
|
self.write_ptp_vec(vec, |cur, v| cur.write_ptp_i16(v))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_ptp_u32_vec(&mut self, vec: &[u32]) -> io::Result<()> {
|
||||||
|
self.write_ptp_vec(vec, |cur, v| cur.write_ptp_u32(v))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_ptp_i32_vec(&mut self, vec: &[i32]) -> io::Result<()> {
|
||||||
|
self.write_ptp_vec(vec, |cur, v| cur.write_ptp_i32(v))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_ptp_u64_vec(&mut self, vec: &[u64]) -> io::Result<()> {
|
||||||
|
self.write_ptp_vec(vec, |cur, v| cur.write_ptp_u64(v))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_ptp_i64_vec(&mut self, vec: &[i64]) -> io::Result<()> {
|
||||||
|
self.write_ptp_vec(vec, |cur, v| cur.write_ptp_i64(v))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_ptp_str(&mut self, s: &str) -> io::Result<()> {
|
||||||
|
if s.is_empty() {
|
||||||
|
return self.write_u8(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
let utf16: Vec<u16> = s.encode_utf16().collect();
|
||||||
|
self.write_u8((utf16.len() + 1) as u8)?;
|
||||||
|
for c in utf16 {
|
||||||
|
self.write_u16::<LittleEndian>(c)?;
|
||||||
|
}
|
||||||
|
self.write_u16::<LittleEndian>(0)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Write for Vec<u8> {}
|
||||||
|
|
||||||
|
pub trait PtpSerialize: Sized {
|
||||||
|
fn try_into_ptp(&self) -> io::Result<Vec<u8>>;
|
||||||
|
|
||||||
|
fn try_write_ptp(&self, buf: &mut Vec<u8>) -> io::Result<()>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait PtpDeserialize: Sized {
|
||||||
|
fn try_from_ptp(buf: &[u8]) -> io::Result<Self>;
|
||||||
|
|
||||||
|
fn try_read_ptp<R: Read>(cur: &mut R) -> io::Result<Self>;
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! impl_ptp {
|
||||||
|
($ty:ty, $read_fn:ident, $write_fn:ident) => {
|
||||||
|
impl PtpSerialize for $ty {
|
||||||
|
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<()> {
|
||||||
|
buf.$write_fn(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PtpDeserialize for $ty {
|
||||||
|
fn try_from_ptp(buf: &[u8]) -> io::Result<Self> {
|
||||||
|
let mut cur = Cursor::new(buf);
|
||||||
|
let val = Self::try_read_ptp(&mut cur)?;
|
||||||
|
cur.expect_end()?;
|
||||||
|
Ok(val)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn try_read_ptp<R: Read>(cur: &mut R) -> io::Result<Self> {
|
||||||
|
cur.$read_fn()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
impl_ptp!(u8, read_ptp_u8, write_ptp_u8);
|
||||||
|
impl_ptp!(i8, read_ptp_i8, write_ptp_i8);
|
||||||
|
impl_ptp!(u16, read_ptp_u16, write_ptp_u16);
|
||||||
|
impl_ptp!(i16, read_ptp_i16, write_ptp_i16);
|
||||||
|
impl_ptp!(u32, read_ptp_u32, write_ptp_u32);
|
||||||
|
impl_ptp!(i32, read_ptp_i32, write_ptp_i32);
|
||||||
|
impl_ptp!(u64, read_ptp_u64, write_ptp_u64);
|
||||||
|
impl_ptp!(i64, read_ptp_i64, write_ptp_i64);
|
||||||
|
impl_ptp!(String, read_ptp_str, write_ptp_str);
|
||||||
|
impl_ptp!(Vec<u8>, read_ptp_u8_vec, write_ptp_u8_vec);
|
||||||
|
impl_ptp!(Vec<i8>, read_ptp_i8_vec, write_ptp_i8_vec);
|
||||||
|
impl_ptp!(Vec<u16>, read_ptp_u16_vec, write_ptp_u16_vec);
|
||||||
|
impl_ptp!(Vec<i16>, read_ptp_i16_vec, write_ptp_i16_vec);
|
||||||
|
impl_ptp!(Vec<u32>, read_ptp_u32_vec, write_ptp_u32_vec);
|
||||||
|
impl_ptp!(Vec<i32>, read_ptp_i32_vec, write_ptp_i32_vec);
|
||||||
|
impl_ptp!(Vec<u64>, read_ptp_u64_vec, write_ptp_u64_vec);
|
||||||
|
impl_ptp!(Vec<i64>, read_ptp_i64_vec, write_ptp_i64_vec);
|
62
crates/ptp/macro/Cargo.lock
generated
Normal file
62
crates/ptp/macro/Cargo.lock
generated
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
version = 4
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "byteorder"
|
||||||
|
version = "1.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro2"
|
||||||
|
version = "1.0.101"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ptp_cursor"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"byteorder",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ptp_macro"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"byteorder",
|
||||||
|
"proc-macro2",
|
||||||
|
"ptp_cursor",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quote"
|
||||||
|
version = "1.0.41"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "syn"
|
||||||
|
version = "2.0.106"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-ident"
|
||||||
|
version = "1.0.19"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d"
|
14
crates/ptp/macro/Cargo.toml
Normal file
14
crates/ptp/macro/Cargo.toml
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
[package]
|
||||||
|
name = "ptp_macro"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
proc-macro = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
byteorder = "1.5.0"
|
||||||
|
proc-macro2 = "1.0.101"
|
||||||
|
quote = "1.0.41"
|
||||||
|
syn = { version = "2.0.106", features = ["full"] }
|
||||||
|
ptp_cursor = { path = "../cursor" }
|
253
crates/ptp/macro/src/lib.rs
Normal file
253
crates/ptp/macro/src/lib.rs
Normal file
@@ -0,0 +1,253 @@
|
|||||||
|
use proc_macro::TokenStream;
|
||||||
|
use quote::quote;
|
||||||
|
use syn::{Data, DeriveInput, Expr, Fields, parse_macro_input, punctuated::Punctuated};
|
||||||
|
|
||||||
|
#[proc_macro_derive(PtpSerialize)]
|
||||||
|
pub fn derive_ptp_serialize(input: TokenStream) -> TokenStream {
|
||||||
|
let input = parse_macro_input!(input as DeriveInput);
|
||||||
|
let name = &input.ident;
|
||||||
|
|
||||||
|
let expanded = match &input.data {
|
||||||
|
Data::Struct(s) => match &s.fields {
|
||||||
|
Fields::Named(named) => {
|
||||||
|
let fields = &named.named;
|
||||||
|
|
||||||
|
let write_fields = fields.iter().map(|f| {
|
||||||
|
let name = &f.ident;
|
||||||
|
quote! {
|
||||||
|
self.#name.try_write_ptp(buf)?;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
impl ptp_cursor::PtpSerialize for #name {
|
||||||
|
fn try_into_ptp(&self) -> std::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>) -> std::io::Result<()> {
|
||||||
|
#(#write_fields)*
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Fields::Unnamed(unnamed) => {
|
||||||
|
let fields = &unnamed.unnamed;
|
||||||
|
|
||||||
|
let write_fields = (0..fields.len()).map(|i| {
|
||||||
|
let idx = syn::Index::from(i);
|
||||||
|
quote! { self.#idx.try_write_ptp(buf)?; }
|
||||||
|
});
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
impl ptp_cursor::PtpSerialize for #name {
|
||||||
|
fn try_into_ptp(&self) -> std::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>) -> std::io::Result<()> {
|
||||||
|
#(#write_fields)*
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Fields::Unit => {
|
||||||
|
quote! {
|
||||||
|
impl ptp_cursor::PtpSerialize for #name {
|
||||||
|
fn try_into_ptp(&self) -> std::io::Result<Vec<u8>> {
|
||||||
|
Ok(Vec::new())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn try_write_ptp(&self, _buf: &mut Vec<u8>) -> std::io::Result<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Data::Enum(_) => {
|
||||||
|
let repr_ty = input
|
||||||
|
.attrs
|
||||||
|
.iter()
|
||||||
|
.find_map(|attr| {
|
||||||
|
if attr.path().is_ident("repr") {
|
||||||
|
attr.parse_args_with(
|
||||||
|
Punctuated::<Expr, syn::Token![,]>::parse_separated_nonempty,
|
||||||
|
)
|
||||||
|
.ok()
|
||||||
|
.and_then(|args| args.into_iter().next())
|
||||||
|
.and_then(|expr| match expr {
|
||||||
|
Expr::Path(path) => path.path.get_ident().cloned(),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.expect("Enums must have a #[repr(T)] attribute for PtpSerialize");
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
impl ptp_cursor::PtpSerialize for #name
|
||||||
|
where
|
||||||
|
#name: Clone + Copy + TryFrom<#repr_ty> + Into<#repr_ty>
|
||||||
|
{
|
||||||
|
fn try_into_ptp(&self) -> std::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>) -> std::io::Result<()> {
|
||||||
|
let discriminant: #repr_ty = (*self).into();
|
||||||
|
discriminant.try_write_ptp(buf)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
unimplemented!("PtpSerialize cannot be automatically derived for unions")
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
expanded.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[proc_macro_derive(PtpDeserialize)]
|
||||||
|
pub fn derive_ptp_deserialize(input: TokenStream) -> TokenStream {
|
||||||
|
let input = parse_macro_input!(input as DeriveInput);
|
||||||
|
let name = &input.ident;
|
||||||
|
|
||||||
|
let expanded = match &input.data {
|
||||||
|
Data::Struct(s) => match &s.fields {
|
||||||
|
Fields::Named(named) => {
|
||||||
|
let fields = &named.named;
|
||||||
|
|
||||||
|
let read_fields = fields.iter().map(|f| {
|
||||||
|
let name = &f.ident;
|
||||||
|
let ty = &f.ty;
|
||||||
|
quote! {
|
||||||
|
#name: <#ty>::try_read_ptp(cur)?
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
impl ptp_cursor::PtpDeserialize for #name {
|
||||||
|
fn try_from_ptp(buf: &[u8]) -> std::io::Result<Self> {
|
||||||
|
use ptp_cursor::Read;
|
||||||
|
|
||||||
|
let mut cur = std::io::Cursor::new(buf);
|
||||||
|
let val = Self::try_read_ptp(&mut cur)?;
|
||||||
|
cur.expect_end()?;
|
||||||
|
Ok(val)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn try_read_ptp<R: ptp_cursor::Read>(cur: &mut R) -> std::io::Result<Self> {
|
||||||
|
Ok(Self { #(#read_fields),* })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Fields::Unnamed(unnamed) => {
|
||||||
|
let fields = &unnamed.unnamed;
|
||||||
|
|
||||||
|
let read_fields = fields.iter().map(|f| {
|
||||||
|
let ty = &f.ty;
|
||||||
|
quote! { <#ty>::try_read_ptp(cur)? }
|
||||||
|
});
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
impl ptp_cursor::PtpDeserialize for #name {
|
||||||
|
fn try_from_ptp(buf: &[u8]) -> std::io::Result<Self> {
|
||||||
|
use ptp_cursor::Read;
|
||||||
|
|
||||||
|
let mut cur = std::io::Cursor::new(buf);
|
||||||
|
let val = Self::try_read_ptp(&mut cur)?;
|
||||||
|
cur.expect_end()?;
|
||||||
|
Ok(val)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn try_read_ptp<R: ptp_cursor::Read>(cur: &mut R) -> std::io::Result<Self> {
|
||||||
|
Ok(Self(#(#read_fields),*))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Fields::Unit => {
|
||||||
|
quote! {
|
||||||
|
impl ptp_cursor::PtpDeserialize for #name {
|
||||||
|
fn try_from_ptp(buf: &[u8]) -> std::io::Result<Self> {
|
||||||
|
use ptp_cursor::Read;
|
||||||
|
|
||||||
|
let mut cur = std::io::Cursor::new(buf);
|
||||||
|
cur.expect_end()?;
|
||||||
|
Ok(Self)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn try_read_ptp<R: ptp_cursor::Read>(_cur: &mut R) -> std::io::Result<Self> {
|
||||||
|
Ok(Self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Data::Enum(_) => {
|
||||||
|
let repr_ty = input
|
||||||
|
.attrs
|
||||||
|
.iter()
|
||||||
|
.find_map(|attr| {
|
||||||
|
if attr.path().is_ident("repr") {
|
||||||
|
attr.parse_args_with(
|
||||||
|
Punctuated::<Expr, syn::Token![,]>::parse_separated_nonempty,
|
||||||
|
)
|
||||||
|
.ok()
|
||||||
|
.and_then(|args| args.into_iter().next())
|
||||||
|
.and_then(|expr| match expr {
|
||||||
|
Expr::Path(path) => path.path.get_ident().cloned(),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.expect("Enums must have a #[repr(T)] attribute for PtpDeserialize");
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
impl ptp_cursor::PtpDeserialize for #name
|
||||||
|
where
|
||||||
|
#name: Clone + Copy + TryFrom<#repr_ty> + Into<#repr_ty>
|
||||||
|
{
|
||||||
|
fn try_from_ptp(buf: &[u8]) -> std::io::Result<Self> {
|
||||||
|
use ptp_cursor::Read;
|
||||||
|
|
||||||
|
let mut cur = std::io::Cursor::new(buf);
|
||||||
|
let val = Self::try_read_ptp(&mut cur)?;
|
||||||
|
cur.expect_end()?;
|
||||||
|
Ok(val)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn try_read_ptp<R: ptp_cursor::Read>(cur: &mut R) -> std::io::Result<Self> {
|
||||||
|
let discriminant = <#repr_ty>::try_read_ptp(cur)?;
|
||||||
|
discriminant.try_into().map_err(|_| {
|
||||||
|
std::io::Error::new(
|
||||||
|
std::io::ErrorKind::InvalidData,
|
||||||
|
format!("Invalid discriminant for {}: {:?}", stringify!(#name), discriminant)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
unimplemented!("PtpDeserialize cannot be automatically derived for unions")
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
expanded.into()
|
||||||
|
}
|
@@ -59,6 +59,7 @@
|
|||||||
clippy
|
clippy
|
||||||
cargo-udeps
|
cargo-udeps
|
||||||
cargo-outdated
|
cargo-outdated
|
||||||
|
cargo-expand
|
||||||
];
|
];
|
||||||
|
|
||||||
shellHook = ''
|
shellHook = ''
|
||||||
|
@@ -2,17 +2,23 @@ pub mod devices;
|
|||||||
pub mod error;
|
pub mod error;
|
||||||
pub mod ptp;
|
pub mod ptp;
|
||||||
|
|
||||||
use std::{io::Cursor, time::Duration};
|
use std::time::Duration;
|
||||||
|
|
||||||
use anyhow::{anyhow, bail};
|
use anyhow::{anyhow, bail};
|
||||||
use byteorder::{LittleEndian, WriteBytesExt};
|
|
||||||
use devices::SupportedCamera;
|
use devices::SupportedCamera;
|
||||||
use log::{debug, error};
|
use log::{debug, error};
|
||||||
use ptp::{
|
use ptp::{
|
||||||
Ptp,
|
Ptp,
|
||||||
enums::{CommandCode, PropCode, UsbMode},
|
hex::{
|
||||||
|
CommandCode, DevicePropCode, FujiClarity, FujiColor, FujiColorChromeEffect,
|
||||||
|
FujiColorChromeFXBlue, FujiCustomSetting, FujiDynamicRange, FujiFilmSimulation,
|
||||||
|
FujiGrainEffect, FujiHighISONR, FujiHighlightTone, FujiImageQuality, FujiImageSize,
|
||||||
|
FujiShadowTone, FujiSharpness, FujiStillDynamicRangePriority, FujiWhiteBalance,
|
||||||
|
FujiWhiteBalanceShift, FujiWhiteBalanceTemperature, UsbMode,
|
||||||
|
},
|
||||||
structs::DeviceInfo,
|
structs::DeviceInfo,
|
||||||
};
|
};
|
||||||
|
use ptp_cursor::{PtpDeserialize, PtpSerialize};
|
||||||
use rusb::{GlobalContext, constants::LIBUSB_CLASS_IMAGE};
|
use rusb::{GlobalContext, constants::LIBUSB_CLASS_IMAGE};
|
||||||
|
|
||||||
const SESSION: u32 = 1;
|
const SESSION: u32 = 1;
|
||||||
@@ -108,17 +114,6 @@ impl Camera {
|
|||||||
format!("{}.{}", self.ptp.bus, self.ptp.address)
|
format!("{}.{}", self.ptp.bus, self.ptp.address)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn prop_value_as_scalar(data: &[u8]) -> anyhow::Result<u32> {
|
|
||||||
let data = match data.len() {
|
|
||||||
1 => anyhow::Ok(u32::from(data[0])),
|
|
||||||
2 => anyhow::Ok(u32::from(u16::from_le_bytes([data[0], data[1]]))),
|
|
||||||
4 => anyhow::Ok(u32::from_le_bytes([data[0], data[1], data[2], data[3]])),
|
|
||||||
n => bail!("Cannot parse {n} bytes as scalar"),
|
|
||||||
}?;
|
|
||||||
|
|
||||||
Ok(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_info(&mut self) -> anyhow::Result<DeviceInfo> {
|
pub fn get_info(&mut self) -> anyhow::Result<DeviceInfo> {
|
||||||
let info = self.r#impl.get_info(&mut self.ptp)?;
|
let info = self.r#impl.get_info(&mut self.ptp)?;
|
||||||
Ok(info)
|
Ok(info)
|
||||||
@@ -127,30 +122,22 @@ impl Camera {
|
|||||||
pub fn get_usb_mode(&mut self) -> anyhow::Result<UsbMode> {
|
pub fn get_usb_mode(&mut self) -> anyhow::Result<UsbMode> {
|
||||||
let data = self
|
let data = self
|
||||||
.r#impl
|
.r#impl
|
||||||
.get_prop_value(&mut self.ptp, PropCode::FujiUsbMode);
|
.get_prop_value(&mut self.ptp, DevicePropCode::FujiUsbMode)?;
|
||||||
|
let result = UsbMode::try_from_ptp(&data)?;
|
||||||
let result = Self::prop_value_as_scalar(&data?)?.into();
|
|
||||||
Ok(result)
|
Ok(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_battery_info(&mut self) -> anyhow::Result<u32> {
|
pub fn get_battery_info(&mut self) -> anyhow::Result<u32> {
|
||||||
let data = self
|
let data = self
|
||||||
.r#impl
|
.r#impl
|
||||||
.get_prop_value(&mut self.ptp, PropCode::FujiBatteryInfo2);
|
.get_prop_value(&mut self.ptp, DevicePropCode::FujiBatteryInfo2)?;
|
||||||
|
|
||||||
let data = data?;
|
|
||||||
debug!("Raw battery data: {data:?}");
|
debug!("Raw battery data: {data:?}");
|
||||||
|
|
||||||
let utf16: Vec<u16> = data[1..]
|
let raw_string = String::try_from_ptp(&data)?;
|
||||||
.chunks(2)
|
debug!("Decoded raw string: {raw_string}");
|
||||||
.map(|chunk| u16::from_le_bytes([chunk[0], chunk[1]]))
|
|
||||||
.take_while(|&c| c != 0)
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let utf8_string = String::from_utf16(&utf16)?;
|
let percentage: u32 = raw_string
|
||||||
debug!("Decoded UTF-16 string: {utf8_string}");
|
|
||||||
|
|
||||||
let percentage: u32 = utf8_string
|
|
||||||
.split(',')
|
.split(',')
|
||||||
.next()
|
.next()
|
||||||
.ok_or_else(|| anyhow!("Failed to parse battery percentage"))?
|
.ok_or_else(|| anyhow!("Failed to parse battery percentage"))?
|
||||||
@@ -166,6 +153,86 @@ impl Camera {
|
|||||||
pub fn import_backup(&mut self, backup: &[u8]) -> anyhow::Result<()> {
|
pub fn import_backup(&mut self, backup: &[u8]) -> anyhow::Result<()> {
|
||||||
self.r#impl.import_backup(&mut self.ptp, backup)
|
self.r#impl.import_backup(&mut self.ptp, backup)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_active_custom_setting(&mut self, slot: FujiCustomSetting) -> anyhow::Result<()> {
|
||||||
|
self.r#impl.set_custom_setting_slot(&mut self.ptp, slot)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_custom_setting_name(&mut self) -> anyhow::Result<String> {
|
||||||
|
self.r#impl.get_custom_setting_name(&mut self.ptp)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_image_size(&mut self) -> anyhow::Result<FujiImageSize> {
|
||||||
|
self.r#impl.get_image_size(&mut self.ptp)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_image_quality(&mut self) -> anyhow::Result<FujiImageQuality> {
|
||||||
|
self.r#impl.get_image_quality(&mut self.ptp)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_dynamic_range(&mut self) -> anyhow::Result<FujiDynamicRange> {
|
||||||
|
self.r#impl.get_dynamic_range(&mut self.ptp)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_dynamic_range_priority(&mut self) -> anyhow::Result<FujiStillDynamicRangePriority> {
|
||||||
|
self.r#impl.get_dynamic_range_priority(&mut self.ptp)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_film_simulation(&mut self) -> anyhow::Result<FujiFilmSimulation> {
|
||||||
|
self.r#impl.get_film_simulation(&mut self.ptp)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_grain_effect(&mut self) -> anyhow::Result<FujiGrainEffect> {
|
||||||
|
self.r#impl.get_grain_effect(&mut self.ptp)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_white_balance(&mut self) -> anyhow::Result<FujiWhiteBalance> {
|
||||||
|
self.r#impl.get_white_balance(&mut self.ptp)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_high_iso_nr(&mut self) -> anyhow::Result<FujiHighISONR> {
|
||||||
|
self.r#impl.get_high_iso_nr(&mut self.ptp)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_highlight_tone(&mut self) -> anyhow::Result<FujiHighlightTone> {
|
||||||
|
self.r#impl.get_highlight_tone(&mut self.ptp)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_shadow_tone(&mut self) -> anyhow::Result<FujiShadowTone> {
|
||||||
|
self.r#impl.get_shadow_tone(&mut self.ptp)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_color(&mut self) -> anyhow::Result<FujiColor> {
|
||||||
|
self.r#impl.get_color(&mut self.ptp)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_sharpness(&mut self) -> anyhow::Result<FujiSharpness> {
|
||||||
|
self.r#impl.get_sharpness(&mut self.ptp)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_clarity(&mut self) -> anyhow::Result<FujiClarity> {
|
||||||
|
self.r#impl.get_clarity(&mut self.ptp)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_wb_shift_red(&mut self) -> anyhow::Result<FujiWhiteBalanceShift> {
|
||||||
|
self.r#impl.get_wb_shift_red(&mut self.ptp)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_wb_shift_blue(&mut self) -> anyhow::Result<FujiWhiteBalanceShift> {
|
||||||
|
self.r#impl.get_wb_shift_blue(&mut self.ptp)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_wb_temperature(&mut self) -> anyhow::Result<FujiWhiteBalanceTemperature> {
|
||||||
|
self.r#impl.get_wb_temperature(&mut self.ptp)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_color_chrome_effect(&mut self) -> anyhow::Result<FujiColorChromeEffect> {
|
||||||
|
self.r#impl.get_color_chrome_effect(&mut self.ptp)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_color_chrome_fx_blue(&mut self) -> anyhow::Result<FujiColorChromeFXBlue> {
|
||||||
|
self.r#impl.get_color_chrome_fx_blue(&mut self.ptp)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for Camera {
|
impl Drop for Camera {
|
||||||
@@ -193,9 +260,8 @@ pub trait CameraImpl<P: rusb::UsbContext> {
|
|||||||
debug!("Sending OpenSession command");
|
debug!("Sending OpenSession command");
|
||||||
_ = ptp.send(
|
_ = ptp.send(
|
||||||
CommandCode::OpenSession,
|
CommandCode::OpenSession,
|
||||||
Some(&[session_id]),
|
&[session_id],
|
||||||
None,
|
None,
|
||||||
true,
|
|
||||||
self.timeout(),
|
self.timeout(),
|
||||||
)?;
|
)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -203,25 +269,41 @@ pub trait CameraImpl<P: rusb::UsbContext> {
|
|||||||
|
|
||||||
fn close_session(&self, ptp: &mut Ptp, _: u32) -> anyhow::Result<()> {
|
fn close_session(&self, ptp: &mut Ptp, _: u32) -> anyhow::Result<()> {
|
||||||
debug!("Sending CloseSession command");
|
debug!("Sending CloseSession command");
|
||||||
_ = ptp.send(CommandCode::CloseSession, None, None, true, self.timeout())?;
|
_ = ptp.send(CommandCode::CloseSession, &[], None, self.timeout())?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_info(&self, ptp: &mut Ptp) -> anyhow::Result<DeviceInfo> {
|
fn get_info(&self, ptp: &mut Ptp) -> anyhow::Result<DeviceInfo> {
|
||||||
debug!("Sending GetDeviceInfo command");
|
debug!("Sending GetDeviceInfo command");
|
||||||
let response = ptp.send(CommandCode::GetDeviceInfo, None, None, true, self.timeout())?;
|
let response = ptp.send(CommandCode::GetDeviceInfo, &[], None, self.timeout())?;
|
||||||
debug!("Received response with {} bytes", response.len());
|
debug!("Received response with {} bytes", response.len());
|
||||||
let info = DeviceInfo::try_from(response.as_slice())?;
|
let info = DeviceInfo::try_from_ptp(&response)?;
|
||||||
Ok(info)
|
Ok(info)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_prop_value(&self, ptp: &mut Ptp, prop: PropCode) -> anyhow::Result<Vec<u8>> {
|
fn get_prop_value(&self, ptp: &mut Ptp, prop: DevicePropCode) -> anyhow::Result<Vec<u8>> {
|
||||||
debug!("Sending GetDevicePropValue command for property {prop:?}");
|
debug!("Sending GetDevicePropValue command for property {prop:?}");
|
||||||
let response = ptp.send(
|
let response = ptp.send(
|
||||||
CommandCode::GetDevicePropValue,
|
CommandCode::GetDevicePropValue,
|
||||||
Some(&[prop as u32]),
|
&[prop.into()],
|
||||||
None,
|
None,
|
||||||
true,
|
self.timeout(),
|
||||||
|
)?;
|
||||||
|
debug!("Received response with {} bytes", response.len());
|
||||||
|
Ok(response)
|
||||||
|
}
|
||||||
|
|
||||||
|
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(),
|
self.timeout(),
|
||||||
)?;
|
)?;
|
||||||
debug!("Received response with {} bytes", response.len());
|
debug!("Received response with {} bytes", response.len());
|
||||||
@@ -232,23 +314,11 @@ pub trait CameraImpl<P: rusb::UsbContext> {
|
|||||||
const HANDLE: u32 = 0x0;
|
const HANDLE: u32 = 0x0;
|
||||||
|
|
||||||
debug!("Sending GetObjectInfo command for backup");
|
debug!("Sending GetObjectInfo command for backup");
|
||||||
let response = ptp.send(
|
let response = ptp.send(CommandCode::GetObjectInfo, &[HANDLE], None, self.timeout())?;
|
||||||
CommandCode::GetObjectInfo,
|
|
||||||
Some(&[HANDLE]),
|
|
||||||
None,
|
|
||||||
true,
|
|
||||||
self.timeout(),
|
|
||||||
)?;
|
|
||||||
debug!("Received response with {} bytes", response.len());
|
debug!("Received response with {} bytes", response.len());
|
||||||
|
|
||||||
debug!("Sending GetObject command for backup");
|
debug!("Sending GetObject command for backup");
|
||||||
let response = ptp.send(
|
let response = ptp.send(CommandCode::GetObject, &[HANDLE], None, self.timeout())?;
|
||||||
CommandCode::GetObject,
|
|
||||||
Some(&[HANDLE]),
|
|
||||||
None,
|
|
||||||
true,
|
|
||||||
self.timeout(),
|
|
||||||
)?;
|
|
||||||
debug!("Received response with {} bytes", response.len());
|
debug!("Received response with {} bytes", response.len());
|
||||||
|
|
||||||
Ok(response)
|
Ok(response)
|
||||||
@@ -257,21 +327,18 @@ pub trait CameraImpl<P: rusb::UsbContext> {
|
|||||||
fn import_backup(&self, ptp: &mut Ptp, buffer: &[u8]) -> anyhow::Result<()> {
|
fn import_backup(&self, ptp: &mut Ptp, buffer: &[u8]) -> anyhow::Result<()> {
|
||||||
debug!("Preparing ObjectInfo header for backup");
|
debug!("Preparing ObjectInfo header for backup");
|
||||||
|
|
||||||
let mut header1 = vec![0u8; 1012];
|
let mut header = vec![0u8; 1076];
|
||||||
let mut cursor = Cursor::new(&mut header1[..]);
|
0x0u32.try_write_ptp(&mut header)?;
|
||||||
cursor.write_u32::<LittleEndian>(0x0)?;
|
0x5000u16.try_write_ptp(&mut header)?;
|
||||||
cursor.write_u16::<LittleEndian>(0x5000)?;
|
0x0u16.try_write_ptp(&mut header)?;
|
||||||
cursor.write_u16::<LittleEndian>(0x0)?;
|
0x0u32.try_write_ptp(&mut header)?;
|
||||||
cursor.write_u32::<LittleEndian>(u32::try_from(buffer.len())?)?;
|
u32::try_from(buffer.len())?.try_write_ptp(&mut header)?;
|
||||||
|
|
||||||
let header2 = vec![0u8; 64];
|
|
||||||
|
|
||||||
debug!("Sending SendObjectInfo command for backup");
|
debug!("Sending SendObjectInfo command for backup");
|
||||||
let response = ptp.send_many(
|
let response = ptp.send(
|
||||||
CommandCode::SendObjectInfo,
|
CommandCode::SendObjectInfo,
|
||||||
Some(&[0x0, 0x0]),
|
&[0x0, 0x0],
|
||||||
Some(&[&header1, &header2]),
|
Some(&header),
|
||||||
true,
|
|
||||||
self.timeout(),
|
self.timeout(),
|
||||||
)?;
|
)?;
|
||||||
debug!("Received response with {} bytes", response.len());
|
debug!("Received response with {} bytes", response.len());
|
||||||
@@ -279,13 +346,154 @@ pub trait CameraImpl<P: rusb::UsbContext> {
|
|||||||
debug!("Sending SendObject command for backup");
|
debug!("Sending SendObject command for backup");
|
||||||
let response = ptp.send(
|
let response = ptp.send(
|
||||||
CommandCode::SendObject,
|
CommandCode::SendObject,
|
||||||
Some(&[0x0]),
|
&[0x0],
|
||||||
Some(buffer),
|
Some(buffer),
|
||||||
true,
|
|
||||||
self.timeout(),
|
self.timeout(),
|
||||||
)?;
|
)?;
|
||||||
debug!("Received response with {} bytes", response.len());
|
debug!("Received response with {} bytes", response.len());
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn set_custom_setting_slot(
|
||||||
|
&self,
|
||||||
|
ptp: &mut Ptp,
|
||||||
|
slot: FujiCustomSetting,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
self.set_prop_value(
|
||||||
|
ptp,
|
||||||
|
DevicePropCode::FujiStillCustomSetting,
|
||||||
|
&slot.try_into_ptp()?,
|
||||||
|
)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_custom_setting_name(&self, ptp: &mut Ptp) -> anyhow::Result<String> {
|
||||||
|
let bytes = self.get_prop_value(ptp, DevicePropCode::FujiStillCustomSettingName)?;
|
||||||
|
let name = String::try_from_ptp(&bytes)?;
|
||||||
|
Ok(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_image_size(&self, ptp: &mut Ptp) -> anyhow::Result<FujiImageSize> {
|
||||||
|
let bytes = self.get_prop_value(ptp, DevicePropCode::FujiStillCustomSettingImageSize)?;
|
||||||
|
let result = FujiImageSize::try_from_ptp(&bytes)?;
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_image_quality(&self, ptp: &mut Ptp) -> anyhow::Result<FujiImageQuality> {
|
||||||
|
let bytes = self.get_prop_value(ptp, DevicePropCode::FujiStillCustomSettingImageQuality)?;
|
||||||
|
let result = FujiImageQuality::try_from_ptp(&bytes)?;
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_dynamic_range(&self, ptp: &mut Ptp) -> anyhow::Result<FujiDynamicRange> {
|
||||||
|
let bytes = self.get_prop_value(ptp, DevicePropCode::FujiStillCustomSettingDynamicRange)?;
|
||||||
|
let result = FujiDynamicRange::try_from_ptp(&bytes)?;
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_dynamic_range_priority(
|
||||||
|
&self,
|
||||||
|
ptp: &mut Ptp,
|
||||||
|
) -> anyhow::Result<FujiStillDynamicRangePriority> {
|
||||||
|
let bytes = self.get_prop_value(
|
||||||
|
ptp,
|
||||||
|
DevicePropCode::FujiStillCustomSettingDynamicRangePriority,
|
||||||
|
)?;
|
||||||
|
let result = FujiStillDynamicRangePriority::try_from_ptp(&bytes)?;
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_film_simulation(&self, ptp: &mut Ptp) -> anyhow::Result<FujiFilmSimulation> {
|
||||||
|
let bytes =
|
||||||
|
self.get_prop_value(ptp, DevicePropCode::FujiStillCustomSettingFilmSimulation)?;
|
||||||
|
let result = FujiFilmSimulation::try_from_ptp(&bytes)?;
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_grain_effect(&self, ptp: &mut Ptp) -> anyhow::Result<FujiGrainEffect> {
|
||||||
|
let bytes = self.get_prop_value(ptp, DevicePropCode::FujiStillCustomSettingGrainEffect)?;
|
||||||
|
let result = FujiGrainEffect::try_from_ptp(&bytes)?;
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_white_balance(&self, ptp: &mut Ptp) -> anyhow::Result<FujiWhiteBalance> {
|
||||||
|
let bytes = self.get_prop_value(ptp, DevicePropCode::FujiStillCustomSettingWhiteBalance)?;
|
||||||
|
let result = FujiWhiteBalance::try_from_ptp(&bytes)?;
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_high_iso_nr(&self, ptp: &mut Ptp) -> anyhow::Result<FujiHighISONR> {
|
||||||
|
let bytes = self.get_prop_value(ptp, DevicePropCode::FujiStillCustomSettingHighISONR)?;
|
||||||
|
let result = FujiHighISONR::try_from_ptp(&bytes)?;
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_highlight_tone(&self, ptp: &mut Ptp) -> anyhow::Result<FujiHighlightTone> {
|
||||||
|
let bytes =
|
||||||
|
self.get_prop_value(ptp, DevicePropCode::FujiStillCustomSettingHighlightTone)?;
|
||||||
|
let result = FujiHighlightTone::try_from_ptp(&bytes)?;
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_shadow_tone(&self, ptp: &mut Ptp) -> anyhow::Result<FujiShadowTone> {
|
||||||
|
let bytes = self.get_prop_value(ptp, DevicePropCode::FujiStillCustomSettingShadowTone)?;
|
||||||
|
let result = FujiShadowTone::try_from_ptp(&bytes)?;
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_color(&self, ptp: &mut Ptp) -> anyhow::Result<FujiColor> {
|
||||||
|
let bytes = self.get_prop_value(ptp, DevicePropCode::FujiStillCustomSettingColor)?;
|
||||||
|
let result = FujiColor::try_from_ptp(&bytes)?;
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_sharpness(&self, ptp: &mut Ptp) -> anyhow::Result<FujiSharpness> {
|
||||||
|
let bytes = self.get_prop_value(ptp, DevicePropCode::FujiStillCustomSettingSharpness)?;
|
||||||
|
let result = FujiSharpness::try_from_ptp(&bytes)?;
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_clarity(&self, ptp: &mut Ptp) -> anyhow::Result<FujiClarity> {
|
||||||
|
let bytes = self.get_prop_value(ptp, DevicePropCode::FujiStillCustomSettingClarity)?;
|
||||||
|
let result = FujiClarity::try_from_ptp(&bytes)?;
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_wb_shift_red(&self, ptp: &mut Ptp) -> anyhow::Result<FujiWhiteBalanceShift> {
|
||||||
|
let bytes =
|
||||||
|
self.get_prop_value(ptp, DevicePropCode::FujiStillCustomSettingWhiteBalanceRed)?;
|
||||||
|
let result = FujiWhiteBalanceShift::try_from_ptp(&bytes)?;
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_wb_shift_blue(&self, ptp: &mut Ptp) -> anyhow::Result<FujiWhiteBalanceShift> {
|
||||||
|
let bytes =
|
||||||
|
self.get_prop_value(ptp, DevicePropCode::FujiStillCustomSettingWhiteBalanceBlue)?;
|
||||||
|
let result = FujiWhiteBalanceShift::try_from_ptp(&bytes)?;
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_wb_temperature(&self, ptp: &mut Ptp) -> anyhow::Result<FujiWhiteBalanceTemperature> {
|
||||||
|
let bytes = self.get_prop_value(
|
||||||
|
ptp,
|
||||||
|
DevicePropCode::FujiStillCustomSettingWhiteBalanceTemperature,
|
||||||
|
)?;
|
||||||
|
let result = FujiWhiteBalanceTemperature::try_from_ptp(&bytes)?;
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_color_chrome_effect(&self, ptp: &mut Ptp) -> anyhow::Result<FujiColorChromeEffect> {
|
||||||
|
let bytes =
|
||||||
|
self.get_prop_value(ptp, DevicePropCode::FujiStillCustomSettingColorChromeEffect)?;
|
||||||
|
let result = FujiColorChromeEffect::try_from_ptp(&bytes)?;
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_color_chrome_fx_blue(&self, ptp: &mut Ptp) -> anyhow::Result<FujiColorChromeFXBlue> {
|
||||||
|
let bytes =
|
||||||
|
self.get_prop_value(ptp, DevicePropCode::FujiStillCustomSettingColorChromeFXBlue)?;
|
||||||
|
let result = FujiColorChromeFXBlue::try_from_ptp(&bytes)?;
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,205 +0,0 @@
|
|||||||
use std::fmt;
|
|
||||||
|
|
||||||
use anyhow::bail;
|
|
||||||
use serde::Serialize;
|
|
||||||
|
|
||||||
#[repr(u16)]
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
||||||
pub enum CommandCode {
|
|
||||||
GetDeviceInfo = 0x1001,
|
|
||||||
OpenSession = 0x1002,
|
|
||||||
CloseSession = 0x1003,
|
|
||||||
GetObjectInfo = 0x1008,
|
|
||||||
GetObject = 0x1009,
|
|
||||||
SendObjectInfo = 0x100C,
|
|
||||||
SendObject = 0x100D,
|
|
||||||
GetDevicePropValue = 0x1015,
|
|
||||||
SetDevicePropValue = 0x1016,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TryFrom<u16> for CommandCode {
|
|
||||||
type Error = anyhow::Error;
|
|
||||||
|
|
||||||
fn try_from(value: u16) -> Result<Self, Self::Error> {
|
|
||||||
match value {
|
|
||||||
0x1001 => Ok(Self::GetDeviceInfo),
|
|
||||||
0x1002 => Ok(Self::OpenSession),
|
|
||||||
0x1003 => Ok(Self::CloseSession),
|
|
||||||
0x1008 => Ok(Self::GetObjectInfo),
|
|
||||||
0x1009 => Ok(Self::GetObject),
|
|
||||||
0x100C => Ok(Self::SendObjectInfo),
|
|
||||||
0x100D => Ok(Self::SendObject),
|
|
||||||
0x1015 => Ok(Self::GetDevicePropValue),
|
|
||||||
0x1016 => Ok(Self::SetDevicePropValue),
|
|
||||||
v => bail!("Unknown command code '{v:x?}'"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[repr(u16)]
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
||||||
pub enum ResponseCode {
|
|
||||||
Undefined = 0x2000,
|
|
||||||
Ok = 0x2001,
|
|
||||||
GeneralError = 0x2002,
|
|
||||||
SessionNotOpen = 0x2003,
|
|
||||||
InvalidTransactionId = 0x2004,
|
|
||||||
OperationNotSupported = 0x2005,
|
|
||||||
ParameterNotSupported = 0x2006,
|
|
||||||
IncompleteTransfer = 0x2007,
|
|
||||||
InvalidStorageId = 0x2008,
|
|
||||||
InvalidObjectHandle = 0x2009,
|
|
||||||
DevicePropNotSupported = 0x200A,
|
|
||||||
InvalidObjectFormatCode = 0x200B,
|
|
||||||
StoreFull = 0x200C,
|
|
||||||
ObjectWriteProtected = 0x200D,
|
|
||||||
StoreReadOnly = 0x200E,
|
|
||||||
AccessDenied = 0x200F,
|
|
||||||
NoThumbnailPresent = 0x2010,
|
|
||||||
SelfTestFailed = 0x2011,
|
|
||||||
PartialDeletion = 0x2012,
|
|
||||||
StoreNotAvailable = 0x2013,
|
|
||||||
SpecificationByFormatUnsupported = 0x2014,
|
|
||||||
NoValidObjectInfo = 0x2015,
|
|
||||||
InvalidCodeFormat = 0x2016,
|
|
||||||
UnknownVendorCode = 0x2017,
|
|
||||||
CaptureAlreadyTerminated = 0x2018,
|
|
||||||
DeviceBusy = 0x2019,
|
|
||||||
InvalidParentObject = 0x201A,
|
|
||||||
InvalidDevicePropFormat = 0x201B,
|
|
||||||
InvalidDevicePropValue = 0x201C,
|
|
||||||
InvalidParameter = 0x201D,
|
|
||||||
SessionAlreadyOpen = 0x201E,
|
|
||||||
TransactionCancelled = 0x201F,
|
|
||||||
SpecificationOfDestinationUnsupported = 0x2020,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::convert::TryFrom<u16> for ResponseCode {
|
|
||||||
type Error = anyhow::Error;
|
|
||||||
|
|
||||||
fn try_from(value: u16) -> Result<Self, Self::Error> {
|
|
||||||
match value {
|
|
||||||
0x2000 => Ok(Self::Undefined),
|
|
||||||
0x2001 => Ok(Self::Ok),
|
|
||||||
0x2002 => Ok(Self::GeneralError),
|
|
||||||
0x2003 => Ok(Self::SessionNotOpen),
|
|
||||||
0x2004 => Ok(Self::InvalidTransactionId),
|
|
||||||
0x2005 => Ok(Self::OperationNotSupported),
|
|
||||||
0x2006 => Ok(Self::ParameterNotSupported),
|
|
||||||
0x2007 => Ok(Self::IncompleteTransfer),
|
|
||||||
0x2008 => Ok(Self::InvalidStorageId),
|
|
||||||
0x2009 => Ok(Self::InvalidObjectHandle),
|
|
||||||
0x200A => Ok(Self::DevicePropNotSupported),
|
|
||||||
0x200B => Ok(Self::InvalidObjectFormatCode),
|
|
||||||
0x200C => Ok(Self::StoreFull),
|
|
||||||
0x200D => Ok(Self::ObjectWriteProtected),
|
|
||||||
0x200E => Ok(Self::StoreReadOnly),
|
|
||||||
0x200F => Ok(Self::AccessDenied),
|
|
||||||
0x2010 => Ok(Self::NoThumbnailPresent),
|
|
||||||
0x2011 => Ok(Self::SelfTestFailed),
|
|
||||||
0x2012 => Ok(Self::PartialDeletion),
|
|
||||||
0x2013 => Ok(Self::StoreNotAvailable),
|
|
||||||
0x2014 => Ok(Self::SpecificationByFormatUnsupported),
|
|
||||||
0x2015 => Ok(Self::NoValidObjectInfo),
|
|
||||||
0x2016 => Ok(Self::InvalidCodeFormat),
|
|
||||||
0x2017 => Ok(Self::UnknownVendorCode),
|
|
||||||
0x2018 => Ok(Self::CaptureAlreadyTerminated),
|
|
||||||
0x2019 => Ok(Self::DeviceBusy),
|
|
||||||
0x201A => Ok(Self::InvalidParentObject),
|
|
||||||
0x201B => Ok(Self::InvalidDevicePropFormat),
|
|
||||||
0x201C => Ok(Self::InvalidDevicePropValue),
|
|
||||||
0x201D => Ok(Self::InvalidParameter),
|
|
||||||
0x201E => Ok(Self::SessionAlreadyOpen),
|
|
||||||
0x201F => Ok(Self::TransactionCancelled),
|
|
||||||
0x2020 => Ok(Self::SpecificationOfDestinationUnsupported),
|
|
||||||
v => bail!("Unknown response code '{v:x?}'"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[repr(u16)]
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
||||||
pub enum ContainerCode {
|
|
||||||
Command(CommandCode),
|
|
||||||
Response(ResponseCode),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<ContainerCode> for u16 {
|
|
||||||
fn from(code: ContainerCode) -> Self {
|
|
||||||
match code {
|
|
||||||
ContainerCode::Command(cmd) => cmd as Self,
|
|
||||||
ContainerCode::Response(resp) => resp as Self,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TryFrom<u16> for ContainerCode {
|
|
||||||
type Error = anyhow::Error;
|
|
||||||
|
|
||||||
fn try_from(value: u16) -> Result<Self, Self::Error> {
|
|
||||||
if let Ok(cmd) = CommandCode::try_from(value) {
|
|
||||||
return Ok(Self::Command(cmd));
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Ok(resp) = ResponseCode::try_from(value) {
|
|
||||||
return Ok(Self::Response(resp));
|
|
||||||
}
|
|
||||||
|
|
||||||
bail!("Unknown container code '{value:x?}'");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[repr(u32)]
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
||||||
pub enum PropCode {
|
|
||||||
FujiUsbMode = 0xd16e,
|
|
||||||
FujiBatteryInfo2 = 0xD36B,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, Serialize, PartialEq, Eq)]
|
|
||||||
pub enum UsbMode {
|
|
||||||
RawConversion,
|
|
||||||
Unsupported,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<u32> for UsbMode {
|
|
||||||
fn from(val: u32) -> Self {
|
|
||||||
match val {
|
|
||||||
6 => Self::RawConversion,
|
|
||||||
_ => Self::Unsupported,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for UsbMode {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
let s = match self {
|
|
||||||
Self::RawConversion => "USB RAW CONV./BACKUP RESTORE",
|
|
||||||
Self::Unsupported => "Unsupported USB Mode",
|
|
||||||
};
|
|
||||||
write!(f, "{s}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
||||||
#[repr(u16)]
|
|
||||||
pub enum ContainerType {
|
|
||||||
Command = 1,
|
|
||||||
Data = 2,
|
|
||||||
Response = 3,
|
|
||||||
Event = 4,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TryFrom<u16> for ContainerType {
|
|
||||||
type Error = anyhow::Error;
|
|
||||||
|
|
||||||
fn try_from(value: u16) -> Result<Self, Self::Error> {
|
|
||||||
match value {
|
|
||||||
1 => Ok(Self::Command),
|
|
||||||
2 => Ok(Self::Data),
|
|
||||||
3 => Ok(Self::Response),
|
|
||||||
4 => Ok(Self::Event),
|
|
||||||
v => bail!("Invalid message type '{v}'"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,6 +1,6 @@
|
|||||||
use std::{fmt, io};
|
use std::{fmt, io};
|
||||||
|
|
||||||
use crate::camera::ptp::enums::ResponseCode;
|
use crate::camera::ptp::hex::ResponseCode;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
|
1120
src/camera/ptp/hex.rs
Normal file
1120
src/camera/ptp/hex.rs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,14 +1,13 @@
|
|||||||
pub mod enums;
|
|
||||||
pub mod error;
|
pub mod error;
|
||||||
pub mod read;
|
pub mod hex;
|
||||||
pub mod structs;
|
pub mod structs;
|
||||||
|
|
||||||
use std::{cmp::min, time::Duration};
|
use std::{cmp::min, io::Cursor, time::Duration};
|
||||||
|
|
||||||
use anyhow::bail;
|
use anyhow::bail;
|
||||||
use byteorder::{LittleEndian, WriteBytesExt};
|
use hex::{CommandCode, ContainerCode, ContainerType, ResponseCode};
|
||||||
use enums::{CommandCode, ContainerCode, ContainerType, ResponseCode};
|
use log::{debug, error, trace, warn};
|
||||||
use log::{debug, error, trace};
|
use ptp_cursor::{PtpDeserialize, PtpSerialize};
|
||||||
use rusb::GlobalContext;
|
use rusb::GlobalContext;
|
||||||
use structs::ContainerInfo;
|
use structs::ContainerInfo;
|
||||||
|
|
||||||
@@ -27,61 +26,30 @@ impl Ptp {
|
|||||||
pub fn send(
|
pub fn send(
|
||||||
&mut self,
|
&mut self,
|
||||||
code: CommandCode,
|
code: CommandCode,
|
||||||
params: Option<&[u32]>,
|
params: &[u32],
|
||||||
data: Option<&[u8]>,
|
data: Option<&[u8]>,
|
||||||
transaction: bool,
|
|
||||||
timeout: Duration,
|
timeout: Duration,
|
||||||
) -> anyhow::Result<Vec<u8>> {
|
) -> anyhow::Result<Vec<u8>> {
|
||||||
let (params, transaction_id) = self.prepare_send(params, transaction);
|
let transaction_id = self.transaction_id;
|
||||||
self.send_header(code, params, transaction_id, timeout)?;
|
self.send_header(code, params, transaction_id, timeout)?;
|
||||||
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, timeout)?;
|
||||||
}
|
}
|
||||||
self.receive_response(timeout)
|
let response = self.receive_response(timeout);
|
||||||
}
|
self.transaction_id += 1;
|
||||||
|
response
|
||||||
pub fn send_many(
|
|
||||||
&mut self,
|
|
||||||
code: CommandCode,
|
|
||||||
params: Option<&[u32]>,
|
|
||||||
data: Option<&[&[u8]]>,
|
|
||||||
transaction: bool,
|
|
||||||
timeout: Duration,
|
|
||||||
) -> anyhow::Result<Vec<u8>> {
|
|
||||||
let (params, transaction_id) = self.prepare_send(params, transaction);
|
|
||||||
self.send_header(code, params, transaction_id, timeout)?;
|
|
||||||
if let Some(data) = data {
|
|
||||||
self.write_many(ContainerType::Data, code, data, transaction_id, timeout)?;
|
|
||||||
}
|
|
||||||
self.receive_response(timeout)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn prepare_send<'a>(
|
|
||||||
&mut self,
|
|
||||||
params: Option<&'a [u32]>,
|
|
||||||
transaction: bool,
|
|
||||||
) -> (&'a [u32], Option<u32>) {
|
|
||||||
let params = params.unwrap_or_default();
|
|
||||||
let transaction_id = if transaction {
|
|
||||||
let transaction_id = Some(self.transaction_id);
|
|
||||||
self.transaction_id += 1;
|
|
||||||
transaction_id
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
(params, transaction_id)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn send_header(
|
fn send_header(
|
||||||
&self,
|
&self,
|
||||||
code: CommandCode,
|
code: CommandCode,
|
||||||
params: &[u32],
|
params: &[u32],
|
||||||
transaction_id: Option<u32>,
|
transaction_id: u32,
|
||||||
timeout: Duration,
|
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 {
|
||||||
payload.write_u32::<LittleEndian>(*p).ok();
|
p.try_write_ptp(&mut payload)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
trace!(
|
trace!(
|
||||||
@@ -113,10 +81,18 @@ impl Ptp {
|
|||||||
}
|
}
|
||||||
ContainerType::Response => {
|
ContainerType::Response => {
|
||||||
trace!("Response received: code {:?}", container.code);
|
trace!("Response received: code {:?}", container.code);
|
||||||
|
|
||||||
|
if self.transaction_id != container.transaction_id {
|
||||||
|
warn!(
|
||||||
|
"Mismatched transaction_id {}, expecting {}",
|
||||||
|
container.transaction_id, self.transaction_id
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
match container.code {
|
match container.code {
|
||||||
ContainerCode::Command(_) | ContainerCode::Response(ResponseCode::Ok) => {}
|
ContainerCode::Command(_) | ContainerCode::Response(ResponseCode::Ok) => {}
|
||||||
ContainerCode::Response(code) => {
|
ContainerCode::Response(code) => {
|
||||||
bail!(error::Error::Response(code as u16));
|
bail!(error::Error::Response(code.into()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -127,7 +103,7 @@ impl Ptp {
|
|||||||
return Ok(response);
|
return Ok(response);
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
debug!("Ignoring unexpected container type: {:?}", container.kind);
|
warn!("Unexpected container type: {:?}", container.kind);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -138,13 +114,13 @@ impl Ptp {
|
|||||||
kind: ContainerType,
|
kind: ContainerType,
|
||||||
code: CommandCode,
|
code: CommandCode,
|
||||||
payload: &[u8],
|
payload: &[u8],
|
||||||
transaction_id: Option<u32>,
|
transaction_id: u32,
|
||||||
timeout: Duration,
|
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()?;
|
let mut buffer: Vec<u8> = container_info.try_into_ptp()?;
|
||||||
|
|
||||||
let first_chunk_len = min(payload.len(), self.chunk_size - container_info.len());
|
let first_chunk_len = min(payload.len(), self.chunk_size - ContainerInfo::SIZE);
|
||||||
buffer.extend_from_slice(&payload[..first_chunk_len]);
|
buffer.extend_from_slice(&payload[..first_chunk_len]);
|
||||||
|
|
||||||
trace!(
|
trace!(
|
||||||
@@ -165,62 +141,6 @@ impl Ptp {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_many(
|
|
||||||
&self,
|
|
||||||
kind: ContainerType,
|
|
||||||
code: CommandCode,
|
|
||||||
parts: &[&[u8]],
|
|
||||||
transaction_id: Option<u32>,
|
|
||||||
timeout: Duration,
|
|
||||||
) -> anyhow::Result<()> {
|
|
||||||
if parts.is_empty() {
|
|
||||||
return self.write(kind, code, &[], transaction_id, timeout);
|
|
||||||
}
|
|
||||||
|
|
||||||
if parts.len() == 1 {
|
|
||||||
return self.write(kind, code, parts[0], transaction_id, timeout);
|
|
||||||
}
|
|
||||||
|
|
||||||
let total_len: usize = parts.iter().map(|c| c.len()).sum();
|
|
||||||
let container_info = ContainerInfo::new(kind, code, transaction_id, total_len)?;
|
|
||||||
let mut buffer: Vec<u8> = container_info.try_into()?;
|
|
||||||
|
|
||||||
let first = parts[0];
|
|
||||||
let first_part_chunk_len = min(first.len(), self.chunk_size - container_info.len());
|
|
||||||
buffer.extend_from_slice(&first[..first_part_chunk_len]);
|
|
||||||
|
|
||||||
trace!(
|
|
||||||
"Writing PTP {kind:?} container, code: {code:?}, transaction: {transaction_id:?}, first payload part chunk ({first_part_chunk_len} bytes)",
|
|
||||||
);
|
|
||||||
self.handle.write_bulk(self.bulk_out, &buffer, timeout)?;
|
|
||||||
|
|
||||||
for chunk in first[first_part_chunk_len..].chunks(self.chunk_size) {
|
|
||||||
trace!(
|
|
||||||
"Writing additional payload part chunk ({} bytes)",
|
|
||||||
chunk.len(),
|
|
||||||
);
|
|
||||||
self.handle.write_bulk(self.bulk_out, chunk, timeout)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
for part in &parts[1..] {
|
|
||||||
trace!("Writing additional payload part");
|
|
||||||
for chunk in part.chunks(self.chunk_size) {
|
|
||||||
trace!(
|
|
||||||
"Writing additional payload part chunk ({} bytes)",
|
|
||||||
chunk.len(),
|
|
||||||
);
|
|
||||||
self.handle.write_bulk(self.bulk_out, chunk, timeout)?;
|
|
||||||
}
|
|
||||||
trace!(
|
|
||||||
"Write completed for part, total payload of {} bytes",
|
|
||||||
part.len()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
trace!("Write completed for code {code:?}, total payload of {total_len} bytes");
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read(&self, timeout: Duration) -> anyhow::Result<(ContainerInfo, Vec<u8>)> {
|
fn read(&self, timeout: Duration) -> anyhow::Result<(ContainerInfo, Vec<u8>)> {
|
||||||
let mut stack_buf = [0u8; 8 * 1024];
|
let mut stack_buf = [0u8; 8 * 1024];
|
||||||
|
|
||||||
@@ -230,7 +150,8 @@ impl Ptp {
|
|||||||
let buf = &stack_buf[..n];
|
let buf = &stack_buf[..n];
|
||||||
trace!("Read chunk ({n} bytes)");
|
trace!("Read chunk ({n} bytes)");
|
||||||
|
|
||||||
let container_info = ContainerInfo::try_from(buf)?;
|
let mut cur = Cursor::new(buf);
|
||||||
|
let container_info = ContainerInfo::try_read_ptp(&mut cur)?;
|
||||||
|
|
||||||
let payload_len = container_info.payload_len();
|
let payload_len = container_info.payload_len();
|
||||||
if payload_len == 0 {
|
if payload_len == 0 {
|
||||||
|
@@ -1,127 +0,0 @@
|
|||||||
#![allow(dead_code)]
|
|
||||||
#![allow(clippy::redundant_closure_for_method_calls)]
|
|
||||||
|
|
||||||
use std::io::Cursor;
|
|
||||||
|
|
||||||
use anyhow::bail;
|
|
||||||
use byteorder::{LittleEndian, ReadBytesExt};
|
|
||||||
|
|
||||||
pub trait Read: ReadBytesExt {
|
|
||||||
fn read_ptp_u8(&mut self) -> anyhow::Result<u8> {
|
|
||||||
Ok(self.read_u8()?)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_ptp_i8(&mut self) -> anyhow::Result<i8> {
|
|
||||||
Ok(self.read_i8()?)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_ptp_u16(&mut self) -> anyhow::Result<u16> {
|
|
||||||
Ok(self.read_u16::<LittleEndian>()?)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_ptp_i16(&mut self) -> anyhow::Result<i16> {
|
|
||||||
Ok(self.read_i16::<LittleEndian>()?)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_ptp_u32(&mut self) -> anyhow::Result<u32> {
|
|
||||||
Ok(self.read_u32::<LittleEndian>()?)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_ptp_i32(&mut self) -> anyhow::Result<i32> {
|
|
||||||
Ok(self.read_i32::<LittleEndian>()?)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_ptp_u64(&mut self) -> anyhow::Result<u64> {
|
|
||||||
Ok(self.read_u64::<LittleEndian>()?)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_ptp_i64(&mut self) -> anyhow::Result<i64> {
|
|
||||||
Ok(self.read_i64::<LittleEndian>()?)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_ptp_u128(&mut self) -> anyhow::Result<u128> {
|
|
||||||
Ok(self.read_u128::<LittleEndian>()?)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_ptp_i128(&mut self) -> anyhow::Result<i128> {
|
|
||||||
Ok(self.read_i128::<LittleEndian>()?)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_ptp_vec<T: Sized, U: Fn(&mut Self) -> anyhow::Result<T>>(
|
|
||||||
&mut self,
|
|
||||||
func: U,
|
|
||||||
) -> anyhow::Result<Vec<T>> {
|
|
||||||
let len = self.read_u32::<LittleEndian>()? as usize;
|
|
||||||
(0..len).map(|_| func(self)).collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_ptp_u8_vec(&mut self) -> anyhow::Result<Vec<u8>> {
|
|
||||||
self.read_ptp_vec(|cur| cur.read_ptp_u8())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_ptp_i8_vec(&mut self) -> anyhow::Result<Vec<i8>> {
|
|
||||||
self.read_ptp_vec(|cur| cur.read_ptp_i8())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_ptp_u16_vec(&mut self) -> anyhow::Result<Vec<u16>> {
|
|
||||||
self.read_ptp_vec(|cur| cur.read_ptp_u16())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_ptp_i16_vec(&mut self) -> anyhow::Result<Vec<i16>> {
|
|
||||||
self.read_ptp_vec(|cur| cur.read_ptp_i16())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_ptp_u32_vec(&mut self) -> anyhow::Result<Vec<u32>> {
|
|
||||||
self.read_ptp_vec(|cur| cur.read_ptp_u32())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_ptp_i32_vec(&mut self) -> anyhow::Result<Vec<i32>> {
|
|
||||||
self.read_ptp_vec(|cur| cur.read_ptp_i32())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_ptp_u64_vec(&mut self) -> anyhow::Result<Vec<u64>> {
|
|
||||||
self.read_ptp_vec(|cur| cur.read_ptp_u64())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_ptp_i64_vec(&mut self) -> anyhow::Result<Vec<i64>> {
|
|
||||||
self.read_ptp_vec(|cur| cur.read_ptp_i64())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_ptp_u128_vec(&mut self) -> anyhow::Result<Vec<u128>> {
|
|
||||||
self.read_ptp_vec(|cur| cur.read_ptp_u128())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_ptp_i128_vec(&mut self) -> anyhow::Result<Vec<i128>> {
|
|
||||||
self.read_ptp_vec(|cur| cur.read_ptp_i128())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_ptp_str(&mut self) -> anyhow::Result<String> {
|
|
||||||
let len = self.read_u8()?;
|
|
||||||
if len > 0 {
|
|
||||||
let data: Vec<u16> = (0..(len - 1))
|
|
||||||
.map(|_| self.read_u16::<LittleEndian>())
|
|
||||||
.collect::<std::result::Result<_, _>>()?;
|
|
||||||
self.read_u16::<LittleEndian>()?;
|
|
||||||
Ok(String::from_utf16(&data)?)
|
|
||||||
} else {
|
|
||||||
Ok(String::new())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn expect_end(&mut self) -> anyhow::Result<()>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: AsRef<[u8]>> Read for Cursor<T> {
|
|
||||||
fn expect_end(&mut self) -> anyhow::Result<()> {
|
|
||||||
let len = self.get_ref().as_ref().len();
|
|
||||||
if len as u64 != self.position() {
|
|
||||||
bail!(super::error::Error::Malformed(format!(
|
|
||||||
"Response {} bytes, expected {} bytes",
|
|
||||||
len,
|
|
||||||
self.position()
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,14 +1,9 @@
|
|||||||
use std::io::Cursor;
|
use ptp_macro::{PtpDeserialize, PtpSerialize};
|
||||||
|
|
||||||
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
|
use super::hex::{CommandCode, ContainerCode, ContainerType};
|
||||||
|
|
||||||
use super::{
|
|
||||||
enums::{CommandCode, ContainerCode, ContainerType},
|
|
||||||
read::Read,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
#[derive(Debug)]
|
#[derive(Debug, PtpSerialize, PtpDeserialize)]
|
||||||
pub struct DeviceInfo {
|
pub struct DeviceInfo {
|
||||||
pub version: u16,
|
pub version: u16,
|
||||||
pub vendor_ex_id: u32,
|
pub vendor_ex_id: u32,
|
||||||
@@ -26,87 +21,26 @@ pub struct DeviceInfo {
|
|||||||
pub serial_number: String,
|
pub serial_number: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<&[u8]> for DeviceInfo {
|
#[derive(Debug, Clone, Copy, PtpSerialize, PtpDeserialize)]
|
||||||
type Error = anyhow::Error;
|
|
||||||
|
|
||||||
fn try_from(buf: &[u8]) -> Result<Self, Self::Error> {
|
|
||||||
let mut cur = Cursor::new(buf);
|
|
||||||
|
|
||||||
Ok(Self {
|
|
||||||
version: cur.read_ptp_u16()?,
|
|
||||||
vendor_ex_id: cur.read_ptp_u32()?,
|
|
||||||
vendor_ex_version: cur.read_ptp_u16()?,
|
|
||||||
vendor_extension_desc: cur.read_ptp_str()?,
|
|
||||||
functional_mode: cur.read_ptp_u16()?,
|
|
||||||
operations_supported: cur.read_ptp_u16_vec()?,
|
|
||||||
events_supported: cur.read_ptp_u16_vec()?,
|
|
||||||
device_properties_supported: cur.read_ptp_u16_vec()?,
|
|
||||||
capture_formats: cur.read_ptp_u16_vec()?,
|
|
||||||
image_formats: cur.read_ptp_u16_vec()?,
|
|
||||||
manufacturer: cur.read_ptp_str()?,
|
|
||||||
model: cur.read_ptp_str()?,
|
|
||||||
device_version: cur.read_ptp_str()?,
|
|
||||||
serial_number: cur.read_ptp_str()?,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
|
||||||
pub struct ContainerInfo {
|
pub struct ContainerInfo {
|
||||||
pub total_len: u32,
|
pub total_len: u32,
|
||||||
pub kind: ContainerType,
|
pub kind: ContainerType,
|
||||||
pub code: ContainerCode,
|
pub code: ContainerCode,
|
||||||
pub transaction_id: Option<u32>,
|
pub transaction_id: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ContainerInfo {
|
impl ContainerInfo {
|
||||||
const BASE_SIZE: usize = size_of::<u32>() + size_of::<u16>() + size_of::<u16>();
|
pub const SIZE: usize =
|
||||||
pub const SIZE: usize = Self::BASE_SIZE + size_of::<u32>();
|
size_of::<u32>() + size_of::<u16>() + size_of::<u16>() + size_of::<u32>();
|
||||||
|
|
||||||
pub fn new(
|
pub fn new(
|
||||||
kind: ContainerType,
|
kind: ContainerType,
|
||||||
code: CommandCode,
|
code: CommandCode,
|
||||||
transaction_id: Option<u32>,
|
transaction_id: u32,
|
||||||
payload_len: usize,
|
payload_len: usize,
|
||||||
) -> anyhow::Result<Self> {
|
) -> anyhow::Result<Self> {
|
||||||
let mut total_len = if transaction_id.is_some() {
|
let total_len = u32::try_from(Self::SIZE + payload_len)?;
|
||||||
Self::SIZE
|
let code = ContainerCode::Command(code);
|
||||||
} else {
|
|
||||||
Self::BASE_SIZE
|
|
||||||
};
|
|
||||||
total_len += payload_len;
|
|
||||||
|
|
||||||
Ok(Self {
|
|
||||||
total_len: u32::try_from(total_len)?,
|
|
||||||
kind,
|
|
||||||
code: ContainerCode::Command(code),
|
|
||||||
transaction_id,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const fn len(&self) -> usize {
|
|
||||||
if self.transaction_id.is_some() {
|
|
||||||
Self::SIZE
|
|
||||||
} else {
|
|
||||||
Self::BASE_SIZE
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const fn payload_len(&self) -> usize {
|
|
||||||
self.total_len as usize - self.len()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TryFrom<&[u8]> for ContainerInfo {
|
|
||||||
type Error = anyhow::Error;
|
|
||||||
|
|
||||||
fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
|
|
||||||
let mut r = Cursor::new(bytes);
|
|
||||||
|
|
||||||
let total_len = r.read_u32::<LittleEndian>()?;
|
|
||||||
let kind = ContainerType::try_from(r.read_u16::<LittleEndian>()?)?;
|
|
||||||
let code = ContainerCode::try_from(r.read_u16::<LittleEndian>()?)?;
|
|
||||||
let transaction_id = Some(r.read_u32::<LittleEndian>()?);
|
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
total_len,
|
total_len,
|
||||||
@@ -115,19 +49,8 @@ impl TryFrom<&[u8]> for ContainerInfo {
|
|||||||
transaction_id,
|
transaction_id,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl TryFrom<ContainerInfo> for Vec<u8> {
|
pub const fn payload_len(&self) -> usize {
|
||||||
type Error = anyhow::Error;
|
self.total_len as usize - Self::SIZE
|
||||||
|
|
||||||
fn try_from(val: ContainerInfo) -> Result<Self, Self::Error> {
|
|
||||||
let mut buf = Self::with_capacity(val.len());
|
|
||||||
buf.write_u32::<LittleEndian>(val.total_len)?;
|
|
||||||
buf.write_u16::<LittleEndian>(val.kind as u16)?;
|
|
||||||
buf.write_u16::<LittleEndian>(val.code.into())?;
|
|
||||||
if let Some(transaction_id) = val.transaction_id {
|
|
||||||
buf.write_u32::<LittleEndian>(transaction_id)?;
|
|
||||||
}
|
|
||||||
Ok(buf)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,4 +1,87 @@
|
|||||||
use clap::Args;
|
use clap::Args;
|
||||||
|
|
||||||
|
use crate::camera::ptp::hex::{
|
||||||
|
FujiClarity, FujiColor, FujiColorChromeEffect, FujiColorChromeFXBlue, FujiDynamicRange,
|
||||||
|
FujiFilmSimulation, FujiGrainEffect, FujiHighISONR, FujiHighlightTone, FujiImageQuality,
|
||||||
|
FujiImageSize, FujiShadowTone, FujiSharpness, FujiStillDynamicRangePriority, FujiWhiteBalance,
|
||||||
|
FujiWhiteBalanceShift, FujiWhiteBalanceTemperature,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Args, Debug)]
|
#[derive(Args, Debug)]
|
||||||
pub struct FilmSimulationOptions {}
|
pub struct FilmSimulationOptions {
|
||||||
|
/// The name of the slot
|
||||||
|
#[clap(long)]
|
||||||
|
pub name: Option<String>,
|
||||||
|
|
||||||
|
/// The Fujifilm film simulation to use
|
||||||
|
#[clap(long)]
|
||||||
|
pub simulation: Option<FujiFilmSimulation>,
|
||||||
|
|
||||||
|
/// The output image resolution
|
||||||
|
#[clap(long, alias = "size")]
|
||||||
|
pub resolution: Option<FujiImageSize>,
|
||||||
|
|
||||||
|
/// The output image quality (JPEG compression level)
|
||||||
|
#[clap(long, value_parser)]
|
||||||
|
pub quality: Option<FujiImageQuality>,
|
||||||
|
|
||||||
|
/// Highlight Tone
|
||||||
|
#[clap(long, value_parser)]
|
||||||
|
pub highlights: Option<FujiHighlightTone>,
|
||||||
|
|
||||||
|
/// Shadow Tone
|
||||||
|
#[clap(long, value_parser)]
|
||||||
|
pub shadows: Option<FujiShadowTone>,
|
||||||
|
|
||||||
|
/// Color
|
||||||
|
#[clap(long, value_parser)]
|
||||||
|
pub color: Option<FujiColor>,
|
||||||
|
|
||||||
|
/// Sharpness
|
||||||
|
#[clap(long, value_parser)]
|
||||||
|
pub sharpness: Option<FujiSharpness>,
|
||||||
|
|
||||||
|
/// Clarity
|
||||||
|
#[clap(long, value_parser)]
|
||||||
|
pub clarity: Option<FujiClarity>,
|
||||||
|
|
||||||
|
/// White Balance
|
||||||
|
#[clap(long, value_parser)]
|
||||||
|
pub white_balance: Option<FujiWhiteBalance>,
|
||||||
|
|
||||||
|
/// White Balance Shift Red
|
||||||
|
#[clap(long, value_parser)]
|
||||||
|
pub wb_shift_red: Option<FujiWhiteBalanceShift>,
|
||||||
|
|
||||||
|
/// White Balance Shift Blue
|
||||||
|
#[clap(long, value_parser)]
|
||||||
|
pub wb_shift_blue: Option<FujiWhiteBalanceShift>,
|
||||||
|
|
||||||
|
/// White Balance Temperature (Only used if WB is set to 'Temperature')
|
||||||
|
#[clap(long, value_parser)]
|
||||||
|
pub wb_temperature: Option<FujiWhiteBalanceTemperature>,
|
||||||
|
|
||||||
|
/// Dynamic Range
|
||||||
|
#[clap(long, value_parser)]
|
||||||
|
pub dynamic_range: Option<FujiDynamicRange>,
|
||||||
|
|
||||||
|
/// Dynamic Range Priority
|
||||||
|
#[clap(long, value_parser)]
|
||||||
|
pub dr_priority: Option<FujiStillDynamicRangePriority>,
|
||||||
|
|
||||||
|
/// High ISO Noise Reduction
|
||||||
|
#[clap(long, value_parser)]
|
||||||
|
pub noise_reduction: Option<FujiHighISONR>,
|
||||||
|
|
||||||
|
/// Grain Effect
|
||||||
|
#[clap(long, value_parser)]
|
||||||
|
pub grain: Option<FujiGrainEffect>,
|
||||||
|
|
||||||
|
/// Color Chrome Effect
|
||||||
|
#[clap(long, value_parser)]
|
||||||
|
pub color_chrome_effect: Option<FujiColorChromeEffect>,
|
||||||
|
|
||||||
|
/// Color Chrome FX Blue
|
||||||
|
#[clap(long, value_parser)]
|
||||||
|
pub color_chrome_fx_blue: Option<FujiColorChromeFXBlue>,
|
||||||
|
}
|
||||||
|
@@ -4,7 +4,7 @@ use clap::Subcommand;
|
|||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
camera::{Camera, ptp::enums::UsbMode},
|
camera::{Camera, ptp::hex::UsbMode},
|
||||||
usb,
|
usb,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -20,6 +20,7 @@ pub enum DeviceCmd {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct CameraItemRepr {
|
pub struct CameraItemRepr {
|
||||||
pub name: &'static str,
|
pub name: &'static str,
|
||||||
pub usb_id: String,
|
pub usb_id: String,
|
||||||
@@ -60,11 +61,11 @@ fn handle_list(json: bool) -> anyhow::Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if cameras.is_empty() {
|
if cameras.is_empty() {
|
||||||
println!("No supported cameras connected.");
|
println!("No supported cameras connected");
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
println!("Connected cameras:");
|
println!("Connected Cameras:");
|
||||||
for d in cameras {
|
for d in cameras {
|
||||||
println!("- {d}");
|
println!("- {d}");
|
||||||
}
|
}
|
||||||
@@ -73,6 +74,7 @@ fn handle_list(json: bool) -> anyhow::Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct CameraRepr {
|
pub struct CameraRepr {
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
pub device: CameraItemRepr,
|
pub device: CameraItemRepr,
|
||||||
|
@@ -1,8 +1,23 @@
|
|||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
camera::ptp::hex::{
|
||||||
|
FujiClarity, FujiColor, FujiColorChromeEffect, FujiColorChromeFXBlue, FujiCustomSetting,
|
||||||
|
FujiDynamicRange, FujiFilmSimulation, FujiGrainEffect, FujiHighISONR, FujiHighlightTone,
|
||||||
|
FujiImageQuality, FujiImageSize, FujiShadowTone, FujiSharpness,
|
||||||
|
FujiStillDynamicRangePriority, FujiWhiteBalance, FujiWhiteBalanceShift,
|
||||||
|
FujiWhiteBalanceTemperature,
|
||||||
|
},
|
||||||
|
usb,
|
||||||
|
};
|
||||||
|
|
||||||
use super::common::{
|
use super::common::{
|
||||||
file::{Input, Output},
|
file::{Input, Output},
|
||||||
film::FilmSimulationOptions,
|
film::FilmSimulationOptions,
|
||||||
};
|
};
|
||||||
use clap::Subcommand;
|
use clap::Subcommand;
|
||||||
|
use serde::Serialize;
|
||||||
|
use strum::IntoEnumIterator;
|
||||||
|
|
||||||
#[derive(Subcommand, Debug)]
|
#[derive(Subcommand, Debug)]
|
||||||
pub enum SimulationCmd {
|
pub enum SimulationCmd {
|
||||||
@@ -13,15 +28,15 @@ pub enum SimulationCmd {
|
|||||||
/// Get simulation
|
/// Get simulation
|
||||||
#[command(alias = "g")]
|
#[command(alias = "g")]
|
||||||
Get {
|
Get {
|
||||||
/// Simulation number or name
|
/// Simulation slot number
|
||||||
simulation: u8,
|
slot: FujiCustomSetting,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// Set simulation parameters
|
/// Set simulation parameters
|
||||||
#[command(alias = "s")]
|
#[command(alias = "s")]
|
||||||
Set {
|
Set {
|
||||||
/// Simulation number or name
|
/// Simulation slot number
|
||||||
simulation: u8,
|
slot: FujiCustomSetting,
|
||||||
|
|
||||||
#[command(flatten)]
|
#[command(flatten)]
|
||||||
film_simulation_options: FilmSimulationOptions,
|
film_simulation_options: FilmSimulationOptions,
|
||||||
@@ -30,8 +45,8 @@ pub enum SimulationCmd {
|
|||||||
/// Export simulation
|
/// Export simulation
|
||||||
#[command(alias = "e")]
|
#[command(alias = "e")]
|
||||||
Export {
|
Export {
|
||||||
/// Simulation number or name
|
/// Simulation slot number
|
||||||
simulation: u8,
|
slot: FujiCustomSetting,
|
||||||
|
|
||||||
/// Output file (use '-' to write to stdout)
|
/// Output file (use '-' to write to stdout)
|
||||||
output_file: Output,
|
output_file: Output,
|
||||||
@@ -40,10 +55,166 @@ pub enum SimulationCmd {
|
|||||||
/// Import simulation
|
/// Import simulation
|
||||||
#[command(alias = "i")]
|
#[command(alias = "i")]
|
||||||
Import {
|
Import {
|
||||||
/// Simulation number
|
/// Simulation slot number
|
||||||
slot: u8,
|
slot: FujiCustomSetting,
|
||||||
|
|
||||||
/// Input file (use '-' to read from stdin)
|
/// Input file (use '-' to read from stdin)
|
||||||
input_file: Input,
|
input_file: Input,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct CustomSettingRepr {
|
||||||
|
pub slot: FujiCustomSetting,
|
||||||
|
pub name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_list(json: bool, device_id: Option<&str>) -> anyhow::Result<()> {
|
||||||
|
let mut camera = usb::get_camera(device_id)?;
|
||||||
|
|
||||||
|
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 {
|
||||||
|
println!("{}", serde_json::to_string_pretty(&slots)?);
|
||||||
|
} else {
|
||||||
|
println!("Film Simulations:");
|
||||||
|
for slot in slots {
|
||||||
|
println!("- {}: {}", slot.slot, slot.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct FilmSimulationRepr {
|
||||||
|
pub slot: FujiCustomSetting,
|
||||||
|
pub name: String,
|
||||||
|
pub simulation: FujiFilmSimulation,
|
||||||
|
pub resolution: FujiImageSize,
|
||||||
|
pub quality: FujiImageQuality,
|
||||||
|
pub highlights: FujiHighlightTone,
|
||||||
|
pub shadows: FujiShadowTone,
|
||||||
|
pub color: FujiColor,
|
||||||
|
pub sharpness: FujiSharpness,
|
||||||
|
pub clarity: FujiClarity,
|
||||||
|
pub white_balance: FujiWhiteBalance,
|
||||||
|
pub wb_shift_red: FujiWhiteBalanceShift,
|
||||||
|
pub wb_shift_blue: FujiWhiteBalanceShift,
|
||||||
|
pub wb_temperature: FujiWhiteBalanceTemperature,
|
||||||
|
pub dynamic_range: FujiDynamicRange,
|
||||||
|
pub dr_priority: FujiStillDynamicRangePriority,
|
||||||
|
pub noise_reduction: FujiHighISONR,
|
||||||
|
pub grain: FujiGrainEffect,
|
||||||
|
pub color_chrome_effect: FujiColorChromeEffect,
|
||||||
|
pub color_chrome_fx_blue: FujiColorChromeFXBlue,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for FilmSimulationRepr {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
writeln!(f, "Slot: {}", self.slot)?;
|
||||||
|
writeln!(f, "Name: {}", self.name)?;
|
||||||
|
writeln!(f, "Simulation: {}", self.simulation)?;
|
||||||
|
writeln!(f, "Resolution: {}", self.resolution)?;
|
||||||
|
writeln!(f, "Quality: {}", self.quality)?;
|
||||||
|
writeln!(f, "Highlights: {}", self.highlights)?;
|
||||||
|
writeln!(f, "Shadows: {}", self.shadows)?;
|
||||||
|
writeln!(f, "Color: {}", self.color)?;
|
||||||
|
writeln!(f, "Sharpness: {}", self.sharpness)?;
|
||||||
|
writeln!(f, "Clarity: {}", self.clarity)?;
|
||||||
|
writeln!(f, "White Balance: {}", self.white_balance)?;
|
||||||
|
writeln!(
|
||||||
|
f,
|
||||||
|
"WB Shift (R/B): {} / {}",
|
||||||
|
self.wb_shift_red, self.wb_shift_blue
|
||||||
|
)?;
|
||||||
|
writeln!(f, "WB Temperature: {}K", self.wb_temperature)?;
|
||||||
|
writeln!(f, "Dynamic Range: {}", self.dynamic_range)?;
|
||||||
|
writeln!(f, "DR Priority: {}", self.dr_priority)?;
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_get(json: bool, device_id: Option<&str>, slot: FujiCustomSetting) -> anyhow::Result<()> {
|
||||||
|
let mut camera = usb::get_camera(device_id)?;
|
||||||
|
camera.set_active_custom_setting(slot)?;
|
||||||
|
|
||||||
|
let repr = FilmSimulationRepr {
|
||||||
|
slot,
|
||||||
|
name: camera.get_custom_setting_name()?,
|
||||||
|
simulation: camera.get_film_simulation()?,
|
||||||
|
resolution: camera.get_image_size()?,
|
||||||
|
quality: camera.get_image_quality()?,
|
||||||
|
highlights: camera.get_highlight_tone()?,
|
||||||
|
shadows: camera.get_shadow_tone()?,
|
||||||
|
color: camera.get_color()?,
|
||||||
|
sharpness: camera.get_sharpness()?,
|
||||||
|
clarity: camera.get_clarity()?,
|
||||||
|
white_balance: camera.get_white_balance()?,
|
||||||
|
wb_shift_red: camera.get_wb_shift_red()?,
|
||||||
|
wb_shift_blue: camera.get_wb_shift_blue()?,
|
||||||
|
wb_temperature: camera.get_wb_temperature()?,
|
||||||
|
dynamic_range: camera.get_dynamic_range()?,
|
||||||
|
dr_priority: camera.get_dynamic_range_priority()?,
|
||||||
|
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()?,
|
||||||
|
};
|
||||||
|
|
||||||
|
if json {
|
||||||
|
println!("{}", serde_json::to_string_pretty(&repr)?);
|
||||||
|
} else {
|
||||||
|
println!("{repr}");
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_set(
|
||||||
|
_device_id: Option<&str>,
|
||||||
|
_slot: FujiCustomSetting,
|
||||||
|
_opts: &FilmSimulationOptions,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
todo!();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_export(
|
||||||
|
_device_id: Option<&str>,
|
||||||
|
_slot: FujiCustomSetting,
|
||||||
|
_output: &Output,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
todo!();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_import(
|
||||||
|
_device_id: Option<&str>,
|
||||||
|
_slot: FujiCustomSetting,
|
||||||
|
_input: &Input,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
todo!();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle(cmd: SimulationCmd, json: bool, device_id: Option<&str>) -> anyhow::Result<()> {
|
||||||
|
match cmd {
|
||||||
|
SimulationCmd::List => handle_list(json, device_id),
|
||||||
|
SimulationCmd::Get { slot } => handle_get(json, device_id, slot),
|
||||||
|
SimulationCmd::Set {
|
||||||
|
slot,
|
||||||
|
film_simulation_options,
|
||||||
|
} => handle_set(device_id, slot, &film_simulation_options),
|
||||||
|
SimulationCmd::Export { slot, output_file } => handle_export(device_id, slot, &output_file),
|
||||||
|
SimulationCmd::Import { slot, input_file } => handle_import(device_id, slot, &input_file),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -19,7 +19,10 @@ fn main() -> anyhow::Result<()> {
|
|||||||
match cli.command {
|
match cli.command {
|
||||||
Commands::Device(device_cmd) => cli::device::handle(device_cmd, cli.json, device_id)?,
|
Commands::Device(device_cmd) => cli::device::handle(device_cmd, cli.json, device_id)?,
|
||||||
Commands::Backup(backup_cmd) => cli::backup::handle(backup_cmd, device_id)?,
|
Commands::Backup(backup_cmd) => cli::backup::handle(backup_cmd, device_id)?,
|
||||||
_ => todo!(),
|
Commands::Simulation(simulation_cmd) => {
|
||||||
|
cli::simulation::handle(simulation_cmd, cli.json, device_id)?;
|
||||||
|
}
|
||||||
|
Commands::Render(_) => todo!(),
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@@ -38,6 +38,6 @@ pub fn get_camera(device_id: Option<&str>) -> anyhow::Result<Camera> {
|
|||||||
None => get_connected_cameras()?
|
None => get_connected_cameras()?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.next()
|
.next()
|
||||||
.ok_or_else(|| anyhow!("No supported devices connected.")),
|
.ok_or_else(|| anyhow!("No supported devices connected")),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user