webp2gif 0.1.0

Convert an animated WEBP into an animated GIF, nothing else
use clap::Parser;
use zengif::{EncodeRequest, EncoderConfig, FrameInput, Limits, Repeat, Unstoppable};
use zenwebp::decoder::LoopCount;
use zenwebp::mux::AnimationDecoder;

#[derive(Parser, Debug)]
#[command(about = "Convert a web pee file to a gif", long_about = None)]
struct Args {
    #[clap(help = "Webp you want to read")]
    source_file: String,
    #[clap(help = "Gif you want to write")]
    destination_file: String,
    #[clap(short, long, help="overwrite destination file if it exists", default_value_t=false)]
    force: bool,
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let args = Args::parse();
    let bytes = std::fs::read(&args.source_file)?;
    let decoder = zenwebp::WebPDecoder::new(&bytes)?;
    // fail fast in case destination file already exists
    if !args.force && std::fs::exists(&args.destination_file)? {
        println!("Destination file already exists, quitting");
        return Ok(());
    }
    let (w, h) = decoder.dimensions();
    let anim: bool = decoder.is_animated();
    let loop_count = decoder.loop_count();
    let loop_duration = decoder.loop_duration();
    let frames = decoder.num_frames();
    println!("Source file: {}", args.source_file);
    println!(" - Dimensions: {}x{}", w, h);
    println!(" - Has alpha: {}", decoder.has_alpha());
    if !anim {
        println!("\n\nNo animation, quitting. todo i guess; use tui-img instead or XnViewMP until then");
        return Ok(());
    };

    println!(" - Animation frames: {frames}");
    println!(" - Loop count and total length: {loop_count:?} / {loop_duration}ms");

    let gif_loop_count = match loop_count {
        LoopCount::Forever => Repeat::Infinite,
        LoopCount::Times(n) => Repeat::Count(n.into()),
    };
    println!("Decoding all frames...");
    let frames = AnimationDecoder::new(&bytes)?.decode_all()?;
    let config = EncoderConfig::new()
        .use_transparency(decoder.has_alpha())
        .repeat(gif_loop_count);
    let limits = Limits::default().max_dimensions(4096, 4096);
    let mut encoder = EncodeRequest::new(&config, w as u16, h as u16)
        .limits(&limits)
        .stop(&Unstoppable)
        .build()?;
    print!("Adding frames to GIF: ");
    let mut frame_index: u64 = 0;
    for frame in frames {
        frame_index += 1;
        print!("{frame_index} ");
        encoder.add_frame(FrameInput::from_bytes(
            frame.width as u16,                            // == meta.width  (canvas dims)
            frame.height as u16,                           // == meta.height
            (frame.duration_ms as u16).saturating_div(10), // centiseconds, preserved from source
            &frame.data,                                   // Vec<Rgba>, full-canvas composited
        ))?;
    }
    println!("\nTurning it into binary mush");
    let output_gif_bytes = encoder.finish()?;
    if std::fs::exists(&args.destination_file)? && args.force {
        println!("Overwriting existing file in place: {}", args.destination_file)
    } else {
        println!("Writing to disk: {}", args.destination_file);
    }

    std::fs::write(&args.destination_file, output_gif_bytes)?;

    Ok(())
}