photocraft/crates/automation/src/files.rs

71 lines
3.2 KiB
Rust

//! File I/O shared by the MCP server and the CLI: open/save any supported
//! format, with `.pcraft` (ZIP or directory bundle) handled natively.
use std::path::Path;
use photocraft_doc::Document;
use photocraft_format::{PcraftWriter, SaveOptions};
use photocraft_io::ExportOptions;
use crate::AutomationError;
/// Result of opening a file.
pub struct Opened {
pub document: Document,
pub warnings: Vec<String>,
}
/// Open a document from disk. Directory bundles and `.pcraft` ZIPs load
/// natively; everything else goes through `photocraft-io` (PSD, PNG, …).
pub fn open(path: &Path) -> Result<Opened, AutomationError> {
if path.is_dir() {
return Ok(Opened { document: photocraft_format::load_path(path)?, warnings: Vec::new() });
}
let bytes = std::fs::read(path).map_err(|e| AutomationError::Io(format!("{}: {e}", path.display())))?;
let name = path.file_name().map(|n| n.to_string_lossy().into_owned()).unwrap_or_default();
let r = photocraft_io::import(&name, &bytes)?;
Ok(Opened { document: r.document, warnings: r.warnings })
}
/// Previews for a `.pcraft` bundle.
pub fn previews(doc: &Document) -> SaveOptions {
SaveOptions { thumbnail: Some(photocraft_compose::thumbnail(doc, 256)), composite: Some(photocraft_compose::thumbnail(doc, 1024)) }
}
/// Save or export `doc` to `path`, choosing the format from the extension
/// (or `format_override`, an extension such as `"png"`). `writer` makes
/// repeated `.pcraft` saves incremental. Returns warnings about lost data.
pub fn save(
doc: &Document,
path: &Path,
format_override: Option<&str>,
opts: &ExportOptions,
writer: Option<&mut PcraftWriter>,
) -> Result<Vec<String>, AutomationError> {
let ext = format_override
.map(|f| f.trim_start_matches('.').to_ascii_lowercase())
.or_else(|| path.extension().map(|e| e.to_string_lossy().to_ascii_lowercase()))
.ok_or_else(|| AutomationError::BadRequest(format!("cannot tell the format of `{}`; pass a format", path.display())))?;
if ext == photocraft_format::EXTENSION {
let mut local = PcraftWriter::new();
let w = writer.unwrap_or(&mut local);
w.save_path(doc, path, &previews(doc))?;
return Ok(Vec::new());
}
let r = photocraft_io::export(doc, &ext, opts)?;
if let Some(parent) = path.parent().filter(|p| !p.as_os_str().is_empty()) {
std::fs::create_dir_all(parent).map_err(|e| AutomationError::Io(e.to_string()))?;
}
std::fs::write(path, &r.bytes).map_err(|e| AutomationError::Io(format!("{}: {e}", path.display())))?;
Ok(r.warnings)
}
/// Flattened document as PNG, scaled to fit `max_side` (0 = full size).
pub fn render_png(doc: &Document, max_side: u32) -> Result<Vec<u8>, AutomationError> {
let side = if max_side == 0 { doc.size.width.max(doc.size.height) } else { max_side };
let img = photocraft_compose::thumbnail(doc, side.max(1));
let image = photocraft_codecs::Image::from_u8(img.width, img.height, photocraft_codecs::ChannelLayout::Rgba, img.pixels)
.map_err(|e| AutomationError::Other(e.to_string()))?;
photocraft_codecs::encode(&image, photocraft_codecs::Format::Png, &Default::default()).map_err(|e| AutomationError::Other(e.to_string()))
}