mirror of
https://github.com/storytold/filmcraft.git
synced 2026-10-26 02:59:51 -06:00
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>
66 lines
2.9 KiB
Rust
66 lines
2.9 KiB
Rust
//! 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)
|
||
);
|
||
}
|
||
}
|