use clap::{Arg, Command};
use std::{fs, io::Write, path::PathBuf, process::Command as Pcmd, time::SystemTime};
use sha2::{Digest, Sha256};
fn sha256_file(path: &PathBuf) -> anyhow::Result<String> {
let bytes = fs::read(path)?;
let mut h = Sha256::new();
h.update(&bytes);
Ok(format!("{:x}", h.finalize()))
}
fn timestamp() -> String {
let dt = SystemTime::now();
let chrono_dt: chrono::DateTime<chrono::Utc> = dt.into();
chrono_dt.format("%Y-%m-%dT%H%M%S").to_string()
}
fn main() -> anyhow::Result<()> {
let m = Command::new("whisper_input (demo)")
.about("Records a short audio clip or uses --file, stores it in ./vaults, computes SHA256, and can auto-anchor via vault_license.")
.arg(Arg::new("file").long("file").value_name("WAV").help("Use an existing WAV file"))
.arg(Arg::new("seconds").long("seconds").value_name("N").default_value("5").help("Record N seconds via ffmpeg if --file not given"))
.arg(Arg::new("auto_mint").long("auto-mint").action(clap::ArgAction::SetTrue).help("Automatically call ./target/release/vault_license --hash <sha>"))
.get_matches();
fs::create_dir_all("vaults")?;
let wav_path: PathBuf = if let Some(f) = m.get_one::<String>("file") {
PathBuf::from(f)
} else {
let secs = m.get_one::<String>("seconds").unwrap().parse::<u64>().unwrap_or(5);
let out = PathBuf::from(format!("vaults/voice_{}.wav", timestamp()));
eprintln!("Recording {secs}s from default mic to {:?}", out);
let status = Pcmd::new("ffmpeg")
.args([
"-y",
"-f", "avfoundation", "-i", ":0",
"-t", &secs.to_string(),
out.to_str().unwrap(),
])
.status();
match status {
Ok(s) if s.success() => out,
_ => {
eprintln!("ffmpeg recording failed. Try selecting a different input index (e.g. :1).");
std::process::exit(2);
}
}
};
let dst_path = if wav_path.starts_with("vaults/") {
wav_path.clone()
} else {
let dst = PathBuf::from(format!("vaults/voice_{}.wav", timestamp()));
fs::copy(&wav_path, &dst)?;
dst
};
let sha = sha256_file(&dst_path)?;
println!("Saved: {}", dst_path.display());
println!("SHA256: {}", sha);
let manifest = serde_json::json!({
"type": "voice_log_demo",
"file": dst_path.to_string_lossy(),
"sha256": sha,
"created_at": chrono::Utc::now().to_rfc3339(),
});
let manifest_path = PathBuf::from(format!("vaults/{}.json", dst_path.file_stem().unwrap().to_string_lossy()));
let mut f = fs::File::create(&manifest_path)?;
f.write_all(serde_json::to_string_pretty(&manifest)?.as_bytes())?;
println!("Manifest: {}", manifest_path.display());
let auto = m.get_flag("auto_mint");
if auto {
println!("\n-- auto-minting via vault_license --");
let status = Pcmd::new("./target/release/vault_license")
.args(["--hash", &sha])
.status();
match status {
Ok(s) if s.success() => {
println!("vault_license completed.");
}
_ => {
eprintln!("vault_license failed to run. You can run it yourself:\n ./target/release/vault_license --hash {}", sha);
}
}
} else {
println!("\nNext step (manual mint):\n ./target/release/vault_license --hash {}", sha);
}
Ok(())
}