chore: reorganize cli traits
Signed-off-by: Nikolaos Karaolidis <nick@karaolidis.com>
This commit is contained in:
@@ -1,16 +1,13 @@
|
|||||||
use std::{
|
use std::{
|
||||||
fmt,
|
|
||||||
io::{self, Cursor},
|
io::{self, Cursor},
|
||||||
ops::{Deref, DerefMut},
|
ops::{Deref, DerefMut},
|
||||||
str::FromStr,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use anyhow::{Context, bail};
|
use anyhow::bail;
|
||||||
use num_enum::{IntoPrimitive, TryFromPrimitive};
|
use num_enum::{IntoPrimitive, TryFromPrimitive};
|
||||||
use ptp_cursor::{PtpDeserialize, PtpSerialize, Read};
|
use ptp_cursor::{PtpDeserialize, PtpSerialize, Read};
|
||||||
use ptp_macro::{PtpDeserialize, PtpSerialize};
|
use ptp_macro::{PtpDeserialize, PtpSerialize};
|
||||||
use serde::{Serialize, Serializer};
|
use serde::Serialize;
|
||||||
use strum::IntoEnumIterator;
|
|
||||||
use strum_macros::EnumIter;
|
use strum_macros::EnumIter;
|
||||||
|
|
||||||
#[repr(u16)]
|
#[repr(u16)]
|
||||||
@@ -166,34 +163,6 @@ pub enum DevicePropCode {
|
|||||||
FujiBatteryInfo2 = 0xD36B,
|
FujiBatteryInfo2 = 0xD36B,
|
||||||
}
|
}
|
||||||
|
|
||||||
const SIMILARITY_THRESHOLD: usize = 8;
|
|
||||||
|
|
||||||
fn suggest_closest<'a, I, S>(input: &str, choices: I) -> Option<&'a str>
|
|
||||||
where
|
|
||||||
I: IntoIterator<Item = &'a S>,
|
|
||||||
S: AsRef<str> + 'a,
|
|
||||||
{
|
|
||||||
let mut best_score = usize::MAX;
|
|
||||||
let mut best_match: Option<&'a str> = None;
|
|
||||||
|
|
||||||
for choice in choices {
|
|
||||||
let choice_str = choice.as_ref();
|
|
||||||
let dist = strsim::damerau_levenshtein(&input.to_lowercase(), &choice_str.to_lowercase());
|
|
||||||
|
|
||||||
if dist < best_score {
|
|
||||||
best_score = dist;
|
|
||||||
best_match = Some(choice_str);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
println!("{best_score}");
|
|
||||||
if best_score <= SIMILARITY_THRESHOLD {
|
|
||||||
best_match
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[repr(u16)]
|
#[repr(u16)]
|
||||||
#[derive(
|
#[derive(
|
||||||
Debug, Clone, Copy, PartialEq, Eq, IntoPrimitive, TryFromPrimitive, PtpSerialize, PtpDeserialize,
|
Debug, Clone, Copy, PartialEq, Eq, IntoPrimitive, TryFromPrimitive, PtpSerialize, PtpDeserialize,
|
||||||
@@ -233,55 +202,15 @@ pub enum FujiCustomSetting {
|
|||||||
C7 = 0x7,
|
C7 = 0x7,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for FujiCustomSetting {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
match self {
|
|
||||||
Self::C1 => write!(f, "C1"),
|
|
||||||
Self::C2 => write!(f, "C2"),
|
|
||||||
Self::C3 => write!(f, "C3"),
|
|
||||||
Self::C4 => write!(f, "C4"),
|
|
||||||
Self::C5 => write!(f, "C5"),
|
|
||||||
Self::C6 => write!(f, "C6"),
|
|
||||||
Self::C7 => write!(f, "C7"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Serialize for FujiCustomSetting {
|
|
||||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
|
||||||
where
|
|
||||||
S: Serializer,
|
|
||||||
{
|
|
||||||
serializer.serialize_u16((*self).into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromStr for FujiCustomSetting {
|
|
||||||
type Err = anyhow::Error;
|
|
||||||
|
|
||||||
fn from_str(s: &str) -> anyhow::Result<Self> {
|
|
||||||
let input = s.trim().to_lowercase();
|
|
||||||
|
|
||||||
let variant = match input.as_str() {
|
|
||||||
"c1" | "1" => Self::C1,
|
|
||||||
"c2" | "2" => Self::C2,
|
|
||||||
"c3" | "3" => Self::C3,
|
|
||||||
"c4" | "4" => Self::C4,
|
|
||||||
"c5" | "5" => Self::C5,
|
|
||||||
"c6" | "6" => Self::C6,
|
|
||||||
"c7" | "7" => Self::C7,
|
|
||||||
_ => bail!("Unknown custom setting '{s}'"),
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(variant)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, PtpSerialize, PtpDeserialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, PtpSerialize, PtpDeserialize)]
|
||||||
pub struct FujiCustomSettingName(String);
|
pub struct FujiCustomSettingName(String);
|
||||||
|
|
||||||
impl FujiCustomSettingName {
|
impl FujiCustomSettingName {
|
||||||
pub const MAX_LEN: usize = 25;
|
pub const MAX_LEN: usize = 25;
|
||||||
|
|
||||||
|
pub const unsafe fn new_unchecked(value: String) -> Self {
|
||||||
|
Self(value)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Deref for FujiCustomSettingName {
|
impl Deref for FujiCustomSettingName {
|
||||||
@@ -308,23 +237,6 @@ impl TryFrom<String> for FujiCustomSettingName {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromStr for FujiCustomSettingName {
|
|
||||||
type Err = anyhow::Error;
|
|
||||||
|
|
||||||
fn from_str(s: &str) -> anyhow::Result<Self> {
|
|
||||||
if s.len() > Self::MAX_LEN {
|
|
||||||
bail!("Value '{}' exceeds max length of {}", s, Self::MAX_LEN);
|
|
||||||
}
|
|
||||||
Ok(Self(s.to_string()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::fmt::Display for FujiCustomSettingName {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
write!(f, "{}", self.0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[repr(u16)]
|
#[repr(u16)]
|
||||||
#[derive(
|
#[derive(
|
||||||
Debug,
|
Debug,
|
||||||
@@ -356,81 +268,6 @@ pub enum FujiImageSize {
|
|||||||
R3264x2592 = 0xc,
|
R3264x2592 = 0xc,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for FujiImageSize {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
match self {
|
|
||||||
Self::R7728x5152 => write!(f, "7728x5152"),
|
|
||||||
Self::R7728x4344 => write!(f, "7728x4344"),
|
|
||||||
Self::R5152x5152 => write!(f, "5152x5152"),
|
|
||||||
Self::R6864x5152 => write!(f, "6864x5152"),
|
|
||||||
Self::R6432x5152 => write!(f, "6432x5152"),
|
|
||||||
Self::R5472x3648 => write!(f, "5472x3648"),
|
|
||||||
Self::R5472x3080 => write!(f, "5472x3080"),
|
|
||||||
Self::R3648x3648 => write!(f, "3648x3648"),
|
|
||||||
Self::R4864x3648 => write!(f, "4864x3648"),
|
|
||||||
Self::R4560x3648 => write!(f, "4560x3648"),
|
|
||||||
Self::R3888x2592 => write!(f, "3888x2592"),
|
|
||||||
Self::R3888x2184 => write!(f, "3888x2184"),
|
|
||||||
Self::R2592x2592 => write!(f, "2592x2592"),
|
|
||||||
Self::R3456x2592 => write!(f, "3456x2592"),
|
|
||||||
Self::R3264x2592 => write!(f, "3264x2592"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Serialize for FujiImageSize {
|
|
||||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
|
||||||
where
|
|
||||||
S: Serializer,
|
|
||||||
{
|
|
||||||
serializer.serialize_str(&self.to_string())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromStr for FujiImageSize {
|
|
||||||
type Err = anyhow::Error;
|
|
||||||
|
|
||||||
fn from_str(s: &str) -> anyhow::Result<Self> {
|
|
||||||
let input = s.trim().to_lowercase();
|
|
||||||
|
|
||||||
match input.as_str() {
|
|
||||||
"max" | "maximum" | "full" | "largest" => return Ok(Self::R7728x5152),
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
|
|
||||||
let input = s.replace(' ', "x").replace("by", "x");
|
|
||||||
if let Some((w_str, h_str)) = input.split_once('x')
|
|
||||||
&& let (Ok(w), Ok(h)) = (w_str.trim().parse::<u32>(), h_str.trim().parse::<u32>())
|
|
||||||
{
|
|
||||||
match (w, h) {
|
|
||||||
(7728, 5152) => return Ok(Self::R7728x5152),
|
|
||||||
(7728, 4344) => return Ok(Self::R7728x4344),
|
|
||||||
(5152, 5152) => return Ok(Self::R5152x5152),
|
|
||||||
(6864, 5152) => return Ok(Self::R6864x5152),
|
|
||||||
(6432, 5152) => return Ok(Self::R6432x5152),
|
|
||||||
(5472, 3648) => return Ok(Self::R5472x3648),
|
|
||||||
(5472, 3080) => return Ok(Self::R5472x3080),
|
|
||||||
(3648, 3648) => return Ok(Self::R3648x3648),
|
|
||||||
(4864, 3648) => return Ok(Self::R4864x3648),
|
|
||||||
(4560, 3648) => return Ok(Self::R4560x3648),
|
|
||||||
(3888, 2592) => return Ok(Self::R3888x2592),
|
|
||||||
(3888, 2184) => return Ok(Self::R3888x2184),
|
|
||||||
(2592, 2592) => return Ok(Self::R2592x2592),
|
|
||||||
(3456, 2592) => return Ok(Self::R3456x2592),
|
|
||||||
(3264, 2592) => return Ok(Self::R3264x2592),
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let choices: Vec<String> = Self::iter().map(|v| v.to_string()).collect();
|
|
||||||
if let Some(best) = suggest_closest(s, &choices) {
|
|
||||||
bail!("Unknown image size '{s}'. Did you mean '{best}'?");
|
|
||||||
}
|
|
||||||
|
|
||||||
bail!("Unknown image size '{s}'. Expected a resolution (e.g., '5472x3648') or 'maximum'.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[repr(u16)]
|
#[repr(u16)]
|
||||||
#[derive(
|
#[derive(
|
||||||
Debug,
|
Debug,
|
||||||
@@ -453,42 +290,6 @@ pub enum FujiImageQuality {
|
|||||||
Raw = 0x1,
|
Raw = 0x1,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for FujiImageQuality {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
match self {
|
|
||||||
Self::FineRaw => write!(f, "Fine + RAW"),
|
|
||||||
Self::Fine => write!(f, "Fine"),
|
|
||||||
Self::NormalRaw => write!(f, "Normal + RAW"),
|
|
||||||
Self::Normal => write!(f, "Normal"),
|
|
||||||
Self::Raw => write!(f, "RAW"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromStr for FujiImageQuality {
|
|
||||||
type Err = anyhow::Error;
|
|
||||||
|
|
||||||
fn from_str(s: &str) -> anyhow::Result<Self> {
|
|
||||||
let input = s.trim().to_lowercase().replace(['+', ' '].as_ref(), "");
|
|
||||||
|
|
||||||
match input.as_str() {
|
|
||||||
"fineraw" => return Ok(Self::FineRaw),
|
|
||||||
"fine" => return Ok(Self::Fine),
|
|
||||||
"normalraw" => return Ok(Self::NormalRaw),
|
|
||||||
"normal" => return Ok(Self::Normal),
|
|
||||||
"raw" => return Ok(Self::Raw),
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
|
|
||||||
let choices: Vec<String> = Self::iter().map(|v| v.to_string()).collect();
|
|
||||||
if let Some(best) = suggest_closest(s, &choices) {
|
|
||||||
bail!("Unknown image quality '{s}'. Did you mean '{best}'?");
|
|
||||||
}
|
|
||||||
|
|
||||||
bail!("Unknown image quality '{s}'");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[repr(u16)]
|
#[repr(u16)]
|
||||||
#[derive(
|
#[derive(
|
||||||
Debug,
|
Debug,
|
||||||
@@ -510,40 +311,6 @@ pub enum FujiDynamicRange {
|
|||||||
HDR400 = 0x190,
|
HDR400 = 0x190,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for FujiDynamicRange {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
match self {
|
|
||||||
Self::Auto => write!(f, "Auto"),
|
|
||||||
Self::HDR100 => write!(f, "HDR100"),
|
|
||||||
Self::HDR200 => write!(f, "HDR200"),
|
|
||||||
Self::HDR400 => write!(f, "HDR400"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromStr for FujiDynamicRange {
|
|
||||||
type Err = anyhow::Error;
|
|
||||||
|
|
||||||
fn from_str(s: &str) -> anyhow::Result<Self> {
|
|
||||||
let input = s.trim().to_lowercase().replace(['-', ' '].as_ref(), "");
|
|
||||||
|
|
||||||
match input.as_str() {
|
|
||||||
"auto" | "hdrauto" | "drauto" => return Ok(Self::Auto),
|
|
||||||
"100" | "hdr100" | "dr100" => return Ok(Self::HDR100),
|
|
||||||
"200" | "hdr200" | "dr200" => return Ok(Self::HDR200),
|
|
||||||
"400" | "hdr400" | "dr400" => return Ok(Self::HDR400),
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
|
|
||||||
let choices: Vec<String> = Self::iter().map(|v| v.to_string()).collect();
|
|
||||||
if let Some(best) = suggest_closest(s, &choices) {
|
|
||||||
bail!("Unknown dynamic range '{s}'. Did you mean '{best}'?");
|
|
||||||
}
|
|
||||||
|
|
||||||
bail!("Unknown dynamic range '{s}'");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[repr(u16)]
|
#[repr(u16)]
|
||||||
#[derive(
|
#[derive(
|
||||||
Debug,
|
Debug,
|
||||||
@@ -565,40 +332,6 @@ pub enum FujiDynamicRangePriority {
|
|||||||
Off = 0x0,
|
Off = 0x0,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for FujiDynamicRangePriority {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
match self {
|
|
||||||
Self::Auto => write!(f, "Auto"),
|
|
||||||
Self::Strong => write!(f, "Strong"),
|
|
||||||
Self::Weak => write!(f, "Weak"),
|
|
||||||
Self::Off => write!(f, "Off"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromStr for FujiDynamicRangePriority {
|
|
||||||
type Err = anyhow::Error;
|
|
||||||
|
|
||||||
fn from_str(s: &str) -> anyhow::Result<Self> {
|
|
||||||
let input = s.trim().to_lowercase().replace(['-', ' '].as_ref(), "");
|
|
||||||
|
|
||||||
match input.as_str() {
|
|
||||||
"auto" | "drpauto" => return Ok(Self::Auto),
|
|
||||||
"strong" | "drpstrong" => return Ok(Self::Strong),
|
|
||||||
"weak" | "drpweak" => return Ok(Self::Weak),
|
|
||||||
"off" | "drpoff" => return Ok(Self::Off),
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
|
|
||||||
let choices: Vec<String> = Self::iter().map(|v| v.to_string()).collect();
|
|
||||||
if let Some(best) = suggest_closest(s, &choices) {
|
|
||||||
bail!("Unknown dynamic range priority '{s}'. Did you mean '{best}'?");
|
|
||||||
}
|
|
||||||
|
|
||||||
bail!("Unknown dynamic range priority '{s}'");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[repr(u16)]
|
#[repr(u16)]
|
||||||
#[derive(
|
#[derive(
|
||||||
Debug,
|
Debug,
|
||||||
@@ -636,100 +369,6 @@ pub enum FujiFilmSimulation {
|
|||||||
RealaAce = 0x14,
|
RealaAce = 0x14,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for FujiFilmSimulation {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
match self {
|
|
||||||
Self::Provia => write!(f, "Provia"),
|
|
||||||
Self::Velvia => write!(f, "Velvia"),
|
|
||||||
Self::Astia => write!(f, "Astia"),
|
|
||||||
Self::PRONegHi => write!(f, "PRO Neg. Hi"),
|
|
||||||
Self::PRONegStd => write!(f, "PRO Neg. Std"),
|
|
||||||
Self::Monochrome => write!(f, "Monochrome"),
|
|
||||||
Self::MonochromeYe => write!(f, "Monochrome + Ye"),
|
|
||||||
Self::MonochromeR => write!(f, "Monochrome + R"),
|
|
||||||
Self::MonochromeG => write!(f, "Monochrome + G"),
|
|
||||||
Self::Sepia => write!(f, "Sepia"),
|
|
||||||
Self::ClassicChrome => write!(f, "Classic Chrome"),
|
|
||||||
Self::AcrosSTD => write!(f, "Acros"),
|
|
||||||
Self::AcrosYe => write!(f, "Acros + Ye"),
|
|
||||||
Self::AcrosR => write!(f, "Acros + R"),
|
|
||||||
Self::AcrosG => write!(f, "Acros + G"),
|
|
||||||
Self::Eterna => write!(f, "Eterna"),
|
|
||||||
Self::ClassicNegative => write!(f, "Classic Negative"),
|
|
||||||
Self::NostalgicNegative => write!(f, "Nostalgic Negative"),
|
|
||||||
Self::EternaBleachBypass => write!(f, "Eterna Bleach Bypass"),
|
|
||||||
Self::RealaAce => write!(f, "Reala Ace"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromStr for FujiFilmSimulation {
|
|
||||||
type Err = anyhow::Error;
|
|
||||||
|
|
||||||
fn from_str(s: &str) -> anyhow::Result<Self> {
|
|
||||||
let input = s
|
|
||||||
.trim()
|
|
||||||
.to_lowercase()
|
|
||||||
.replace([' ', '.', '+'].as_ref(), "");
|
|
||||||
|
|
||||||
match input.as_str() {
|
|
||||||
"provia" => return Ok(Self::Provia),
|
|
||||||
"velvia" => return Ok(Self::Velvia),
|
|
||||||
"astia" => return Ok(Self::Astia),
|
|
||||||
"proneghi" | "proneghigh" => {
|
|
||||||
return Ok(Self::PRONegHi);
|
|
||||||
}
|
|
||||||
"pronegstd" | "pronegstandard" => {
|
|
||||||
return Ok(Self::PRONegStd);
|
|
||||||
}
|
|
||||||
"mono" | "monochrome" => return Ok(Self::Monochrome),
|
|
||||||
"monoy" | "monoye" | "monoyellow" | "monochromey" | "monochromeye"
|
|
||||||
| "monochromeyellow" => {
|
|
||||||
return Ok(Self::MonochromeYe);
|
|
||||||
}
|
|
||||||
"monor" | "monored" | "monochromer" | "monochromered" => {
|
|
||||||
return Ok(Self::MonochromeR);
|
|
||||||
}
|
|
||||||
"monog" | "monogreen" | "monochromeg" | "monochromegreen" => {
|
|
||||||
return Ok(Self::MonochromeG);
|
|
||||||
}
|
|
||||||
"sepia" => return Ok(Self::Sepia),
|
|
||||||
"classicchrome" => return Ok(Self::ClassicChrome),
|
|
||||||
"acros" => return Ok(Self::AcrosSTD),
|
|
||||||
"acrosy" | "acrosye" | "acrosyellow" => {
|
|
||||||
return Ok(Self::AcrosYe);
|
|
||||||
}
|
|
||||||
"acrossr" | "acrossred" => {
|
|
||||||
return Ok(Self::AcrosR);
|
|
||||||
}
|
|
||||||
"acrossg" | "acrossgreen" => {
|
|
||||||
return Ok(Self::AcrosG);
|
|
||||||
}
|
|
||||||
"eterna" => return Ok(Self::Eterna),
|
|
||||||
"classicneg" | "classicnegative" => {
|
|
||||||
return Ok(Self::ClassicNegative);
|
|
||||||
}
|
|
||||||
"nostalgicneg" | "nostalgicnegative" => {
|
|
||||||
return Ok(Self::NostalgicNegative);
|
|
||||||
}
|
|
||||||
"eternabb" | "eternableach" | "eternableachbypass" => {
|
|
||||||
return Ok(Self::EternaBleachBypass);
|
|
||||||
}
|
|
||||||
"realaace" => {
|
|
||||||
return Ok(Self::RealaAce);
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
|
|
||||||
let choices: Vec<String> = Self::iter().map(|v| v.to_string()).collect();
|
|
||||||
if let Some(best) = suggest_closest(s, &choices) {
|
|
||||||
bail!("Unknown value '{s}'. Did you mean '{best}'?");
|
|
||||||
}
|
|
||||||
|
|
||||||
bail!("Unknown value '{input}'");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[repr(u16)]
|
#[repr(u16)]
|
||||||
#[derive(
|
#[derive(
|
||||||
Debug,
|
Debug,
|
||||||
@@ -752,45 +391,6 @@ pub enum FujiGrainEffect {
|
|||||||
Off = 0x6,
|
Off = 0x6,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for FujiGrainEffect {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
match self {
|
|
||||||
Self::StrongLarge => write!(f, "Strong Large"),
|
|
||||||
Self::WeakLarge => write!(f, "Weak Large"),
|
|
||||||
Self::StrongSmall => write!(f, "Strong Small"),
|
|
||||||
Self::WeakSmall => write!(f, "Weak Small"),
|
|
||||||
Self::Off => write!(f, "Off"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromStr for FujiGrainEffect {
|
|
||||||
type Err = anyhow::Error;
|
|
||||||
|
|
||||||
fn from_str(s: &str) -> anyhow::Result<Self> {
|
|
||||||
let input = s
|
|
||||||
.trim()
|
|
||||||
.to_lowercase()
|
|
||||||
.replace(['+', '-', ',', ' '].as_ref(), "");
|
|
||||||
|
|
||||||
match input.as_str() {
|
|
||||||
"stronglarge" | "largestrong" => return Ok(Self::StrongLarge),
|
|
||||||
"weaklarge" | "largeweak" => return Ok(Self::WeakLarge),
|
|
||||||
"strongsmall" | "smallstrong" => return Ok(Self::StrongSmall),
|
|
||||||
"weaksmall" | "smallweak" => return Ok(Self::WeakSmall),
|
|
||||||
"off" => return Ok(Self::Off),
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
|
|
||||||
let choices: Vec<String> = Self::iter().map(|v| v.to_string()).collect();
|
|
||||||
if let Some(best) = suggest_closest(&input, &choices) {
|
|
||||||
bail!("Unknown grain effect '{s}'. Did you mean '{best}'?");
|
|
||||||
}
|
|
||||||
|
|
||||||
bail!("Unknown grain effect '{s}'");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[repr(u16)]
|
#[repr(u16)]
|
||||||
#[derive(
|
#[derive(
|
||||||
Debug,
|
Debug,
|
||||||
@@ -811,38 +411,6 @@ pub enum FujiColorChromeEffect {
|
|||||||
Off = 0x1,
|
Off = 0x1,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for FujiColorChromeEffect {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
match self {
|
|
||||||
Self::Strong => write!(f, "Strong"),
|
|
||||||
Self::Weak => write!(f, "Weak"),
|
|
||||||
Self::Off => write!(f, "Off"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromStr for FujiColorChromeEffect {
|
|
||||||
type Err = anyhow::Error;
|
|
||||||
|
|
||||||
fn from_str(s: &str) -> anyhow::Result<Self> {
|
|
||||||
let input = s.trim().to_lowercase();
|
|
||||||
|
|
||||||
match input.as_str() {
|
|
||||||
"strong" => return Ok(Self::Strong),
|
|
||||||
"weak" => return Ok(Self::Weak),
|
|
||||||
"off" => return Ok(Self::Off),
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
|
|
||||||
let choices: Vec<String> = Self::iter().map(|v| v.to_string()).collect();
|
|
||||||
if let Some(best) = suggest_closest(s, &choices) {
|
|
||||||
bail!("Unknown color chrome effect '{s}'. Did you mean '{best}'?");
|
|
||||||
}
|
|
||||||
|
|
||||||
bail!("Unknown color chrome effect '{s}'");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[repr(u16)]
|
#[repr(u16)]
|
||||||
#[derive(
|
#[derive(
|
||||||
Debug,
|
Debug,
|
||||||
@@ -863,38 +431,6 @@ pub enum FujiColorChromeFXBlue {
|
|||||||
Off = 0x1,
|
Off = 0x1,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for FujiColorChromeFXBlue {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
match self {
|
|
||||||
Self::Strong => write!(f, "Strong"),
|
|
||||||
Self::Weak => write!(f, "Weak"),
|
|
||||||
Self::Off => write!(f, "Off"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromStr for FujiColorChromeFXBlue {
|
|
||||||
type Err = anyhow::Error;
|
|
||||||
|
|
||||||
fn from_str(s: &str) -> anyhow::Result<Self> {
|
|
||||||
let input = s.trim().to_lowercase();
|
|
||||||
|
|
||||||
match input.as_str() {
|
|
||||||
"strong" => return Ok(Self::Strong),
|
|
||||||
"weak" => return Ok(Self::Weak),
|
|
||||||
"off" => return Ok(Self::Off),
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
|
|
||||||
let choices: Vec<String> = Self::iter().map(|v| v.to_string()).collect();
|
|
||||||
if let Some(best) = suggest_closest(s, &choices) {
|
|
||||||
bail!("Unknown color chrome fx blue '{s}'. Did you mean '{best}'?");
|
|
||||||
}
|
|
||||||
|
|
||||||
bail!("Unknown color chrome fx blue '{s}'");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[repr(u16)]
|
#[repr(u16)]
|
||||||
#[derive(
|
#[derive(
|
||||||
Debug,
|
Debug,
|
||||||
@@ -915,38 +451,6 @@ pub enum FujiSmoothSkinEffect {
|
|||||||
Off = 0x1,
|
Off = 0x1,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for FujiSmoothSkinEffect {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
match self {
|
|
||||||
Self::Strong => write!(f, "Strong"),
|
|
||||||
Self::Weak => write!(f, "Weak"),
|
|
||||||
Self::Off => write!(f, "Off"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromStr for FujiSmoothSkinEffect {
|
|
||||||
type Err = anyhow::Error;
|
|
||||||
|
|
||||||
fn from_str(s: &str) -> anyhow::Result<Self> {
|
|
||||||
let input = s.trim().to_lowercase();
|
|
||||||
|
|
||||||
match input.as_str() {
|
|
||||||
"strong" => return Ok(Self::Strong),
|
|
||||||
"weak" => return Ok(Self::Weak),
|
|
||||||
"off" => return Ok(Self::Off),
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
|
|
||||||
let choices: Vec<String> = Self::iter().map(|v| v.to_string()).collect();
|
|
||||||
if let Some(best) = suggest_closest(s, &choices) {
|
|
||||||
bail!("Unknown smooth skin effect '{s}'. Did you mean '{best}'?");
|
|
||||||
}
|
|
||||||
|
|
||||||
bail!("Unknown smooth skin effect '{s}'");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[repr(u16)]
|
#[repr(u16)]
|
||||||
#[derive(
|
#[derive(
|
||||||
Debug,
|
Debug,
|
||||||
@@ -962,6 +466,7 @@ impl FromStr for FujiSmoothSkinEffect {
|
|||||||
EnumIter,
|
EnumIter,
|
||||||
)]
|
)]
|
||||||
pub enum FujiWhiteBalance {
|
pub enum FujiWhiteBalance {
|
||||||
|
AsShot = 0x1,
|
||||||
WhitePriority = 0x8020,
|
WhitePriority = 0x8020,
|
||||||
Auto = 0x2,
|
Auto = 0x2,
|
||||||
AmbiencePriority = 0x8021,
|
AmbiencePriority = 0x8021,
|
||||||
@@ -978,67 +483,6 @@ pub enum FujiWhiteBalance {
|
|||||||
Underwater = 0x8,
|
Underwater = 0x8,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for FujiWhiteBalance {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
match self {
|
|
||||||
Self::WhitePriority => write!(f, "White Priority"),
|
|
||||||
Self::Auto => write!(f, "Auto"),
|
|
||||||
Self::AmbiencePriority => write!(f, "Ambience Priority"),
|
|
||||||
Self::Custom1 => write!(f, "Custom 1"),
|
|
||||||
Self::Custom2 => write!(f, "Custom 2"),
|
|
||||||
Self::Custom3 => write!(f, "Custom 3"),
|
|
||||||
Self::Temperature => write!(f, "Temperature"),
|
|
||||||
Self::Daylight => write!(f, "Daylight"),
|
|
||||||
Self::Shade => write!(f, "Shade"),
|
|
||||||
Self::Fluorescent1 => write!(f, "Fluorescent 1"),
|
|
||||||
Self::Fluorescent2 => write!(f, "Fluorescent 2"),
|
|
||||||
Self::Fluorescent3 => write!(f, "Fluorescent 3"),
|
|
||||||
Self::Incandescent => write!(f, "Incandescent"),
|
|
||||||
Self::Underwater => write!(f, "Underwater"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl FromStr for FujiWhiteBalance {
|
|
||||||
type Err = anyhow::Error;
|
|
||||||
|
|
||||||
fn from_str(s: &str) -> anyhow::Result<Self> {
|
|
||||||
let input = s.trim().to_lowercase().replace(['-', ' '].as_ref(), "");
|
|
||||||
|
|
||||||
match input.as_str() {
|
|
||||||
"whitepriority" | "white" => return Ok(Self::WhitePriority),
|
|
||||||
"auto" => return Ok(Self::Auto),
|
|
||||||
"ambiencepriority" | "ambience" | "ambient" => {
|
|
||||||
return Ok(Self::AmbiencePriority);
|
|
||||||
}
|
|
||||||
"custom1" | "c1" => return Ok(Self::Custom1),
|
|
||||||
"custom2" | "c2" => return Ok(Self::Custom2),
|
|
||||||
"custom3" | "c3" => return Ok(Self::Custom3),
|
|
||||||
"temperature" | "k" | "kelvin" => return Ok(Self::Temperature),
|
|
||||||
"daylight" | "sunny" => return Ok(Self::Daylight),
|
|
||||||
"shade" | "cloudy" => return Ok(Self::Shade),
|
|
||||||
"fluorescent1" => {
|
|
||||||
return Ok(Self::Fluorescent1);
|
|
||||||
}
|
|
||||||
"fluorescent2" => {
|
|
||||||
return Ok(Self::Fluorescent2);
|
|
||||||
}
|
|
||||||
"fluorescent3" => {
|
|
||||||
return Ok(Self::Fluorescent3);
|
|
||||||
}
|
|
||||||
"incandescent" | "tungsten" => return Ok(Self::Incandescent),
|
|
||||||
"underwater" => return Ok(Self::Underwater),
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
|
|
||||||
let choices: Vec<String> = Self::iter().map(|v| v.to_string()).collect();
|
|
||||||
if let Some(best) = suggest_closest(s, &choices) {
|
|
||||||
bail!("Unknown white balance '{s}'. Did you mean '{best}'?");
|
|
||||||
}
|
|
||||||
|
|
||||||
bail!("Unknown white balance '{s}'");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[repr(u16)]
|
#[repr(u16)]
|
||||||
#[derive(
|
#[derive(
|
||||||
Debug, Clone, Copy, PartialEq, Eq, IntoPrimitive, TryFromPrimitive, PtpSerialize, PtpDeserialize,
|
Debug, Clone, Copy, PartialEq, Eq, IntoPrimitive, TryFromPrimitive, PtpSerialize, PtpDeserialize,
|
||||||
@@ -1055,65 +499,6 @@ pub enum FujiHighISONR {
|
|||||||
Minus4 = 0x8000,
|
Minus4 = 0x8000,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for FujiHighISONR {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
match self {
|
|
||||||
Self::Plus4 => write!(f, "+4"),
|
|
||||||
Self::Plus3 => write!(f, "+3"),
|
|
||||||
Self::Plus2 => write!(f, "+2"),
|
|
||||||
Self::Plus1 => write!(f, "+1"),
|
|
||||||
Self::Zero => write!(f, "0"),
|
|
||||||
Self::Minus1 => write!(f, "-1"),
|
|
||||||
Self::Minus2 => write!(f, "-2"),
|
|
||||||
Self::Minus3 => write!(f, "-3"),
|
|
||||||
Self::Minus4 => write!(f, "-4"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Serialize for FujiHighISONR {
|
|
||||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
|
||||||
where
|
|
||||||
S: Serializer,
|
|
||||||
{
|
|
||||||
match self {
|
|
||||||
Self::Plus4 => serializer.serialize_i16(4),
|
|
||||||
Self::Plus3 => serializer.serialize_i16(3),
|
|
||||||
Self::Plus2 => serializer.serialize_i16(2),
|
|
||||||
Self::Plus1 => serializer.serialize_i16(1),
|
|
||||||
Self::Zero => serializer.serialize_i16(0),
|
|
||||||
Self::Minus1 => serializer.serialize_i16(-1),
|
|
||||||
Self::Minus2 => serializer.serialize_i16(-2),
|
|
||||||
Self::Minus3 => serializer.serialize_i16(-3),
|
|
||||||
Self::Minus4 => serializer.serialize_i16(-4),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromStr for FujiHighISONR {
|
|
||||||
type Err = anyhow::Error;
|
|
||||||
|
|
||||||
fn from_str(s: &str) -> anyhow::Result<Self> {
|
|
||||||
let input = s
|
|
||||||
.trim()
|
|
||||||
.parse::<i16>()
|
|
||||||
.with_context(|| format!("Invalid numeric value '{s}'"))?;
|
|
||||||
|
|
||||||
match input {
|
|
||||||
4 => Ok(Self::Plus4),
|
|
||||||
3 => Ok(Self::Plus3),
|
|
||||||
2 => Ok(Self::Plus2),
|
|
||||||
1 => Ok(Self::Plus1),
|
|
||||||
0 => Ok(Self::Zero),
|
|
||||||
-1 => Ok(Self::Minus1),
|
|
||||||
-2 => Ok(Self::Minus2),
|
|
||||||
-3 => Ok(Self::Minus3),
|
|
||||||
-4 => Ok(Self::Minus4),
|
|
||||||
_ => bail!("Value {input} is out of range",),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[repr(u16)]
|
#[repr(u16)]
|
||||||
#[derive(
|
#[derive(
|
||||||
Debug,
|
Debug,
|
||||||
@@ -1133,36 +518,6 @@ pub enum FujiLensModulationOptimizer {
|
|||||||
On = 0x1,
|
On = 0x1,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for FujiLensModulationOptimizer {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
match self {
|
|
||||||
Self::Off => write!(f, "Off"),
|
|
||||||
Self::On => write!(f, "On"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromStr for FujiLensModulationOptimizer {
|
|
||||||
type Err = anyhow::Error;
|
|
||||||
|
|
||||||
fn from_str(s: &str) -> anyhow::Result<Self> {
|
|
||||||
let input = s.trim().to_lowercase();
|
|
||||||
|
|
||||||
match input.as_str() {
|
|
||||||
"off" | "false" => return Ok(Self::Off),
|
|
||||||
"on" | "true" => return Ok(Self::On),
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
|
|
||||||
let choices: Vec<String> = Self::iter().map(|v| v.to_string()).collect();
|
|
||||||
if let Some(best) = suggest_closest(s, &choices) {
|
|
||||||
bail!("Unknown lens modulation optimizer '{s}'. Did you mean '{best}'?");
|
|
||||||
}
|
|
||||||
|
|
||||||
bail!("Unknown lens modulation optimizer '{s}'");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[repr(u16)]
|
#[repr(u16)]
|
||||||
#[derive(
|
#[derive(
|
||||||
Debug,
|
Debug,
|
||||||
@@ -1177,41 +532,12 @@ impl FromStr for FujiLensModulationOptimizer {
|
|||||||
PtpDeserialize,
|
PtpDeserialize,
|
||||||
EnumIter,
|
EnumIter,
|
||||||
)]
|
)]
|
||||||
|
#[allow(clippy::upper_case_acronyms)]
|
||||||
pub enum FujiColorSpace {
|
pub enum FujiColorSpace {
|
||||||
SRGB = 0x2,
|
SRGB = 0x2,
|
||||||
AdobeRGB = 0x1,
|
AdobeRGB = 0x1,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for FujiColorSpace {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
match self {
|
|
||||||
Self::SRGB => write!(f, "sRGB"),
|
|
||||||
Self::AdobeRGB => write!(f, "Adobe RGB"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromStr for FujiColorSpace {
|
|
||||||
type Err = anyhow::Error;
|
|
||||||
|
|
||||||
fn from_str(s: &str) -> anyhow::Result<Self> {
|
|
||||||
let input = s.trim().to_lowercase();
|
|
||||||
|
|
||||||
match input.as_str() {
|
|
||||||
"s" | "srgb" => return Ok(Self::SRGB),
|
|
||||||
"adobe" | "adobergb" => return Ok(Self::AdobeRGB),
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
|
|
||||||
let choices: Vec<String> = Self::iter().map(|v| v.to_string()).collect();
|
|
||||||
if let Some(best) = suggest_closest(s, &choices) {
|
|
||||||
bail!("Unknown color space '{s}'. Did you mean '{best}'?");
|
|
||||||
}
|
|
||||||
|
|
||||||
bail!("Unknown color space '{s}'");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! fuji_i16 {
|
macro_rules! fuji_i16 {
|
||||||
($name:ident, $min:expr, $max:expr, $step:expr, $scale:literal) => {
|
($name:ident, $min:expr, $max:expr, $step:expr, $scale:literal) => {
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PtpSerialize, PtpDeserialize)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, PtpSerialize, PtpDeserialize)]
|
||||||
@@ -1263,49 +589,6 @@ macro_rules! fuji_i16 {
|
|||||||
Ok(Self(value))
|
Ok(Self(value))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Display for $name {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
let value = (f32::from(self.0) / Self::SCALE);
|
|
||||||
write!(f, "{}", value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::str::FromStr for $name {
|
|
||||||
type Err = anyhow::Error;
|
|
||||||
|
|
||||||
fn from_str(s: &str) -> anyhow::Result<Self> {
|
|
||||||
use anyhow::Context;
|
|
||||||
|
|
||||||
let input = s
|
|
||||||
.trim()
|
|
||||||
.parse::<f32>()
|
|
||||||
.with_context(|| format!("Invalid numeric value '{s}'"))?;
|
|
||||||
|
|
||||||
if !(Self::MIN..=Self::MAX).contains(&input) {
|
|
||||||
anyhow::bail!("Value {} is out of range", input);
|
|
||||||
}
|
|
||||||
#[allow(clippy::modulo_one)]
|
|
||||||
if (input - Self::MIN) % Self::STEP != 0.0 {
|
|
||||||
anyhow::bail!("Value {} is not aligned to step {}", input, Self::STEP);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(clippy::cast_possible_truncation)]
|
|
||||||
let raw = (input * Self::SCALE).round() as i16;
|
|
||||||
|
|
||||||
unsafe { Ok(Self::new_unchecked(raw)) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl serde::Serialize for $name {
|
|
||||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
|
||||||
where
|
|
||||||
S: serde::Serializer,
|
|
||||||
{
|
|
||||||
let val = f32::from(self.0) / Self::SCALE;
|
|
||||||
serializer.serialize_f32(val)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1327,15 +610,6 @@ pub enum UsbMode {
|
|||||||
RawConversion = 0x6,
|
RawConversion = 0x6,
|
||||||
}
|
}
|
||||||
|
|
||||||
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",
|
|
||||||
};
|
|
||||||
write!(f, "{s}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(
|
#[derive(
|
||||||
Debug, Clone, Copy, PartialEq, Eq, IntoPrimitive, TryFromPrimitive, PtpSerialize, PtpDeserialize,
|
Debug, Clone, Copy, PartialEq, Eq, IntoPrimitive, TryFromPrimitive, PtpSerialize, PtpDeserialize,
|
||||||
)]
|
)]
|
||||||
|
@@ -1,11 +1,20 @@
|
|||||||
use clap::Args;
|
use std::{fmt, ops::Deref, str::FromStr};
|
||||||
|
|
||||||
use crate::camera::ptp::hex::{
|
use anyhow::{Context, bail};
|
||||||
FujiClarity, FujiColor, FujiColorChromeEffect, FujiColorChromeFXBlue, FujiColorSpace,
|
use clap::Args;
|
||||||
FujiDynamicRange, FujiDynamicRangePriority, FujiFilmSimulation, FujiGrainEffect, FujiHighISONR,
|
use serde::{Serialize, Serializer};
|
||||||
FujiHighlightTone, FujiImageQuality, FujiImageSize, FujiLensModulationOptimizer,
|
use strum::IntoEnumIterator;
|
||||||
FujiMonochromaticColorTemperature, FujiMonochromaticColorTint, FujiShadowTone, FujiSharpness,
|
|
||||||
FujiSmoothSkinEffect, FujiWhiteBalance, FujiWhiteBalanceShift, FujiWhiteBalanceTemperature,
|
use crate::{
|
||||||
|
camera::ptp::hex::{
|
||||||
|
FujiClarity, FujiColor, FujiColorChromeEffect, FujiColorChromeFXBlue, FujiColorSpace,
|
||||||
|
FujiCustomSetting, FujiCustomSettingName, FujiDynamicRange, FujiDynamicRangePriority,
|
||||||
|
FujiFilmSimulation, FujiGrainEffect, FujiHighISONR, FujiHighlightTone, FujiImageQuality,
|
||||||
|
FujiImageSize, FujiLensModulationOptimizer, FujiMonochromaticColorTemperature,
|
||||||
|
FujiMonochromaticColorTint, FujiShadowTone, FujiSharpness, FujiSmoothSkinEffect,
|
||||||
|
FujiWhiteBalance, FujiWhiteBalanceShift, FujiWhiteBalanceTemperature, UsbMode,
|
||||||
|
},
|
||||||
|
cli::common::suggest::get_closest,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Args, Debug)]
|
#[derive(Args, Debug)]
|
||||||
@@ -102,3 +111,721 @@ pub struct FilmSimulationOptions {
|
|||||||
#[clap(long)]
|
#[clap(long)]
|
||||||
pub color_space: Option<FujiColorSpace>,
|
pub color_space: Option<FujiColorSpace>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for FujiCustomSetting {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::C1 => write!(f, "C1"),
|
||||||
|
Self::C2 => write!(f, "C2"),
|
||||||
|
Self::C3 => write!(f, "C3"),
|
||||||
|
Self::C4 => write!(f, "C4"),
|
||||||
|
Self::C5 => write!(f, "C5"),
|
||||||
|
Self::C6 => write!(f, "C6"),
|
||||||
|
Self::C7 => write!(f, "C7"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for FujiCustomSetting {
|
||||||
|
type Err = anyhow::Error;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> anyhow::Result<Self> {
|
||||||
|
let input = s.trim().to_lowercase();
|
||||||
|
|
||||||
|
let variant = match input.as_str() {
|
||||||
|
"c1" | "1" => Self::C1,
|
||||||
|
"c2" | "2" => Self::C2,
|
||||||
|
"c3" | "3" => Self::C3,
|
||||||
|
"c4" | "4" => Self::C4,
|
||||||
|
"c5" | "5" => Self::C5,
|
||||||
|
"c6" | "6" => Self::C6,
|
||||||
|
"c7" | "7" => Self::C7,
|
||||||
|
_ => bail!("Unknown custom setting '{s}'"),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(variant)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Serialize for FujiCustomSetting {
|
||||||
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: Serializer,
|
||||||
|
{
|
||||||
|
serializer.serialize_u16((*self).into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for FujiCustomSettingName {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "{}", &**self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for FujiCustomSettingName {
|
||||||
|
type Err = anyhow::Error;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> anyhow::Result<Self> {
|
||||||
|
if s.len() > Self::MAX_LEN {
|
||||||
|
bail!("Value '{}' exceeds max length of {}", s, Self::MAX_LEN);
|
||||||
|
}
|
||||||
|
Ok(unsafe { Self::new_unchecked(s.to_string()) })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for FujiImageSize {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::R7728x5152 => write!(f, "7728x5152"),
|
||||||
|
Self::R7728x4344 => write!(f, "7728x4344"),
|
||||||
|
Self::R5152x5152 => write!(f, "5152x5152"),
|
||||||
|
Self::R6864x5152 => write!(f, "6864x5152"),
|
||||||
|
Self::R6432x5152 => write!(f, "6432x5152"),
|
||||||
|
Self::R5472x3648 => write!(f, "5472x3648"),
|
||||||
|
Self::R5472x3080 => write!(f, "5472x3080"),
|
||||||
|
Self::R3648x3648 => write!(f, "3648x3648"),
|
||||||
|
Self::R4864x3648 => write!(f, "4864x3648"),
|
||||||
|
Self::R4560x3648 => write!(f, "4560x3648"),
|
||||||
|
Self::R3888x2592 => write!(f, "3888x2592"),
|
||||||
|
Self::R3888x2184 => write!(f, "3888x2184"),
|
||||||
|
Self::R2592x2592 => write!(f, "2592x2592"),
|
||||||
|
Self::R3456x2592 => write!(f, "3456x2592"),
|
||||||
|
Self::R3264x2592 => write!(f, "3264x2592"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for FujiImageSize {
|
||||||
|
type Err = anyhow::Error;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> anyhow::Result<Self> {
|
||||||
|
let input = s.trim().to_lowercase();
|
||||||
|
|
||||||
|
match input.as_str() {
|
||||||
|
"max" | "maximum" | "full" | "largest" => return Ok(Self::R7728x5152),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
let input = s.replace(' ', "x").replace("by", "x");
|
||||||
|
if let Some((w_str, h_str)) = input.split_once('x')
|
||||||
|
&& let (Ok(w), Ok(h)) = (w_str.trim().parse::<u32>(), h_str.trim().parse::<u32>())
|
||||||
|
{
|
||||||
|
match (w, h) {
|
||||||
|
(7728, 5152) => return Ok(Self::R7728x5152),
|
||||||
|
(7728, 4344) => return Ok(Self::R7728x4344),
|
||||||
|
(5152, 5152) => return Ok(Self::R5152x5152),
|
||||||
|
(6864, 5152) => return Ok(Self::R6864x5152),
|
||||||
|
(6432, 5152) => return Ok(Self::R6432x5152),
|
||||||
|
(5472, 3648) => return Ok(Self::R5472x3648),
|
||||||
|
(5472, 3080) => return Ok(Self::R5472x3080),
|
||||||
|
(3648, 3648) => return Ok(Self::R3648x3648),
|
||||||
|
(4864, 3648) => return Ok(Self::R4864x3648),
|
||||||
|
(4560, 3648) => return Ok(Self::R4560x3648),
|
||||||
|
(3888, 2592) => return Ok(Self::R3888x2592),
|
||||||
|
(3888, 2184) => return Ok(Self::R3888x2184),
|
||||||
|
(2592, 2592) => return Ok(Self::R2592x2592),
|
||||||
|
(3456, 2592) => return Ok(Self::R3456x2592),
|
||||||
|
(3264, 2592) => return Ok(Self::R3264x2592),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let choices: Vec<String> = Self::iter().map(|v| v.to_string()).collect();
|
||||||
|
if let Some(best) = get_closest(s, &choices) {
|
||||||
|
bail!("Unknown image size '{s}'. Did you mean '{best}'?");
|
||||||
|
}
|
||||||
|
|
||||||
|
bail!("Unknown image size '{s}'. Expected a resolution (e.g., '5472x3648') or 'maximum'.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Serialize for FujiImageSize {
|
||||||
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: Serializer,
|
||||||
|
{
|
||||||
|
serializer.serialize_str(&self.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for FujiImageQuality {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::FineRaw => write!(f, "Fine + RAW"),
|
||||||
|
Self::Fine => write!(f, "Fine"),
|
||||||
|
Self::NormalRaw => write!(f, "Normal + RAW"),
|
||||||
|
Self::Normal => write!(f, "Normal"),
|
||||||
|
Self::Raw => write!(f, "RAW"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for FujiImageQuality {
|
||||||
|
type Err = anyhow::Error;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> anyhow::Result<Self> {
|
||||||
|
let input = s.trim().to_lowercase().replace(['+', ' '].as_ref(), "");
|
||||||
|
|
||||||
|
match input.as_str() {
|
||||||
|
"fineraw" => return Ok(Self::FineRaw),
|
||||||
|
"fine" => return Ok(Self::Fine),
|
||||||
|
"normalraw" => return Ok(Self::NormalRaw),
|
||||||
|
"normal" => return Ok(Self::Normal),
|
||||||
|
"raw" => return Ok(Self::Raw),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
let choices: Vec<String> = Self::iter().map(|v| v.to_string()).collect();
|
||||||
|
if let Some(best) = get_closest(s, &choices) {
|
||||||
|
bail!("Unknown image quality '{s}'. Did you mean '{best}'?");
|
||||||
|
}
|
||||||
|
|
||||||
|
bail!("Unknown image quality '{s}'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for FujiDynamicRange {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::Auto => write!(f, "Auto"),
|
||||||
|
Self::HDR100 => write!(f, "HDR100"),
|
||||||
|
Self::HDR200 => write!(f, "HDR200"),
|
||||||
|
Self::HDR400 => write!(f, "HDR400"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for FujiDynamicRange {
|
||||||
|
type Err = anyhow::Error;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> anyhow::Result<Self> {
|
||||||
|
let input = s.trim().to_lowercase().replace(['-', ' '].as_ref(), "");
|
||||||
|
|
||||||
|
match input.as_str() {
|
||||||
|
"auto" | "hdrauto" | "drauto" => return Ok(Self::Auto),
|
||||||
|
"100" | "hdr100" | "dr100" => return Ok(Self::HDR100),
|
||||||
|
"200" | "hdr200" | "dr200" => return Ok(Self::HDR200),
|
||||||
|
"400" | "hdr400" | "dr400" => return Ok(Self::HDR400),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
let choices: Vec<String> = Self::iter().map(|v| v.to_string()).collect();
|
||||||
|
if let Some(best) = get_closest(s, &choices) {
|
||||||
|
bail!("Unknown dynamic range '{s}'. Did you mean '{best}'?");
|
||||||
|
}
|
||||||
|
|
||||||
|
bail!("Unknown dynamic range '{s}'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for FujiDynamicRangePriority {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::Auto => write!(f, "Auto"),
|
||||||
|
Self::Strong => write!(f, "Strong"),
|
||||||
|
Self::Weak => write!(f, "Weak"),
|
||||||
|
Self::Off => write!(f, "Off"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for FujiDynamicRangePriority {
|
||||||
|
type Err = anyhow::Error;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> anyhow::Result<Self> {
|
||||||
|
let input = s.trim().to_lowercase().replace(['-', ' '].as_ref(), "");
|
||||||
|
|
||||||
|
match input.as_str() {
|
||||||
|
"auto" | "drpauto" => return Ok(Self::Auto),
|
||||||
|
"strong" | "drpstrong" => return Ok(Self::Strong),
|
||||||
|
"weak" | "drpweak" => return Ok(Self::Weak),
|
||||||
|
"off" | "drpoff" => return Ok(Self::Off),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
let choices: Vec<String> = Self::iter().map(|v| v.to_string()).collect();
|
||||||
|
if let Some(best) = get_closest(s, &choices) {
|
||||||
|
bail!("Unknown dynamic range priority '{s}'. Did you mean '{best}'?");
|
||||||
|
}
|
||||||
|
|
||||||
|
bail!("Unknown dynamic range priority '{s}'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for FujiFilmSimulation {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::Provia => write!(f, "Provia"),
|
||||||
|
Self::Velvia => write!(f, "Velvia"),
|
||||||
|
Self::Astia => write!(f, "Astia"),
|
||||||
|
Self::PRONegHi => write!(f, "PRO Neg. Hi"),
|
||||||
|
Self::PRONegStd => write!(f, "PRO Neg. Std"),
|
||||||
|
Self::Monochrome => write!(f, "Monochrome"),
|
||||||
|
Self::MonochromeYe => write!(f, "Monochrome + Ye"),
|
||||||
|
Self::MonochromeR => write!(f, "Monochrome + R"),
|
||||||
|
Self::MonochromeG => write!(f, "Monochrome + G"),
|
||||||
|
Self::Sepia => write!(f, "Sepia"),
|
||||||
|
Self::ClassicChrome => write!(f, "Classic Chrome"),
|
||||||
|
Self::AcrosSTD => write!(f, "Acros"),
|
||||||
|
Self::AcrosYe => write!(f, "Acros + Ye"),
|
||||||
|
Self::AcrosR => write!(f, "Acros + R"),
|
||||||
|
Self::AcrosG => write!(f, "Acros + G"),
|
||||||
|
Self::Eterna => write!(f, "Eterna"),
|
||||||
|
Self::ClassicNegative => write!(f, "Classic Negative"),
|
||||||
|
Self::NostalgicNegative => write!(f, "Nostalgic Negative"),
|
||||||
|
Self::EternaBleachBypass => write!(f, "Eterna Bleach Bypass"),
|
||||||
|
Self::RealaAce => write!(f, "Reala Ace"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for FujiFilmSimulation {
|
||||||
|
type Err = anyhow::Error;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> anyhow::Result<Self> {
|
||||||
|
let input = s
|
||||||
|
.trim()
|
||||||
|
.to_lowercase()
|
||||||
|
.replace([' ', '.', '+'].as_ref(), "");
|
||||||
|
|
||||||
|
match input.as_str() {
|
||||||
|
"provia" => return Ok(Self::Provia),
|
||||||
|
"velvia" => return Ok(Self::Velvia),
|
||||||
|
"astia" => return Ok(Self::Astia),
|
||||||
|
"proneghi" | "proneghigh" => {
|
||||||
|
return Ok(Self::PRONegHi);
|
||||||
|
}
|
||||||
|
"pronegstd" | "pronegstandard" => {
|
||||||
|
return Ok(Self::PRONegStd);
|
||||||
|
}
|
||||||
|
"mono" | "monochrome" => return Ok(Self::Monochrome),
|
||||||
|
"monoy" | "monoye" | "monoyellow" | "monochromey" | "monochromeye"
|
||||||
|
| "monochromeyellow" => {
|
||||||
|
return Ok(Self::MonochromeYe);
|
||||||
|
}
|
||||||
|
"monor" | "monored" | "monochromer" | "monochromered" => {
|
||||||
|
return Ok(Self::MonochromeR);
|
||||||
|
}
|
||||||
|
"monog" | "monogreen" | "monochromeg" | "monochromegreen" => {
|
||||||
|
return Ok(Self::MonochromeG);
|
||||||
|
}
|
||||||
|
"sepia" => return Ok(Self::Sepia),
|
||||||
|
"classicchrome" => return Ok(Self::ClassicChrome),
|
||||||
|
"acros" => return Ok(Self::AcrosSTD),
|
||||||
|
"acrosy" | "acrosye" | "acrosyellow" => {
|
||||||
|
return Ok(Self::AcrosYe);
|
||||||
|
}
|
||||||
|
"acrossr" | "acrossred" => {
|
||||||
|
return Ok(Self::AcrosR);
|
||||||
|
}
|
||||||
|
"acrossg" | "acrossgreen" => {
|
||||||
|
return Ok(Self::AcrosG);
|
||||||
|
}
|
||||||
|
"eterna" => return Ok(Self::Eterna),
|
||||||
|
"classicneg" | "classicnegative" => {
|
||||||
|
return Ok(Self::ClassicNegative);
|
||||||
|
}
|
||||||
|
"nostalgicneg" | "nostalgicnegative" => {
|
||||||
|
return Ok(Self::NostalgicNegative);
|
||||||
|
}
|
||||||
|
"eternabb" | "eternableach" | "eternableachbypass" => {
|
||||||
|
return Ok(Self::EternaBleachBypass);
|
||||||
|
}
|
||||||
|
"realaace" => {
|
||||||
|
return Ok(Self::RealaAce);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
let choices: Vec<String> = Self::iter().map(|v| v.to_string()).collect();
|
||||||
|
if let Some(best) = get_closest(s, &choices) {
|
||||||
|
bail!("Unknown value '{s}'. Did you mean '{best}'?");
|
||||||
|
}
|
||||||
|
|
||||||
|
bail!("Unknown value '{input}'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for FujiGrainEffect {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::StrongLarge => write!(f, "Strong Large"),
|
||||||
|
Self::WeakLarge => write!(f, "Weak Large"),
|
||||||
|
Self::StrongSmall => write!(f, "Strong Small"),
|
||||||
|
Self::WeakSmall => write!(f, "Weak Small"),
|
||||||
|
Self::Off => write!(f, "Off"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for FujiGrainEffect {
|
||||||
|
type Err = anyhow::Error;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> anyhow::Result<Self> {
|
||||||
|
let input = s
|
||||||
|
.trim()
|
||||||
|
.to_lowercase()
|
||||||
|
.replace(['+', '-', ',', ' '].as_ref(), "");
|
||||||
|
|
||||||
|
match input.as_str() {
|
||||||
|
"stronglarge" | "largestrong" => return Ok(Self::StrongLarge),
|
||||||
|
"weaklarge" | "largeweak" => return Ok(Self::WeakLarge),
|
||||||
|
"strongsmall" | "smallstrong" => return Ok(Self::StrongSmall),
|
||||||
|
"weaksmall" | "smallweak" => return Ok(Self::WeakSmall),
|
||||||
|
"off" => return Ok(Self::Off),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
let choices: Vec<String> = Self::iter().map(|v| v.to_string()).collect();
|
||||||
|
if let Some(best) = get_closest(&input, &choices) {
|
||||||
|
bail!("Unknown grain effect '{s}'. Did you mean '{best}'?");
|
||||||
|
}
|
||||||
|
|
||||||
|
bail!("Unknown grain effect '{s}'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for FujiColorChromeEffect {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::Strong => write!(f, "Strong"),
|
||||||
|
Self::Weak => write!(f, "Weak"),
|
||||||
|
Self::Off => write!(f, "Off"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for FujiColorChromeEffect {
|
||||||
|
type Err = anyhow::Error;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> anyhow::Result<Self> {
|
||||||
|
let input = s.trim().to_lowercase();
|
||||||
|
|
||||||
|
match input.as_str() {
|
||||||
|
"strong" => return Ok(Self::Strong),
|
||||||
|
"weak" => return Ok(Self::Weak),
|
||||||
|
"off" => return Ok(Self::Off),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
let choices: Vec<String> = Self::iter().map(|v| v.to_string()).collect();
|
||||||
|
if let Some(best) = get_closest(s, &choices) {
|
||||||
|
bail!("Unknown color chrome effect '{s}'. Did you mean '{best}'?");
|
||||||
|
}
|
||||||
|
|
||||||
|
bail!("Unknown color chrome effect '{s}'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for FujiColorChromeFXBlue {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::Strong => write!(f, "Strong"),
|
||||||
|
Self::Weak => write!(f, "Weak"),
|
||||||
|
Self::Off => write!(f, "Off"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for FujiColorChromeFXBlue {
|
||||||
|
type Err = anyhow::Error;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> anyhow::Result<Self> {
|
||||||
|
let input = s.trim().to_lowercase();
|
||||||
|
|
||||||
|
match input.as_str() {
|
||||||
|
"strong" => return Ok(Self::Strong),
|
||||||
|
"weak" => return Ok(Self::Weak),
|
||||||
|
"off" => return Ok(Self::Off),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
let choices: Vec<String> = Self::iter().map(|v| v.to_string()).collect();
|
||||||
|
if let Some(best) = get_closest(s, &choices) {
|
||||||
|
bail!("Unknown color chrome fx blue '{s}'. Did you mean '{best}'?");
|
||||||
|
}
|
||||||
|
|
||||||
|
bail!("Unknown color chrome fx blue '{s}'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for FujiSmoothSkinEffect {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::Strong => write!(f, "Strong"),
|
||||||
|
Self::Weak => write!(f, "Weak"),
|
||||||
|
Self::Off => write!(f, "Off"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for FujiSmoothSkinEffect {
|
||||||
|
type Err = anyhow::Error;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> anyhow::Result<Self> {
|
||||||
|
let input = s.trim().to_lowercase();
|
||||||
|
|
||||||
|
match input.as_str() {
|
||||||
|
"strong" => return Ok(Self::Strong),
|
||||||
|
"weak" => return Ok(Self::Weak),
|
||||||
|
"off" => return Ok(Self::Off),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
let choices: Vec<String> = Self::iter().map(|v| v.to_string()).collect();
|
||||||
|
if let Some(best) = get_closest(s, &choices) {
|
||||||
|
bail!("Unknown smooth skin effect '{s}'. Did you mean '{best}'?");
|
||||||
|
}
|
||||||
|
|
||||||
|
bail!("Unknown smooth skin effect '{s}'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for FujiWhiteBalance {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::AsShot => write!(f, "As Shot"),
|
||||||
|
Self::WhitePriority => write!(f, "White Priority"),
|
||||||
|
Self::Auto => write!(f, "Auto"),
|
||||||
|
Self::AmbiencePriority => write!(f, "Ambience Priority"),
|
||||||
|
Self::Custom1 => write!(f, "Custom 1"),
|
||||||
|
Self::Custom2 => write!(f, "Custom 2"),
|
||||||
|
Self::Custom3 => write!(f, "Custom 3"),
|
||||||
|
Self::Temperature => write!(f, "Temperature"),
|
||||||
|
Self::Daylight => write!(f, "Daylight"),
|
||||||
|
Self::Shade => write!(f, "Shade"),
|
||||||
|
Self::Fluorescent1 => write!(f, "Fluorescent 1"),
|
||||||
|
Self::Fluorescent2 => write!(f, "Fluorescent 2"),
|
||||||
|
Self::Fluorescent3 => write!(f, "Fluorescent 3"),
|
||||||
|
Self::Incandescent => write!(f, "Incandescent"),
|
||||||
|
Self::Underwater => write!(f, "Underwater"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for FujiWhiteBalance {
|
||||||
|
type Err = anyhow::Error;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> anyhow::Result<Self> {
|
||||||
|
let input = s.trim().to_lowercase().replace(['-', ' '].as_ref(), "");
|
||||||
|
|
||||||
|
match input.as_str() {
|
||||||
|
"whitepriority" | "white" => return Ok(Self::WhitePriority),
|
||||||
|
// We can't set a film simulation to be "As Shot", so silently parse it to Auto
|
||||||
|
"auto" | "shot" | "asshot" | "original" => return Ok(Self::Auto),
|
||||||
|
"ambiencepriority" | "ambience" | "ambient" => {
|
||||||
|
return Ok(Self::AmbiencePriority);
|
||||||
|
}
|
||||||
|
"custom1" | "c1" => return Ok(Self::Custom1),
|
||||||
|
"custom2" | "c2" => return Ok(Self::Custom2),
|
||||||
|
"custom3" | "c3" => return Ok(Self::Custom3),
|
||||||
|
"temperature" | "k" | "kelvin" => return Ok(Self::Temperature),
|
||||||
|
"daylight" | "sunny" => return Ok(Self::Daylight),
|
||||||
|
"shade" | "cloudy" => return Ok(Self::Shade),
|
||||||
|
"fluorescent1" => {
|
||||||
|
return Ok(Self::Fluorescent1);
|
||||||
|
}
|
||||||
|
"fluorescent2" => {
|
||||||
|
return Ok(Self::Fluorescent2);
|
||||||
|
}
|
||||||
|
"fluorescent3" => {
|
||||||
|
return Ok(Self::Fluorescent3);
|
||||||
|
}
|
||||||
|
"incandescent" | "tungsten" => return Ok(Self::Incandescent),
|
||||||
|
"underwater" => return Ok(Self::Underwater),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
let choices: Vec<String> = Self::iter().map(|v| v.to_string()).collect();
|
||||||
|
if let Some(best) = get_closest(s, &choices) {
|
||||||
|
bail!("Unknown white balance '{s}'. Did you mean '{best}'?");
|
||||||
|
}
|
||||||
|
|
||||||
|
bail!("Unknown white balance '{s}'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for FujiHighISONR {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::Plus4 => write!(f, "+4"),
|
||||||
|
Self::Plus3 => write!(f, "+3"),
|
||||||
|
Self::Plus2 => write!(f, "+2"),
|
||||||
|
Self::Plus1 => write!(f, "+1"),
|
||||||
|
Self::Zero => write!(f, "0"),
|
||||||
|
Self::Minus1 => write!(f, "-1"),
|
||||||
|
Self::Minus2 => write!(f, "-2"),
|
||||||
|
Self::Minus3 => write!(f, "-3"),
|
||||||
|
Self::Minus4 => write!(f, "-4"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for FujiHighISONR {
|
||||||
|
type Err = anyhow::Error;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> anyhow::Result<Self> {
|
||||||
|
let input = s
|
||||||
|
.trim()
|
||||||
|
.parse::<i16>()
|
||||||
|
.with_context(|| format!("Invalid numeric value '{s}'"))?;
|
||||||
|
|
||||||
|
match input {
|
||||||
|
4 => Ok(Self::Plus4),
|
||||||
|
3 => Ok(Self::Plus3),
|
||||||
|
2 => Ok(Self::Plus2),
|
||||||
|
1 => Ok(Self::Plus1),
|
||||||
|
0 => Ok(Self::Zero),
|
||||||
|
-1 => Ok(Self::Minus1),
|
||||||
|
-2 => Ok(Self::Minus2),
|
||||||
|
-3 => Ok(Self::Minus3),
|
||||||
|
-4 => Ok(Self::Minus4),
|
||||||
|
_ => bail!("Value {input} is out of range",),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Serialize for FujiHighISONR {
|
||||||
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: Serializer,
|
||||||
|
{
|
||||||
|
match self {
|
||||||
|
Self::Plus4 => serializer.serialize_i16(4),
|
||||||
|
Self::Plus3 => serializer.serialize_i16(3),
|
||||||
|
Self::Plus2 => serializer.serialize_i16(2),
|
||||||
|
Self::Plus1 => serializer.serialize_i16(1),
|
||||||
|
Self::Zero => serializer.serialize_i16(0),
|
||||||
|
Self::Minus1 => serializer.serialize_i16(-1),
|
||||||
|
Self::Minus2 => serializer.serialize_i16(-2),
|
||||||
|
Self::Minus3 => serializer.serialize_i16(-3),
|
||||||
|
Self::Minus4 => serializer.serialize_i16(-4),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for FujiLensModulationOptimizer {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::Off => write!(f, "Off"),
|
||||||
|
Self::On => write!(f, "On"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for FujiLensModulationOptimizer {
|
||||||
|
type Err = anyhow::Error;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> anyhow::Result<Self> {
|
||||||
|
let input = s.trim().to_lowercase();
|
||||||
|
|
||||||
|
match input.as_str() {
|
||||||
|
"off" | "false" => return Ok(Self::Off),
|
||||||
|
"on" | "true" => return Ok(Self::On),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
let choices: Vec<String> = Self::iter().map(|v| v.to_string()).collect();
|
||||||
|
if let Some(best) = get_closest(s, &choices) {
|
||||||
|
bail!("Unknown lens modulation optimizer '{s}'. Did you mean '{best}'?");
|
||||||
|
}
|
||||||
|
|
||||||
|
bail!("Unknown lens modulation optimizer '{s}'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for FujiColorSpace {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::SRGB => write!(f, "sRGB"),
|
||||||
|
Self::AdobeRGB => write!(f, "Adobe RGB"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for FujiColorSpace {
|
||||||
|
type Err = anyhow::Error;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> anyhow::Result<Self> {
|
||||||
|
let input = s.trim().to_lowercase();
|
||||||
|
|
||||||
|
match input.as_str() {
|
||||||
|
"s" | "srgb" => return Ok(Self::SRGB),
|
||||||
|
"adobe" | "adobergb" => return Ok(Self::AdobeRGB),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
let choices: Vec<String> = Self::iter().map(|v| v.to_string()).collect();
|
||||||
|
if let Some(best) = get_closest(s, &choices) {
|
||||||
|
bail!("Unknown color space '{s}'. Did you mean '{best}'?");
|
||||||
|
}
|
||||||
|
|
||||||
|
bail!("Unknown color space '{s}'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! fuji_i16_cli {
|
||||||
|
($name:ident) => {
|
||||||
|
impl std::str::FromStr for $name {
|
||||||
|
type Err = anyhow::Error;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> anyhow::Result<Self> {
|
||||||
|
use anyhow::Context;
|
||||||
|
|
||||||
|
let input = s
|
||||||
|
.trim()
|
||||||
|
.parse::<f32>()
|
||||||
|
.with_context(|| format!("Invalid numeric value '{s}'"))?;
|
||||||
|
|
||||||
|
if !(Self::MIN..=Self::MAX).contains(&input) {
|
||||||
|
anyhow::bail!("Value {} is out of range", input);
|
||||||
|
}
|
||||||
|
#[allow(clippy::modulo_one)]
|
||||||
|
if (input - Self::MIN) % Self::STEP != 0.0 {
|
||||||
|
anyhow::bail!("Value {} is not aligned to step {}", input, Self::STEP);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::cast_possible_truncation)]
|
||||||
|
let raw = (input * Self::SCALE).round() as i16;
|
||||||
|
|
||||||
|
unsafe { Ok(Self::new_unchecked(raw)) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl serde::Serialize for $name {
|
||||||
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: serde::Serializer,
|
||||||
|
{
|
||||||
|
let val = f32::from(*self.deref()) / Self::SCALE;
|
||||||
|
serializer.serialize_f32(val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for $name {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
let value = (f32::from(*self.deref()) / Self::SCALE);
|
||||||
|
write!(f, "{}", value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fuji_i16_cli!(FujiMonochromaticColorTemperature);
|
||||||
|
fuji_i16_cli!(FujiMonochromaticColorTint);
|
||||||
|
fuji_i16_cli!(FujiWhiteBalanceShift);
|
||||||
|
fuji_i16_cli!(FujiWhiteBalanceTemperature);
|
||||||
|
fuji_i16_cli!(FujiHighlightTone);
|
||||||
|
fuji_i16_cli!(FujiShadowTone);
|
||||||
|
fuji_i16_cli!(FujiColor);
|
||||||
|
fuji_i16_cli!(FujiSharpness);
|
||||||
|
fuji_i16_cli!(FujiClarity);
|
||||||
|
|
||||||
|
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",
|
||||||
|
};
|
||||||
|
write!(f, "{s}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -1,2 +1,3 @@
|
|||||||
pub mod file;
|
pub mod file;
|
||||||
pub mod film;
|
pub mod film;
|
||||||
|
pub mod suggest;
|
||||||
|
29
src/cli/common/suggest.rs
Normal file
29
src/cli/common/suggest.rs
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
use strsim::damerau_levenshtein;
|
||||||
|
|
||||||
|
const SIMILARITY_THRESHOLD: usize = 8;
|
||||||
|
|
||||||
|
pub fn get_closest<'a, I, S>(input: &str, choices: I) -> Option<&'a str>
|
||||||
|
where
|
||||||
|
I: IntoIterator<Item = &'a S>,
|
||||||
|
S: AsRef<str> + 'a,
|
||||||
|
{
|
||||||
|
let mut best_score = usize::MAX;
|
||||||
|
let mut best_match: Option<&'a str> = None;
|
||||||
|
|
||||||
|
for choice in choices {
|
||||||
|
let choice_str = choice.as_ref();
|
||||||
|
let dist = damerau_levenshtein(&input.to_lowercase(), &choice_str.to_lowercase());
|
||||||
|
|
||||||
|
if dist < best_score {
|
||||||
|
best_score = dist;
|
||||||
|
best_match = Some(choice_str);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("{best_score}");
|
||||||
|
if best_score <= SIMILARITY_THRESHOLD {
|
||||||
|
best_match
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
@@ -163,7 +163,7 @@ impl fmt::Display for FilmSimulationRepr {
|
|||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
};
|
}
|
||||||
|
|
||||||
if self.dynamic_range_priority == FujiDynamicRangePriority::Off {
|
if self.dynamic_range_priority == FujiDynamicRangePriority::Off {
|
||||||
writeln!(f, "Highlights: {}", self.highlight)?;
|
writeln!(f, "Highlights: {}", self.highlight)?;
|
||||||
|
Reference in New Issue
Block a user