use std::path::PathBuf;
use clap::{Args, Parser, Subcommand, ValueEnum};
use kallo_io::InputEncoding;
#[derive(Debug, Parser)]
#[command(
name = "kalo",
version,
about = "Kallo — film simulation, with developing time as a dimension",
propagate_version = true
)]
pub struct Cli {
#[command(subcommand)]
pub command: Command,
}
#[derive(Debug, Subcommand)]
pub enum Command {
Render(RenderArgs),
Contact(ContactArgs),
Stocks {
#[arg(long)]
json: bool,
},
Stock {
#[command(subcommand)]
command: StockCommand,
},
Curve(CurveArgs),
Bake(BakeArgs),
}
#[derive(Debug, Subcommand)]
pub enum StockCommand {
Check(StockCheckArgs),
}
#[derive(Debug, Args)]
pub struct StockCheckArgs {
pub file: PathBuf,
}
#[derive(Debug, Args)]
pub struct StockArgs {
#[arg(long, default_value = "vision3_500t", conflicts_with = "film_file")]
pub film: String,
#[arg(long, value_name = "PATH", conflicts_with = "film")]
pub film_file: Option<std::path::PathBuf>,
#[arg(long, default_value = "portra_endura")]
pub paper: String,
#[arg(long, value_name = "Y/M/C", default_value = "")]
pub filters: String,
#[arg(long, value_name = "GLASS")]
pub heat_glass: Option<String>,
#[arg(long = "dev", value_name = "MINUTES")]
pub dev_time: Option<f64>,
#[arg(long, value_name = "C")]
pub temp: Option<f64>,
#[arg(long)]
pub spectral: bool,
#[arg(long, value_name = "N", default_value_t = 0)]
pub lut_size: usize,
#[arg(long, value_name = "PATH")]
pub dictionary: Vec<std::path::PathBuf>,
#[arg(long, value_name = "PATH")]
pub lut: Option<std::path::PathBuf>,
#[arg(long)]
pub allow_model_mismatch: bool,
#[arg(long, value_name = "NAME|PATH")]
pub camera: Option<String>,
#[arg(long)]
pub spectral_print: bool,
#[arg(long)]
pub spectral_scan: bool,
}
#[derive(Debug, Args)]
pub struct RenderArgs {
pub input: PathBuf,
#[arg(short, long)]
pub output: PathBuf,
#[command(flatten)]
pub stock: StockArgs,
#[arg(
long,
value_name = "EV|auto",
default_value = "0",
allow_hyphen_values = true
)]
pub ev: Exposure,
#[arg(
long,
value_name = "EV",
default_value = "0",
allow_hyphen_values = true
)]
pub print_ev: f64,
#[arg(long, value_name = "MINUTES")]
pub balance_from: Option<f64>,
#[arg(long, value_enum, default_value_t = EncodingArg::Srgb)]
pub input_encoding: EncodingArg,
#[arg(long)]
pub grain: bool,
#[arg(long, value_name = "PIXELS")]
pub size: Option<u32>,
#[arg(long, value_name = "MM", default_value_t = 36.0)]
pub frame_mm: f64,
#[arg(long, value_name = "MM")]
pub grain_um: Option<f64>,
#[arg(long, value_name = "N", default_value_t = 0)]
pub grain_seed: u64,
#[arg(long = "no-gpu", action = clap::ArgAction::SetFalse, default_value_t = true)]
pub gpu: bool,
#[arg(long)]
pub couplers: bool,
#[arg(long = "no-emulsion")]
pub no_emulsion: bool,
#[arg(long)]
pub halation: bool,
#[arg(long)]
pub interimage: bool,
#[arg(long)]
pub scanner: bool,
#[arg(long, value_name = "MM", requires = "scanner")]
pub scan_optics_um: Option<f64>,
}
#[derive(Debug, Args)]
pub struct ContactArgs {
pub input: PathBuf,
#[arg(short, long)]
pub output: PathBuf,
#[arg(long, value_name = "PATH")]
pub record: Option<PathBuf>,
#[arg(long, default_value = "vision3_500t")]
pub film: String,
#[arg(long, default_value = "portra_endura")]
pub paper: String,
#[arg(long, value_name = "MINUTES", value_delimiter = ',')]
pub dev: Vec<f64>,
#[arg(long, value_name = "C")]
pub temp: Option<f64>,
#[arg(
long,
value_name = "EV|auto",
default_value = "auto",
allow_hyphen_values = true
)]
pub ev: Exposure,
#[arg(
long,
value_name = "EV",
default_value = "0",
allow_hyphen_values = true
)]
pub print_ev: f64,
#[arg(long, value_name = "DPI", default_value_t = 600.0)]
pub dpi: f64,
#[arg(long, value_enum, default_value_t = EncodingArg::Srgb)]
pub input_encoding: EncodingArg,
#[arg(long)]
pub spectral: bool,
#[arg(long)]
pub grain: bool,
#[arg(long)]
pub halation: bool,
#[arg(long)]
pub couplers: bool,
#[arg(long)]
pub no_edge_codes: bool,
}
#[derive(Debug, Args)]
pub struct BakeArgs {
#[arg(short, long)]
pub output: std::path::PathBuf,
#[arg(long, value_name = "N", default_value_t = 33)]
pub size: usize,
#[arg(long, value_name = "PATH")]
pub dictionary: Vec<std::path::PathBuf>,
#[arg(long, value_name = "NAME|PATH")]
pub camera: Option<String>,
#[arg(long, value_name = "ID", default_value = "vision3_500t")]
pub film: String,
}
#[derive(Debug, Args)]
pub struct CurveArgs {
#[command(flatten)]
pub stock: StockArgs,
#[arg(long)]
pub sweep: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)]
pub enum EncodingArg {
Srgb,
Linear,
}
impl From<EncodingArg> for InputEncoding {
fn from(value: EncodingArg) -> Self {
match value {
EncodingArg::Srgb => Self::Srgb,
EncodingArg::Linear => Self::Linear,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Exposure {
Stops(f64),
Auto,
}
impl std::str::FromStr for Exposure {
type Err = String;
fn from_str(text: &str) -> Result<Self, Self::Err> {
if text.eq_ignore_ascii_case("auto") {
return Ok(Self::Auto);
}
match text.parse::<f64>() {
Ok(ev) => Ok(Self::Stops(ev)),
Err(_) => Err(format!(
"expected a number of stops or `auto`, got {text:?}"
)),
}
}
}
impl std::fmt::Display for Exposure {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Stops(ev) => write!(formatter, "{ev}"),
Self::Auto => write!(formatter, "auto"),
}
}
}