aether_renderer_core/utils/
unzip_frames.rs

1use std::fs::File;
2use std::path::{Path, PathBuf};
3use tempfile::tempdir;
4use zip::ZipArchive;
5
6/// Extracts frame_*.png from a ZIP into a temp folder, returns the folder path
7pub fn unzip_frames(
8    zip_path: &Path,
9) -> Result<(PathBuf, tempfile::TempDir), Box<dyn std::error::Error>> {
10    let file = File::open(zip_path)
11        .map_err(|e| format!("❌ Failed to open zip file '{}': {}", zip_path.display(), e))?;
12
13    let mut archive =
14        ZipArchive::new(file).map_err(|e| format!("❌ Failed to read zip archive: {}", e))?;
15
16    let temp_dir = tempdir().map_err(|e| format!("❌ Failed to create temp dir: {}", e))?;
17
18    let temp_path = temp_dir.path().to_path_buf();
19
20    let mut extracted = 0u32;
21    for i in 0..archive.len() {
22        let mut file = archive
23            .by_index(i)
24            .map_err(|e| format!("❌ Failed to access file in zip at index {}: {}", i, e))?;
25
26        let filename = file.name().rsplit('/').next().unwrap_or("");
27        if !filename.ends_with(".png") {
28            continue;
29        }
30
31        let full_out_path = temp_path.join(filename);
32        let mut out_file = File::create(&full_out_path).map_err(|e| {
33            format!(
34                "❌ Failed to create output file '{}': {}",
35                full_out_path.display(),
36                e
37            )
38        })?;
39
40        std::io::copy(&mut file, &mut out_file).map_err(|e| {
41            format!(
42                "❌ Failed to copy content to '{}': {}",
43                full_out_path.display(),
44                e
45            )
46        })?;
47
48        println!("✅ Extracting: {}", full_out_path.display());
49        extracted += 1;
50    }
51
52    if extracted == 0 {
53        return Err("❌ No PNG files found in zip archive".into());
54    }
55
56    println!("🗂️  Extracted frames to: {}", temp_path.display());
57    Ok((temp_path.clone(), temp_dir))
58}
59
60#[cfg(test)]
61mod tests {
62    use super::unzip_frames;
63    use std::fs::File;
64    use std::io::Write;
65    use std::path::Path;
66    use tempfile::tempdir;
67    use zip::write::{FileOptions, ZipWriter};
68    use zip::CompressionMethod;
69
70    // Helper to create a small zip containing two fake PNG files
71    fn create_test_zip(path: &Path) -> zip::result::ZipResult<()> {
72        let file = File::create(path)?;
73        let mut zip = ZipWriter::new(file);
74        let options = FileOptions::default().compression_method(CompressionMethod::Stored);
75
76        zip.start_file("frame_0000.png", options)?;
77        zip.write_all(b"png0")?;
78        zip.start_file("frame_0001.png", options)?;
79        zip.write_all(b"png1")?;
80        zip.finish()?;
81        Ok(())
82    }
83
84    #[test]
85    fn unzip_frames_extracts_pngs() -> Result<(), Box<dyn std::error::Error>> {
86        let dir = tempdir()?;
87        let zip_path = dir.path().join("frames.zip");
88        create_test_zip(&zip_path)?;
89
90        let (out_dir, _guard) = unzip_frames(&zip_path)?;
91
92        let count = std::fs::read_dir(&out_dir)?.count();
93        assert_eq!(count, 2);
94        assert!(out_dir.join("frame_0000.png").exists());
95        assert!(out_dir.join("frame_0001.png").exists());
96
97        Ok(())
98    }
99}