use std::path::PathBuf;
use aether_renderer_capture::sink::FrameSink;
use aether_renderer_capture::{CaptureOptions, Mode, capture};
use anyhow::{Result, anyhow};
use clap::Parser;
#[derive(Parser, Debug)]
#[command(
name = "aether-renderer-capture",
version,
about = "Capture HTML animations as a deterministic, transparent PNG sequence"
)]
struct Args {
#[arg(short, long)]
input: String,
#[arg(short, long, default_value = "frames")]
out: PathBuf,
#[arg(long)]
stdout: bool,
#[arg(long, default_value_t = 30)]
fps: u32,
#[arg(short, long, default_value_t = 2.0)]
duration: f64,
#[arg(short, long, default_value = "512x512")]
size: String,
#[arg(long, env = "AETHER_CHROME")]
chrome: PathBuf,
#[arg(long, value_enum, default_value_t = Mode::Auto)]
mode: Mode,
#[arg(long, env = "AETHER_NO_SANDBOX", value_parser = clap::builder::BoolishValueParser::new())]
no_sandbox: bool,
}
fn parse_size(s: &str) -> Result<(u32, u32)> {
let (w, h) = s
.split_once('x')
.ok_or_else(|| anyhow!("size must be WIDTHxHEIGHT"))?;
Ok((w.parse()?, h.parse()?))
}
#[tokio::main]
async fn main() -> Result<()> {
let args = Args::parse();
let (width, height) = parse_size(&args.size)?;
let opts = CaptureOptions::new(args.input, args.chrome)
.size(width, height)
.fps(args.fps)
.duration_secs(args.duration)
.mode(args.mode)
.no_sandbox(args.no_sandbox);
opts.validate()?;
let mut sink = FrameSink::open(&args.out, args.stdout)?;
let pb = if sink.is_stdout() {
indicatif::ProgressBar::hidden()
} else {
indicatif::ProgressBar::new(opts.frame_count())
};
let report = capture(&opts, |i, png| {
sink.write(i, &png)?;
pb.inc(1);
Ok(())
})
.await?;
pb.finish_and_clear();
sink.finish()?;
eprintln!(
"captured {} frames @ {} fps ({}x{}, {:?} mode), {} reused (no damage)",
report.frames, report.fps, report.width, report.height, report.mode, report.reused
);
Ok(())
}