chore: reorganize cli traits
Signed-off-by: Nikolaos Karaolidis <nick@karaolidis.com>
This commit is contained in:
@@ -1,16 +1,13 @@
|
||||
use std::{
|
||||
fmt,
|
||||
io::{self, Cursor},
|
||||
ops::{Deref, DerefMut},
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
use anyhow::{Context, bail};
|
||||
use anyhow::bail;
|
||||
use num_enum::{IntoPrimitive, TryFromPrimitive};
|
||||
use ptp_cursor::{PtpDeserialize, PtpSerialize, Read};
|
||||
use ptp_macro::{PtpDeserialize, PtpSerialize};
|
||||
use serde::{Serialize, Serializer};
|
||||
use strum::IntoEnumIterator;
|
||||
use serde::Serialize;
|
||||
use strum_macros::EnumIter;
|
||||
|
||||
#[repr(u16)]
|
||||
@@ -166,34 +163,6 @@ pub enum DevicePropCode {
|
||||
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)]
|
||||
#[derive(
|
||||
Debug, Clone, Copy, PartialEq, Eq, IntoPrimitive, TryFromPrimitive, PtpSerialize, PtpDeserialize,
|
||||
@@ -233,55 +202,15 @@ pub enum FujiCustomSetting {
|
||||
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)]
|
||||
pub struct FujiCustomSettingName(String);
|
||||
|
||||
impl FujiCustomSettingName {
|
||||
pub const MAX_LEN: usize = 25;
|
||||
|
||||
pub const unsafe fn new_unchecked(value: String) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
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)]
|
||||
#[derive(
|
||||
Debug,
|
||||
@@ -356,81 +268,6 @@ pub enum FujiImageSize {
|
||||
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)]
|
||||
#[derive(
|
||||
Debug,
|
||||
@@ -453,42 +290,6 @@ pub enum FujiImageQuality {
|
||||
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)]
|
||||
#[derive(
|
||||
Debug,
|
||||
@@ -510,40 +311,6 @@ pub enum FujiDynamicRange {
|
||||
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)]
|
||||
#[derive(
|
||||
Debug,
|
||||
@@ -565,40 +332,6 @@ pub enum FujiDynamicRangePriority {
|
||||
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)]
|
||||
#[derive(
|
||||
Debug,
|
||||
@@ -636,100 +369,6 @@ pub enum FujiFilmSimulation {
|
||||
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)]
|
||||
#[derive(
|
||||
Debug,
|
||||
@@ -752,45 +391,6 @@ pub enum FujiGrainEffect {
|
||||
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)]
|
||||
#[derive(
|
||||
Debug,
|
||||
@@ -811,38 +411,6 @@ pub enum FujiColorChromeEffect {
|
||||
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)]
|
||||
#[derive(
|
||||
Debug,
|
||||
@@ -863,38 +431,6 @@ pub enum FujiColorChromeFXBlue {
|
||||
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)]
|
||||
#[derive(
|
||||
Debug,
|
||||
@@ -915,38 +451,6 @@ pub enum FujiSmoothSkinEffect {
|
||||
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)]
|
||||
#[derive(
|
||||
Debug,
|
||||
@@ -962,6 +466,7 @@ impl FromStr for FujiSmoothSkinEffect {
|
||||
EnumIter,
|
||||
)]
|
||||
pub enum FujiWhiteBalance {
|
||||
AsShot = 0x1,
|
||||
WhitePriority = 0x8020,
|
||||
Auto = 0x2,
|
||||
AmbiencePriority = 0x8021,
|
||||
@@ -978,67 +483,6 @@ pub enum FujiWhiteBalance {
|
||||
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)]
|
||||
#[derive(
|
||||
Debug, Clone, Copy, PartialEq, Eq, IntoPrimitive, TryFromPrimitive, PtpSerialize, PtpDeserialize,
|
||||
@@ -1055,65 +499,6 @@ pub enum FujiHighISONR {
|
||||
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)]
|
||||
#[derive(
|
||||
Debug,
|
||||
@@ -1133,36 +518,6 @@ pub enum FujiLensModulationOptimizer {
|
||||
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)]
|
||||
#[derive(
|
||||
Debug,
|
||||
@@ -1177,41 +532,12 @@ impl FromStr for FujiLensModulationOptimizer {
|
||||
PtpDeserialize,
|
||||
EnumIter,
|
||||
)]
|
||||
#[allow(clippy::upper_case_acronyms)]
|
||||
pub enum FujiColorSpace {
|
||||
SRGB = 0x2,
|
||||
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 {
|
||||
($name:ident, $min:expr, $max:expr, $step:expr, $scale:literal) => {
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PtpSerialize, PtpDeserialize)]
|
||||
@@ -1263,49 +589,6 @@ macro_rules! fuji_i16 {
|
||||
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,
|
||||
}
|
||||
|
||||
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(
|
||||
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};
|
||||
use clap::Args;
|
||||
use serde::{Serialize, Serializer};
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
use crate::{
|
||||
camera::ptp::hex::{
|
||||
FujiClarity, FujiColor, FujiColorChromeEffect, FujiColorChromeFXBlue, FujiColorSpace,
|
||||
FujiDynamicRange, FujiDynamicRangePriority, FujiFilmSimulation, FujiGrainEffect, FujiHighISONR,
|
||||
FujiHighlightTone, FujiImageQuality, FujiImageSize, FujiLensModulationOptimizer,
|
||||
FujiMonochromaticColorTemperature, FujiMonochromaticColorTint, FujiShadowTone, FujiSharpness,
|
||||
FujiSmoothSkinEffect, FujiWhiteBalance, FujiWhiteBalanceShift, FujiWhiteBalanceTemperature,
|
||||
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)]
|
||||
@@ -102,3 +111,721 @@ pub struct FilmSimulationOptions {
|
||||
#[clap(long)]
|
||||
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 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 {
|
||||
writeln!(f, "Highlights: {}", self.highlight)?;
|
||||
@@ -250,6 +250,7 @@ fn handle_get(json: bool, device_id: Option<&str>, slot: FujiCustomSetting) -> a
|
||||
}
|
||||
|
||||
#[allow(clippy::cognitive_complexity)]
|
||||
#[allow(clippy::too_many_lines)]
|
||||
fn handle_set(
|
||||
device_id: Option<&str>,
|
||||
slot: FujiCustomSetting,
|
||||
@@ -286,7 +287,8 @@ fn handle_set(
|
||||
&camera.get_film_simulation()?
|
||||
};
|
||||
|
||||
let is_bnw = match *simulation {
|
||||
let is_bnw = matches!(
|
||||
*simulation,
|
||||
FujiFilmSimulation::Monochrome
|
||||
| FujiFilmSimulation::MonochromeYe
|
||||
| FujiFilmSimulation::MonochromeR
|
||||
@@ -294,9 +296,8 @@ fn handle_set(
|
||||
| FujiFilmSimulation::AcrosSTD
|
||||
| FujiFilmSimulation::AcrosYe
|
||||
| FujiFilmSimulation::AcrosR
|
||||
| FujiFilmSimulation::AcrosG => true,
|
||||
_ => false,
|
||||
};
|
||||
| FujiFilmSimulation::AcrosG
|
||||
);
|
||||
|
||||
if let Some(monochromatic_color_temperature) = &options.monochromatic_color_temperature {
|
||||
if is_bnw {
|
||||
|
Reference in New Issue
Block a user