Add rotate, flip, watermark, and rename CLI flags with helper functions
This commit is contained in:
@@ -18,6 +18,7 @@ struct Cli {
|
||||
}
|
||||
|
||||
#[derive(Subcommand)]
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
enum Commands {
|
||||
/// Process images with a preset or custom operations
|
||||
Process {
|
||||
@@ -37,7 +38,7 @@ enum Commands {
|
||||
#[arg(long)]
|
||||
resize: Option<String>,
|
||||
|
||||
/// Output format (jpeg, png, webp, avif)
|
||||
/// Output format (jpeg, png, webp, avif, gif, tiff)
|
||||
#[arg(long)]
|
||||
format: Option<String>,
|
||||
|
||||
@@ -49,6 +50,38 @@ enum Commands {
|
||||
#[arg(long)]
|
||||
strip_metadata: bool,
|
||||
|
||||
/// Rotate images (90, 180, 270, auto)
|
||||
#[arg(long)]
|
||||
rotate: Option<String>,
|
||||
|
||||
/// Flip images (horizontal, vertical)
|
||||
#[arg(long)]
|
||||
flip: Option<String>,
|
||||
|
||||
/// Add text watermark (e.g., "(c) 2026 My Name")
|
||||
#[arg(long)]
|
||||
watermark: Option<String>,
|
||||
|
||||
/// Watermark position (top-left, top-center, top-right, center, bottom-left, bottom-center, bottom-right)
|
||||
#[arg(long, default_value = "bottom-right")]
|
||||
watermark_position: String,
|
||||
|
||||
/// Watermark opacity (0.0-1.0)
|
||||
#[arg(long, default_value = "0.5")]
|
||||
watermark_opacity: f32,
|
||||
|
||||
/// Rename with prefix
|
||||
#[arg(long)]
|
||||
rename_prefix: Option<String>,
|
||||
|
||||
/// Rename with suffix
|
||||
#[arg(long)]
|
||||
rename_suffix: Option<String>,
|
||||
|
||||
/// Rename template (e.g., "{name}_{counter:3}.{ext}")
|
||||
#[arg(long)]
|
||||
rename_template: Option<String>,
|
||||
|
||||
/// Include subdirectories
|
||||
#[arg(short, long)]
|
||||
recursive: bool,
|
||||
@@ -93,9 +126,17 @@ fn main() {
|
||||
format,
|
||||
quality,
|
||||
strip_metadata,
|
||||
rotate,
|
||||
flip,
|
||||
watermark,
|
||||
watermark_position,
|
||||
watermark_opacity,
|
||||
rename_prefix,
|
||||
rename_suffix,
|
||||
rename_template,
|
||||
recursive,
|
||||
} => {
|
||||
cmd_process(
|
||||
cmd_process(CmdProcessArgs {
|
||||
input,
|
||||
preset,
|
||||
output,
|
||||
@@ -103,8 +144,16 @@ fn main() {
|
||||
format,
|
||||
quality,
|
||||
strip_metadata,
|
||||
rotate,
|
||||
flip,
|
||||
watermark,
|
||||
watermark_position,
|
||||
watermark_opacity,
|
||||
rename_prefix,
|
||||
rename_suffix,
|
||||
rename_template,
|
||||
recursive,
|
||||
);
|
||||
});
|
||||
}
|
||||
Commands::Preset { action } => match action {
|
||||
PresetAction::List => cmd_preset_list(),
|
||||
@@ -116,23 +165,32 @@ fn main() {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn cmd_process(
|
||||
struct CmdProcessArgs {
|
||||
input: Vec<String>,
|
||||
preset_name: Option<String>,
|
||||
preset: Option<String>,
|
||||
output: Option<String>,
|
||||
resize: Option<String>,
|
||||
format: Option<String>,
|
||||
quality: Option<String>,
|
||||
strip_metadata: bool,
|
||||
rotate: Option<String>,
|
||||
flip: Option<String>,
|
||||
watermark: Option<String>,
|
||||
watermark_position: String,
|
||||
watermark_opacity: f32,
|
||||
rename_prefix: Option<String>,
|
||||
rename_suffix: Option<String>,
|
||||
rename_template: Option<String>,
|
||||
recursive: bool,
|
||||
) {
|
||||
}
|
||||
|
||||
fn cmd_process(args: CmdProcessArgs) {
|
||||
// Collect input files
|
||||
let mut source_files = Vec::new();
|
||||
for path_str in &input {
|
||||
for path_str in &args.input {
|
||||
let path = PathBuf::from(path_str);
|
||||
if path.is_dir() {
|
||||
let files = discovery::discover_images(&path, recursive);
|
||||
let files = discovery::discover_images(&path, args.recursive);
|
||||
source_files.extend(files);
|
||||
} else if path.is_file() {
|
||||
source_files.push(path);
|
||||
@@ -148,19 +206,17 @@ fn cmd_process(
|
||||
|
||||
println!("Found {} image(s)", source_files.len());
|
||||
|
||||
// Determine input directory (use parent of first file)
|
||||
let input_dir = source_files[0]
|
||||
.parent()
|
||||
.unwrap_or_else(|| std::path::Path::new("."))
|
||||
.to_path_buf();
|
||||
|
||||
// Determine output directory
|
||||
let output_dir = output
|
||||
let output_dir = args.output
|
||||
.as_ref()
|
||||
.map(PathBuf::from)
|
||||
.unwrap_or_else(|| input_dir.join("processed"));
|
||||
|
||||
// Build job from preset or CLI args
|
||||
let mut job = if let Some(ref name) = preset_name {
|
||||
let mut job = if let Some(ref name) = args.preset {
|
||||
let preset = find_preset(name);
|
||||
println!("Using preset: {}", preset.name);
|
||||
preset.to_job(&input_dir, &output_dir)
|
||||
@@ -169,24 +225,48 @@ fn cmd_process(
|
||||
};
|
||||
|
||||
// Override with CLI flags
|
||||
if let Some(ref resize_str) = resize {
|
||||
if let Some(ref resize_str) = args.resize {
|
||||
job.resize = Some(parse_resize(resize_str));
|
||||
}
|
||||
if let Some(ref fmt_str) = format
|
||||
if let Some(ref fmt_str) = args.format
|
||||
&& let Some(fmt) = parse_format(fmt_str)
|
||||
{
|
||||
job.convert = Some(ConvertConfig::SingleFormat(fmt));
|
||||
}
|
||||
if let Some(ref q_str) = quality
|
||||
if let Some(ref q_str) = args.quality
|
||||
&& let Some(preset) = parse_quality(q_str)
|
||||
{
|
||||
job.compress = Some(CompressConfig::Preset(preset));
|
||||
}
|
||||
if strip_metadata {
|
||||
if args.strip_metadata {
|
||||
job.metadata = Some(MetadataConfig::StripAll);
|
||||
}
|
||||
if let Some(ref rot) = args.rotate {
|
||||
job.rotation = Some(parse_rotation(rot));
|
||||
}
|
||||
if let Some(ref fl) = args.flip {
|
||||
job.flip = Some(parse_flip(fl));
|
||||
}
|
||||
if let Some(ref text) = args.watermark {
|
||||
let position = parse_watermark_position(&args.watermark_position);
|
||||
job.watermark = Some(WatermarkConfig::Text {
|
||||
text: text.clone(),
|
||||
position,
|
||||
font_size: 24.0,
|
||||
opacity: args.watermark_opacity,
|
||||
color: [255, 255, 255, 255],
|
||||
});
|
||||
}
|
||||
if args.rename_prefix.is_some() || args.rename_suffix.is_some() || args.rename_template.is_some() {
|
||||
job.rename = Some(RenameConfig {
|
||||
prefix: args.rename_prefix.unwrap_or_default(),
|
||||
suffix: args.rename_suffix.unwrap_or_default(),
|
||||
counter_start: 1,
|
||||
counter_padding: 3,
|
||||
template: args.rename_template,
|
||||
});
|
||||
}
|
||||
|
||||
// Add sources
|
||||
for file in &source_files {
|
||||
job.add_source(file);
|
||||
}
|
||||
@@ -251,7 +331,7 @@ fn cmd_process(
|
||||
timestamp: chrono_timestamp(),
|
||||
input_dir: input_dir.to_string_lossy().into(),
|
||||
output_dir: output_dir.to_string_lossy().into(),
|
||||
preset_name,
|
||||
preset_name: args.preset,
|
||||
total: result.total,
|
||||
succeeded: result.succeeded,
|
||||
failed: result.failed,
|
||||
@@ -429,6 +509,50 @@ fn parse_quality(s: &str) -> Option<QualityPreset> {
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_rotation(s: &str) -> Rotation {
|
||||
match s.to_lowercase().as_str() {
|
||||
"90" | "cw90" => Rotation::Cw90,
|
||||
"180" => Rotation::Cw180,
|
||||
"270" | "cw270" => Rotation::Cw270,
|
||||
"auto" => Rotation::AutoOrient,
|
||||
"none" | "0" => Rotation::None,
|
||||
_ => {
|
||||
eprintln!("Unknown rotation: '{}'. Supported: 90, 180, 270, auto, none", s);
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_flip(s: &str) -> Flip {
|
||||
match s.to_lowercase().as_str() {
|
||||
"horizontal" | "h" => Flip::Horizontal,
|
||||
"vertical" | "v" => Flip::Vertical,
|
||||
"none" => Flip::None,
|
||||
_ => {
|
||||
eprintln!("Unknown flip: '{}'. Supported: horizontal, vertical, none", s);
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_watermark_position(s: &str) -> WatermarkPosition {
|
||||
match s.to_lowercase().replace(' ', "-").as_str() {
|
||||
"top-left" | "tl" => WatermarkPosition::TopLeft,
|
||||
"top-center" | "tc" | "top" => WatermarkPosition::TopCenter,
|
||||
"top-right" | "tr" => WatermarkPosition::TopRight,
|
||||
"middle-left" | "ml" | "left" => WatermarkPosition::MiddleLeft,
|
||||
"center" | "c" | "middle" => WatermarkPosition::Center,
|
||||
"middle-right" | "mr" | "right" => WatermarkPosition::MiddleRight,
|
||||
"bottom-left" | "bl" => WatermarkPosition::BottomLeft,
|
||||
"bottom-center" | "bc" | "bottom" => WatermarkPosition::BottomCenter,
|
||||
"bottom-right" | "br" => WatermarkPosition::BottomRight,
|
||||
_ => {
|
||||
eprintln!("Unknown position: '{}'. Supported: top-left, top-center, top-right, center, bottom-left, bottom-center, bottom-right", s);
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn format_bytes(bytes: u64) -> String {
|
||||
if bytes < 1024 {
|
||||
format!("{} B", bytes)
|
||||
|
||||
Reference in New Issue
Block a user