use std::io;
use std::path::{Path, PathBuf};
use crate::timeline::Timeline;
const FRAME_DIGITS: usize = 6;
impl Timeline {
pub fn render(&self, dir: impl AsRef<Path>) -> io::Result<usize> {
let dir = dir.as_ref();
std::fs::create_dir_all(dir)?;
let frames = self.frames();
for (i, frame) in frames.iter().enumerate() {
let name = format!("{:0width$}.png", i + 1, width = FRAME_DIGITS);
frame.save_png(dir.join(name))?;
}
Ok(frames.len())
}
}
pub fn scene_output_dir(manifest_dir: &str, source_file: &str, scene: &str) -> PathBuf {
resolve_source_dir(manifest_dir, source_file).join("outputs").join(scene)
}
fn resolve_source_dir(manifest_dir: &str, source_file: &str) -> PathBuf {
let file = Path::new(source_file);
let candidates: Vec<PathBuf> = if file.is_absolute() {
vec![file.to_path_buf()]
} else {
let manifest = Path::new(manifest_dir);
let mut v = vec![manifest.join(file)];
if let Some(workspace) = manifest.parent() {
v.push(workspace.join(file));
}
v.push(file.to_path_buf()); v
};
let resolved = candidates
.iter()
.find(|p| p.exists())
.cloned()
.unwrap_or_else(|| candidates[0].clone());
resolved.parent().map(Path::to_path_buf).unwrap_or_else(|| PathBuf::from("."))
}
#[macro_export]
macro_rules! scene_dir {
($scene:expr $(,)?) => {
$crate::scene_output_dir(env!("CARGO_MANIFEST_DIR"), file!(), $scene)
};
}
#[macro_export]
macro_rules! render {
($timeline:expr, $scene:expr $(,)?) => {
$timeline.render($crate::scene_dir!($scene)).map(|_| ())
};
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn output_dir_sits_next_to_source() {
let dir = scene_output_dir("/proj/crate", "src/main.rs", "demo");
assert!(dir.ends_with("outputs/demo") || dir.ends_with("outputs\\demo"));
assert!(dir.to_string_lossy().contains("src"));
}
#[test]
fn absolute_source_file_is_used_directly() {
let dir = scene_output_dir("/ignored", "/abs/scenes/intro.rs", "intro");
let s = dir.to_string_lossy().replace('\\', "/");
assert_eq!(s, "/abs/scenes/outputs/intro");
}
}