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 count_pngs_in_zip(zip_path: &Path) -> Result<usize, Box<dyn std::error::Error>> {
73 let file = File::open(zip_path)
74 .map_err(|e| format!("❌ Failed to open zip file '{}': {}", zip_path.display(), e))?;
75 let mut archive =
76 ZipArchive::new(file).map_err(|e| format!("❌ Failed to read zip archive: {}", e))?;
77 let mut count = 0usize;
78 for i in 0..archive.len() {
79 let file = archive.by_index(i)?;
80 let filename = file.name().rsplit('/').next().unwrap_or("");
81 if filename.ends_with(".png") {
82 count += 1;
83 }
84 }
85 Ok(count)
86}
87
88pub fn extract_frame_from_zip(
90 zip_path: &Path,
91 frame_index: usize,
92 output: &Path,
93) -> Result<(), Box<dyn std::error::Error>> {
94 let file = File::open(zip_path)
95 .map_err(|e| format!("❌ Failed to open zip file '{}': {}", zip_path.display(), e))?;
96 let mut archive =
97 ZipArchive::new(file).map_err(|e| format!("❌ Failed to read zip archive: {}", e))?;
98 let mut png_indices = Vec::new();
99 for i in 0..archive.len() {
100 let f = archive.by_index(i)?;
101 let name = f.name().rsplit('/').next().unwrap_or("");
102 if name.ends_with(".png") {
103 png_indices.push(i);
104 }
105 }
106 if png_indices.is_empty() {
107 return Err("❌ No PNG files found in zip archive".into());
108 }
109 if frame_index >= png_indices.len() {
110 return Err(format!(
111 "❌ Frame index {} out of range (0..{})",
112 frame_index,
113 png_indices.len() - 1
114 )
115 .into());
116 }
117 let mut file = archive.by_index(png_indices[frame_index])?;
118 let mut out = File::create(output).map_err(|e| {
119 format!(
120 "❌ Failed to create output file '{}': {}",
121 output.display(),
122 e
123 )
124 })?;
125 std::io::copy(&mut file, &mut out)
126 .map_err(|e| format!("❌ Failed to copy content to '{}': {}", output.display(), e))?;
127 Ok(())
128}
129
130pub fn open_output(path: &str) -> std::io::Result<()> {
132 #[cfg(target_os = "macos")]
133 {
134 Command::new("open").arg(path).status().map(|_| ())
135 }
136 #[cfg(target_os = "linux")]
137 {
138 Command::new("xdg-open").arg(path).status().map(|_| ())
139 }
140 #[cfg(target_os = "windows")]
141 {
142 Command::new("cmd")
143 .args(["/C", "start", path])
144 .status()
145 .map(|_| ())
146 }
147}
148
149#[cfg(test)]
150mod tests {
151 use super::unzip_frames;
152 use std::fs::File;
153 use std::io::Write;
154 use std::path::Path;
155 use tempfile::tempdir;
156 use zip::write::{FileOptions, ZipWriter};
157 use zip::CompressionMethod;
158
159 fn create_test_zip(path: &Path) -> zip::result::ZipResult<()> {
161 let file = File::create(path)?;
162 let mut zip = ZipWriter::new(file);
163 let options = FileOptions::default().compression_method(CompressionMethod::Stored);
164
165 zip.start_file("frame_0000.png", options)?;
166 zip.write_all(b"png0")?;
167 zip.start_file("frame_0001.png", options)?;
168 zip.write_all(b"png1")?;
169 zip.finish()?;
170 Ok(())
171 }
172
173 #[test]
174 fn unzip_frames_extracts_pngs() -> Result<(), Box<dyn std::error::Error>> {
175 let dir = tempdir()?;
176 let zip_path = dir.path().join("frames.zip");
177 create_test_zip(&zip_path)?;
178
179 let (out_dir, _guard) = unzip_frames(&zip_path, false)?;
180
181 let count = std::fs::read_dir(&out_dir)?.count();
182 assert_eq!(count, 2);
183 assert!(out_dir.join("frame_0000.png").exists());
184 assert!(out_dir.join("frame_0001.png").exists());
185
186 Ok(())
187 }
188}