aether_renderer_core/
utils.rs1use std::fs::{self, 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) -> Result<(PathBuf, tempfile::TempDir), Box<dyn std::error::Error>> {
12 let file = File::open(zip_path)
13 .map_err(|e| format!("❌ Failed to open zip file '{}': {}", zip_path.display(), e))?;
14
15 let mut archive =
16 ZipArchive::new(file).map_err(|e| format!("❌ Failed to read zip archive: {}", e))?;
17
18 let temp_dir = tempdir().map_err(|e| format!("❌ Failed to create temp dir: {}", e))?;
19 let temp_path = temp_dir.path().to_path_buf();
20
21 let mut extracted = 0u32;
22 for i in 0..archive.len() {
23 let mut file = archive
24 .by_index(i)
25 .map_err(|e| format!("❌ Failed to access file in zip at index {}: {}", i, e))?;
26
27 let filename = file.name().rsplit('/').next().unwrap_or("");
28 if !filename.ends_with(".png") {
29 continue;
30 }
31
32 let full_out_path = temp_path.join(filename);
33 let mut out_file = File::create(&full_out_path).map_err(|e| {
34 format!(
35 "❌ Failed to create output file '{}': {}",
36 full_out_path.display(),
37 e
38 )
39 })?;
40
41 std::io::copy(&mut file, &mut out_file).map_err(|e| {
42 format!(
43 "❌ Failed to copy content to '{}': {}",
44 full_out_path.display(),
45 e
46 )
47 })?;
48
49 println!("✅ Extracting: {}", full_out_path.display());
50 extracted += 1;
51 }
52
53 if extracted == 0 {
54 return Err("❌ No PNG files found in zip archive".into());
55 }
56
57 println!("🗂️ Extracted frames to: {}", temp_path.display());
58 Ok((temp_path.clone(), temp_dir))
59}
60
61pub fn open_output(path: &str) -> std::io::Result<()> {
63 #[cfg(target_os = "macos")]
64 {
65 Command::new("open").arg(path).status().map(|_| ())
66 }
67 #[cfg(target_os = "linux")]
68 {
69 Command::new("xdg-open").arg(path).status().map(|_| ())
70 }
71 #[cfg(target_os = "windows")]
72 {
73 Command::new("cmd")
74 .args(["/C", "start", path])
75 .status()
76 .map(|_| ())
77 }
78}
79
80pub fn apply_app_output(output: &Path, app_output: Option<PathBuf>) -> std::io::Result<PathBuf> {
83 if let Some(dir) = app_output {
84 fs::create_dir_all(&dir)?;
85 let file_name = output.file_name().unwrap_or_else(|| output.as_os_str());
86 Ok(dir.join(file_name))
87 } else {
88 Ok(output.to_path_buf())
89 }
90}
91
92#[cfg(test)]
93mod tests {
94 use super::{apply_app_output, unzip_frames};
95 use std::fs::File;
96 use std::io::Write;
97 use std::path::{Path, PathBuf};
98 use tempfile::tempdir;
99 use zip::write::{FileOptions, ZipWriter};
100 use zip::CompressionMethod;
101
102 fn create_test_zip(path: &Path) -> zip::result::ZipResult<()> {
104 let file = File::create(path)?;
105 let mut zip = ZipWriter::new(file);
106 let options = FileOptions::default().compression_method(CompressionMethod::Stored);
107
108 zip.start_file("frame_0000.png", options)?;
109 zip.write_all(b"png0")?;
110 zip.start_file("frame_0001.png", options)?;
111 zip.write_all(b"png1")?;
112 zip.finish()?;
113 Ok(())
114 }
115
116 #[test]
117 fn unzip_frames_extracts_pngs() -> Result<(), Box<dyn std::error::Error>> {
118 let dir = tempdir()?;
119 let zip_path = dir.path().join("frames.zip");
120 create_test_zip(&zip_path)?;
121
122 let (out_dir, _guard) = unzip_frames(&zip_path)?;
123
124 let count = std::fs::read_dir(&out_dir)?.count();
125 assert_eq!(count, 2);
126 assert!(out_dir.join("frame_0000.png").exists());
127 assert!(out_dir.join("frame_0001.png").exists());
128
129 Ok(())
130 }
131
132 #[test]
133 fn output_without_app_output() -> Result<(), Box<dyn std::error::Error>> {
134 let out = PathBuf::from("video.webm");
135 let result = apply_app_output(&out, None)?;
136 assert_eq!(result, PathBuf::from("video.webm"));
137 Ok(())
138 }
139
140 #[test]
141 fn output_with_relative_app_output() -> Result<(), Box<dyn std::error::Error>> {
142 let out = PathBuf::from("my.mp4");
143 let base_rel = PathBuf::from("test_previews_rel");
144 if base_rel.exists() {
145 std::fs::remove_dir_all(&base_rel)?;
146 }
147 let result = apply_app_output(&out, Some(base_rel.clone()))?;
148 assert_eq!(result, base_rel.join("my.mp4"));
149 assert!(base_rel.exists());
150 std::fs::remove_dir_all(&base_rel)?;
151 Ok(())
152 }
153
154 #[test]
155 fn output_with_absolute_app_output() -> Result<(), Box<dyn std::error::Error>> {
156 let out = PathBuf::from("my.gif");
157 let tmp = tempdir()?;
158 let abs = tmp.path().join("abs_previews");
159 let result = apply_app_output(&out, Some(abs.clone()))?;
160 assert_eq!(result, abs.join("my.gif"));
161 assert!(abs.exists());
162 Ok(())
163 }
164}