rsmp4decrypt 0.2.0

Rust bindings and a CLI for Bento4 mp4decrypt
use clap::{ArgAction, Parser};
use rsmp4decrypt::Mp4Decryptor;
use std::{
    io::{self, Write},
    path::PathBuf,
};

#[derive(Debug, Parser)]
#[command(
    name = "rsmp4decrypt",
    about = "Rust bindings and CLI for Bento4 mp4decrypt",
    version = concat!(env!("CARGO_PKG_VERSION"), " (Bento4 v1.6.0-641)"),
    after_help = "Key syntax:\n  --key <id>:<key>\n      <id> is either a track ID in decimal or a 128-bit KID in hex\n      <key> is a 128-bit decryption key in hex\n      note: KIDs are only applicable to some encryption methods like MPEG-CENC\n      note: --fragments-info is typically the init segment when decrypting fMP4 fragments"
)]
struct Cli {
    #[arg(long = "show-progress", action = ArgAction::SetTrue)]
    show_progress: bool,

    #[arg(long = "key", value_name = "ID:KEY", required = true, action = ArgAction::Append)]
    keys: Vec<String>,

    #[arg(long = "fragments-info", value_name = "FILE")]
    fragments_info: Option<PathBuf>,

    #[arg(value_name = "INPUT")]
    input: PathBuf,

    #[arg(value_name = "OUTPUT")]
    output: PathBuf,
}

fn main() {
    if let Err(error) = run() {
        eprintln!("error: {error}");
        std::process::exit(1);
    }
}

fn run() -> Result<(), Box<dyn std::error::Error>> {
    let cli = Cli::parse();
    let mut builder = Mp4Decryptor::builder();
    for key in &cli.keys {
        builder = builder.key_spec(key)?;
    }
    let decryptor = builder.build()?;

    if cli.show_progress {
        let mut progress = |step: u32, total: u32| {
            print!("\r{step}/{total}");
            let _ = io::stdout().flush();
        };

        decryptor.decrypt_file_with_progress(
            &cli.input,
            &cli.output,
            cli.fragments_info.as_ref(),
            &mut progress,
        )?;
        println!();
    } else {
        decryptor.decrypt_file(&cli.input, &cli.output, cli.fragments_info.as_ref())?;
    }

    Ok(())
}