aether_renderer_core/ffmpeg/
gif.rs

1use std::fs;
2use std::process::Command;
3
4/// Render a GIF using palettegen + paletteuse filters
5pub fn render_gif(
6    input_pattern: &str,
7    output: &str,
8    fps: u32,
9    fade_filter: Option<&str>,
10) -> Result<(), String> {
11    let palette_path = "palette.png";
12
13    let mut palette_args: Vec<String> = Vec::new();
14    if input_pattern.contains('*') {
15        palette_args.push("-pattern_type".into());
16        palette_args.push("glob".into());
17    }
18    palette_args.push("-i".into());
19    palette_args.push(input_pattern.into());
20    palette_args.push("-vf".into());
21    palette_args.push("fps=30,scale=640:-1:flags=lanczos,palettegen".into());
22    palette_args.push("-y".into());
23    palette_args.push(palette_path.into());
24
25    let palette_status = match Command::new("ffmpeg").args(&palette_args).status() {
26        Ok(s) => s,
27        Err(e) => {
28            if e.kind() == std::io::ErrorKind::NotFound {
29                return Err(
30                    "❌ ffmpeg not found. Please install ffmpeg and ensure it is in your PATH."
31                        .into(),
32                );
33            } else {
34                return Err(format!("❌ Failed to execute ffmpeg: {}", e));
35            }
36        }
37    };
38
39    if !palette_status.success() {
40        return Err("❌ Failed to generate palette".into());
41    }
42
43    let mut gif_filter = String::from("fps=30,scale=640:-1:flags=lanczos");
44    if let Some(filter) = fade_filter {
45        if !filter.is_empty() {
46            gif_filter.push(',');
47            gif_filter.push_str(filter);
48        }
49    }
50    let mut gif_args: Vec<String> = vec!["-framerate".into(), fps.to_string()];
51    if input_pattern.contains('*') {
52        gif_args.push("-pattern_type".into());
53        gif_args.push("glob".into());
54    }
55    gif_args.push("-i".into());
56    gif_args.push(input_pattern.into());
57    gif_args.push("-i".into());
58    gif_args.push(palette_path.into());
59    gif_args.push("-lavfi".into());
60    gif_args.push(format!("{} [x]; [x][1:v] paletteuse", gif_filter));
61    gif_args.push("-y".into());
62    gif_args.push(output.into());
63
64    let gif_status = match Command::new("ffmpeg").args(&gif_args).status() {
65        Ok(s) => s,
66        Err(e) => {
67            if e.kind() == std::io::ErrorKind::NotFound {
68                return Err(
69                    "❌ ffmpeg not found. Please install ffmpeg and ensure it is in your PATH."
70                        .into(),
71                );
72            } else {
73                return Err(format!("❌ Failed to execute ffmpeg: {}", e));
74            }
75        }
76    };
77
78    fs::remove_file(palette_path)
79        .unwrap_or_else(|e| eprintln!("⚠️ Failed to remove palette file: {}", e));
80
81    if gif_status.success() {
82        println!("✅ GIF exported: {}", output);
83        Ok(())
84    } else {
85        Err("❌ Failed to export GIF".into())
86    }
87}