aether_renderer_core/
utils.rs

1use std::fs::File;
2use std::path::{Path, PathBuf};
3use std::process::Command;
4use tempfile::tempdir;
5use zip::ZipArchive;
6
7/// Extracts `frame_*.png` from a ZIP into a temporary folder and returns the
8/// folder path along with the temp directory guard.
9pub fn unzip_frames(
10    zip_path: &Path,
11    verbose: bool,
12) -> Result<(PathBuf, tempfile::TempDir), Box<dyn std::error::Error>> {
13    let file = File::open(zip_path)
14        .map_err(|e| format!("❌ Failed to open zip file '{}': {}", zip_path.display(), e))?;
15
16    let mut archive =
17        ZipArchive::new(file).map_err(|e| format!("❌ Failed to read zip archive: {}", e))?;
18
19    let temp_dir = tempdir().map_err(|e| format!("❌ Failed to create temp dir: {}", e))?;
20    let temp_path = temp_dir.path().to_path_buf();
21
22    let mut extracted = 0u32;
23    for i in 0..archive.len() {
24        let mut file = archive
25            .by_index(i)
26            .map_err(|e| format!("❌ Failed to access file in zip at index {}: {}", i, e))?;
27
28        let filename = file.name().rsplit('/').next().unwrap_or("");
29        if !filename.ends_with(".png") {
30            continue;
31        }
32
33        let full_out_path = temp_path.join(filename);
34        let mut out_file = File::create(&full_out_path).map_err(|e| {
35            format!(
36                "❌ Failed to create output file '{}': {}",
37                full_out_path.display(),
38                e
39            )
40        })?;
41
42        std::io::copy(&mut file, &mut out_file).map_err(|e| {
43            format!(
44                "❌ Failed to copy content to '{}': {}",
45                full_out_path.display(),
46                e
47            )
48        })?;
49
50        if verbose {
51            println!("✅ Extracting: {}", full_out_path.display());
52        }
53        extracted += 1;
54    }
55
56    if extracted == 0 {
57        return Err("❌ No PNG files found in zip archive".into());
58    }
59
60    if verbose {
61        if extracted > 1 {
62            println!("⚠️  Extracted {} frames from zip", extracted);
63        } else {
64            println!("✅ Extracted 1 frame from zip");
65        }
66        println!("🗂️  Extracted frames to: {}", temp_path.display());
67    }
68    Ok((temp_path.clone(), temp_dir))
69}
70
71/// Open the rendered output in the default system viewer
72pub fn open_output(path: &str) -> std::io::Result<()> {
73    #[cfg(target_os = "macos")]
74    {
75        Command::new("open").arg(path).status().map(|_| ())
76    }
77    #[cfg(target_os = "linux")]
78    {
79        Command::new("xdg-open").arg(path).status().map(|_| ())
80    }
81    #[cfg(target_os = "windows")]
82    {
83        Command::new("cmd")
84            .args(["/C", "start", path])
85            .status()
86            .map(|_| ())
87    }
88}
89
90#[cfg(test)]
91mod tests {
92    use super::unzip_frames;
93    use std::fs::File;
94    use std::io::Write;
95    use std::path::Path;
96    use tempfile::tempdir;
97    use zip::write::{FileOptions, ZipWriter};
98    use zip::CompressionMethod;
99
100    // Helper to create a small zip containing two fake PNG files
101    fn create_test_zip(path: &Path) -> zip::result::ZipResult<()> {
102        let file = File::create(path)?;
103        let mut zip = ZipWriter::new(file);
104        let options = FileOptions::default().compression_method(CompressionMethod::Stored);
105
106        zip.start_file("frame_0000.png", options)?;
107        zip.write_all(b"png0")?;
108        zip.start_file("frame_0001.png", options)?;
109        zip.write_all(b"png1")?;
110        zip.finish()?;
111        Ok(())
112    }
113
114    #[test]
115    fn unzip_frames_extracts_pngs() -> Result<(), Box<dyn std::error::Error>> {
116        let dir = tempdir()?;
117        let zip_path = dir.path().join("frames.zip");
118        create_test_zip(&zip_path)?;
119
120        let (out_dir, _guard) = unzip_frames(&zip_path, false)?;
121
122        let count = std::fs::read_dir(&out_dir)?.count();
123        assert_eq!(count, 2);
124        assert!(out_dir.join("frame_0000.png").exists());
125        assert!(out_dir.join("frame_0001.png").exists());
126
127        Ok(())
128    }
129}