Add Preset type with 8 built-in presets and JSON serialization
All 5 preset tests passing.
This commit is contained in:
@@ -1 +1,217 @@
|
||||
// Preset management
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::operations::*;
|
||||
use crate::pipeline::ProcessingJob;
|
||||
use crate::types::*;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Preset {
|
||||
pub name: String,
|
||||
pub description: String,
|
||||
pub icon: String,
|
||||
pub is_custom: bool,
|
||||
pub resize: Option<ResizeConfig>,
|
||||
pub rotation: Option<Rotation>,
|
||||
pub flip: Option<Flip>,
|
||||
pub convert: Option<ConvertConfig>,
|
||||
pub compress: Option<CompressConfig>,
|
||||
pub metadata: Option<MetadataConfig>,
|
||||
pub watermark: Option<WatermarkConfig>,
|
||||
pub rename: Option<RenameConfig>,
|
||||
}
|
||||
|
||||
impl Preset {
|
||||
pub fn to_job(
|
||||
&self,
|
||||
input_dir: impl Into<std::path::PathBuf>,
|
||||
output_dir: impl Into<std::path::PathBuf>,
|
||||
) -> ProcessingJob {
|
||||
ProcessingJob {
|
||||
input_dir: input_dir.into(),
|
||||
output_dir: output_dir.into(),
|
||||
sources: Vec::new(),
|
||||
resize: self.resize.clone(),
|
||||
rotation: self.rotation,
|
||||
flip: self.flip,
|
||||
convert: self.convert.clone(),
|
||||
compress: self.compress.clone(),
|
||||
metadata: self.metadata.clone(),
|
||||
watermark: self.watermark.clone(),
|
||||
rename: self.rename.clone(),
|
||||
preserve_directory_structure: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn all_builtins() -> Vec<Preset> {
|
||||
vec![
|
||||
Self::builtin_blog_photos(),
|
||||
Self::builtin_social_media(),
|
||||
Self::builtin_web_optimization(),
|
||||
Self::builtin_email_friendly(),
|
||||
Self::builtin_privacy_clean(),
|
||||
Self::builtin_photographer_export(),
|
||||
Self::builtin_archive_compress(),
|
||||
Self::builtin_fediverse_ready(),
|
||||
]
|
||||
}
|
||||
|
||||
pub fn builtin_blog_photos() -> Preset {
|
||||
Preset {
|
||||
name: "Blog Photos".into(),
|
||||
description: "Resize 1200px wide, JPEG quality High, strip all metadata".into(),
|
||||
icon: "image-x-generic-symbolic".into(),
|
||||
is_custom: false,
|
||||
resize: Some(ResizeConfig::ByWidth(1200)),
|
||||
rotation: None,
|
||||
flip: None,
|
||||
convert: None,
|
||||
compress: Some(CompressConfig::Preset(QualityPreset::High)),
|
||||
metadata: Some(MetadataConfig::StripAll),
|
||||
watermark: None,
|
||||
rename: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn builtin_social_media() -> Preset {
|
||||
Preset {
|
||||
name: "Social Media".into(),
|
||||
description: "Resize to fit 1080x1080, compress Medium, strip metadata".into(),
|
||||
icon: "system-users-symbolic".into(),
|
||||
is_custom: false,
|
||||
resize: Some(ResizeConfig::FitInBox {
|
||||
max: Dimensions {
|
||||
width: 1080,
|
||||
height: 1080,
|
||||
},
|
||||
allow_upscale: false,
|
||||
}),
|
||||
rotation: None,
|
||||
flip: None,
|
||||
convert: None,
|
||||
compress: Some(CompressConfig::Preset(QualityPreset::Medium)),
|
||||
metadata: Some(MetadataConfig::StripAll),
|
||||
watermark: None,
|
||||
rename: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn builtin_web_optimization() -> Preset {
|
||||
Preset {
|
||||
name: "Web Optimization".into(),
|
||||
description: "Convert to WebP, compress High, sequential rename".into(),
|
||||
icon: "web-browser-symbolic".into(),
|
||||
is_custom: false,
|
||||
resize: None,
|
||||
rotation: None,
|
||||
flip: None,
|
||||
convert: Some(ConvertConfig::SingleFormat(ImageFormat::WebP)),
|
||||
compress: Some(CompressConfig::Preset(QualityPreset::High)),
|
||||
metadata: Some(MetadataConfig::StripAll),
|
||||
watermark: None,
|
||||
rename: Some(RenameConfig {
|
||||
prefix: String::new(),
|
||||
suffix: String::new(),
|
||||
counter_start: 1,
|
||||
counter_padding: 3,
|
||||
template: None,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn builtin_email_friendly() -> Preset {
|
||||
Preset {
|
||||
name: "Email Friendly".into(),
|
||||
description: "Resize 800px wide, JPEG quality Medium".into(),
|
||||
icon: "mail-unread-symbolic".into(),
|
||||
is_custom: false,
|
||||
resize: Some(ResizeConfig::ByWidth(800)),
|
||||
rotation: None,
|
||||
flip: None,
|
||||
convert: None,
|
||||
compress: Some(CompressConfig::Preset(QualityPreset::Medium)),
|
||||
metadata: Some(MetadataConfig::StripAll),
|
||||
watermark: None,
|
||||
rename: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn builtin_privacy_clean() -> Preset {
|
||||
Preset {
|
||||
name: "Privacy Clean".into(),
|
||||
description: "Strip all metadata, no other changes".into(),
|
||||
icon: "security-high-symbolic".into(),
|
||||
is_custom: false,
|
||||
resize: None,
|
||||
rotation: None,
|
||||
flip: None,
|
||||
convert: None,
|
||||
compress: None,
|
||||
metadata: Some(MetadataConfig::StripAll),
|
||||
watermark: None,
|
||||
rename: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn builtin_photographer_export() -> Preset {
|
||||
Preset {
|
||||
name: "Photographer Export".into(),
|
||||
description: "Resize 2048px, compress High, privacy metadata, rename by date".into(),
|
||||
icon: "camera-photo-symbolic".into(),
|
||||
is_custom: false,
|
||||
resize: Some(ResizeConfig::ByWidth(2048)),
|
||||
rotation: None,
|
||||
flip: None,
|
||||
convert: None,
|
||||
compress: Some(CompressConfig::Preset(QualityPreset::High)),
|
||||
metadata: Some(MetadataConfig::Privacy),
|
||||
watermark: None,
|
||||
rename: Some(RenameConfig {
|
||||
prefix: String::new(),
|
||||
suffix: String::new(),
|
||||
counter_start: 1,
|
||||
counter_padding: 4,
|
||||
template: Some("{exif_date}_{name}_{counter:4}".into()),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn builtin_archive_compress() -> Preset {
|
||||
Preset {
|
||||
name: "Archive Compress".into(),
|
||||
description: "Lossless compression, preserve metadata".into(),
|
||||
icon: "folder-symbolic".into(),
|
||||
is_custom: false,
|
||||
resize: None,
|
||||
rotation: None,
|
||||
flip: None,
|
||||
convert: None,
|
||||
compress: Some(CompressConfig::Preset(QualityPreset::Maximum)),
|
||||
metadata: Some(MetadataConfig::KeepAll),
|
||||
watermark: None,
|
||||
rename: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn builtin_fediverse_ready() -> Preset {
|
||||
Preset {
|
||||
name: "Fediverse Ready".into(),
|
||||
description: "Resize 1920x1080, convert to WebP, compress High, strip metadata".into(),
|
||||
icon: "network-server-symbolic".into(),
|
||||
is_custom: false,
|
||||
resize: Some(ResizeConfig::FitInBox {
|
||||
max: Dimensions {
|
||||
width: 1920,
|
||||
height: 1080,
|
||||
},
|
||||
allow_upscale: false,
|
||||
}),
|
||||
rotation: None,
|
||||
flip: None,
|
||||
convert: Some(ConvertConfig::SingleFormat(ImageFormat::WebP)),
|
||||
compress: Some(CompressConfig::Preset(QualityPreset::High)),
|
||||
metadata: Some(MetadataConfig::StripAll),
|
||||
watermark: None,
|
||||
rename: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
59
pixstrip-core/tests/preset_tests.rs
Normal file
59
pixstrip-core/tests/preset_tests.rs
Normal file
@@ -0,0 +1,59 @@
|
||||
use pixstrip_core::preset::*;
|
||||
use pixstrip_core::operations::*;
|
||||
|
||||
#[test]
|
||||
fn builtin_preset_blog_photos() {
|
||||
let preset = Preset::builtin_blog_photos();
|
||||
assert_eq!(preset.name, "Blog Photos");
|
||||
assert!(preset.resize.is_some());
|
||||
assert!(preset.compress.is_some());
|
||||
assert!(preset.metadata.is_some());
|
||||
assert!(!preset.is_custom);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn builtin_preset_privacy_clean() {
|
||||
let preset = Preset::builtin_privacy_clean();
|
||||
assert_eq!(preset.name, "Privacy Clean");
|
||||
assert!(preset.resize.is_none());
|
||||
assert!(preset.compress.is_none());
|
||||
assert!(preset.metadata.is_some());
|
||||
if let Some(MetadataConfig::StripAll) = &preset.metadata {
|
||||
// correct
|
||||
} else {
|
||||
panic!("Expected StripAll metadata config");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn preset_serialization_roundtrip() {
|
||||
let preset = Preset::builtin_blog_photos();
|
||||
let json = serde_json::to_string_pretty(&preset).unwrap();
|
||||
let deserialized: Preset = serde_json::from_str(&json).unwrap();
|
||||
assert_eq!(deserialized.name, preset.name);
|
||||
assert_eq!(deserialized.is_custom, preset.is_custom);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn all_builtin_presets() {
|
||||
let presets = Preset::all_builtins();
|
||||
assert_eq!(presets.len(), 8);
|
||||
let names: Vec<&str> = presets.iter().map(|p| p.name.as_str()).collect();
|
||||
assert!(names.contains(&"Blog Photos"));
|
||||
assert!(names.contains(&"Social Media"));
|
||||
assert!(names.contains(&"Web Optimization"));
|
||||
assert!(names.contains(&"Email Friendly"));
|
||||
assert!(names.contains(&"Privacy Clean"));
|
||||
assert!(names.contains(&"Photographer Export"));
|
||||
assert!(names.contains(&"Archive Compress"));
|
||||
assert!(names.contains(&"Fediverse Ready"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn preset_to_processing_job() {
|
||||
let preset = Preset::builtin_blog_photos();
|
||||
let job = preset.to_job("/tmp/input/", "/tmp/output/");
|
||||
assert!(job.resize.is_some());
|
||||
assert!(job.compress.is_some());
|
||||
assert!(job.metadata.is_some());
|
||||
}
|
||||
Reference in New Issue
Block a user