Files
filmcraft/crates/speech/examples/transcribe.rs
Brandon ThomasandClaude Opus 5.5 3a392ae8da Clippy 1.99: fix new lints so cargo xtask ci is green again
Rust/clippy 1.99 added `chunks_exact_to_as_chunks` and tightened
`needless_late_init` and `drain_collect`, failing `clippy -D warnings`
on main.

- `chunks_exact(N)` / `chunks_exact_mut(N)` with a constant N become
  `as_chunks::<N>()` / `as_chunks_mut::<N>()` (fixed-size arrays, so
  several byte reads drop their per-element indexing). relink's
  fingerprint uses the `(chunks, remainder)` split; its hash output is
  unchanged.
- Late-initialised `let`s in ac3 bit allocation and opus CELT
  bands/rate become `let x = if/match …`.
- `dpb.drain(..).collect()` becomes `std::mem::take(&mut dpb)`.

No behaviour change; `cargo xtask ci` passes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 15:55:23 -04:00

66 lines
2.9 KiB
Rust
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! Transcribe raw mono 16 kHz f32 files and report the word error rate against `<file>.txt`.
//!
//! ```sh
//! ffmpeg -i speech.flac -ar 16000 -ac 1 -f f32le speech.f32
//! cargo run --release -p filmcraft-speech --features whisper --example transcribe -- <model dir> [--words] [--diarize] speech.f32…
//! ```
use filmcraft_speech::{Options, Transcriber, word_error_rate};
fn main() {
let mut args = std::env::args().skip(1);
let dir = std::path::PathBuf::from(args.next().expect("model dir"));
let id = dir.file_name().map(|n| n.to_string_lossy().to_string()).unwrap_or_default();
let t0 = std::time::Instant::now();
let w = filmcraft_speech::whisper::Whisper::load(&dir, &id).expect("load");
eprintln!("loaded {id} in {:.2}s", t0.elapsed().as_secs_f64());
let mut show_words = false;
let mut diarize = false;
let mut language = None;
let (mut errs, mut words, mut audio_s, mut cpu_s) = (0.0, 0usize, 0.0, 0.0);
for a in args {
match a.as_str() {
"--words" => show_words = true,
"--diarize" => diarize = true,
l if l.starts_with("--lang=") => language = Some(l[7..].to_string()),
p => {
let audio: Vec<f32> = std::fs::read(p).expect("read").as_chunks::<4>().0.iter().map(|b| f32::from_le_bytes(*b)).collect();
let t1 = std::time::Instant::now();
let opts = Options { language: language.clone(), diarize, ..Default::default() };
let t = w.transcribe(&audio, &opts, &mut |_, _| true).expect("transcribe");
let secs = t1.elapsed().as_secs_f64();
audio_s += audio.len() as f64 / 16_000.0;
cpu_s += secs;
let hyp = t.text();
print!("{p} [{} {:.1}s]: {hyp}", t.language, secs);
if let Ok(r) = std::fs::read_to_string(std::path::Path::new(p).with_extension("txt")) {
let n = r.split_whitespace().count();
let e = word_error_rate(&r, &hyp);
errs += e * n as f64;
words += n;
print!(" (WER {:.1}%)", e * 100.0);
}
println!();
if show_words {
for x in &t.words {
println!(
" {:7.2} {:7.2} {:>3} {}",
x.start.0 as f64 / 254_016_000_000.0,
x.end.0 as f64 / 254_016_000_000.0,
x.speaker.map(|s| s.to_string()).unwrap_or_default(),
x.text
);
}
}
}
}
}
if words > 0 {
println!(
"total WER {:.2}% over {words} words; {audio_s:.1}s of audio in {cpu_s:.1}s ({:.1}× realtime)",
errs / words as f64 * 100.0,
audio_s / cpu_s.max(1e-9)
);
}
}