aether_renderer_core/
utils.rs1use std::fs::File;
2use std::path::{Path, PathBuf};
3use std::process::Command;
4use tempfile::tempdir;
5use zip::ZipArchive;
6
7pub 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
71pub 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 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}