Please check the build logs for more information.
See Builds for ideas on how to fix a failed build, or Metadata for how to configure docs.rs builds.
If you believe this is docs.rs' fault, open an issue.
Sans-I/O word-level forced alignment with WhisperX-equivalent accuracy.
Overview
Sans-I/O cut/batch/whisper/align state machine for speech-to-text
indexing pipelines, inspired by WhisperX. Asry
itself owns no threads, channels, or runtime — you drive it from a
single thread (or wrap blocking calls in your async runtime),
feeding samples + VAD and pulling commands the runner answers via
sync compute primitives (AsrSource,
run_one_alignment).
Quick start
The wav2vec2-base-960h tokenizer ships inside the crate (parsed at
build time, no serde_json runtime dep) — only the encoder ONNX
and the Whisper ggml checkpoint are BYO. Both files live above
crates.io's 10 MB hard limit and cannot be bundled. Fetch them
once with the pinned commands below; each one verifies SHA-256
before installing so a republished or truncated upstream surfaces
as a hard failure rather than silently altering alignment output.
Whisper ggml model (ggml-large-v3-turbo.bin, ~1.6 GB)
ASRY_WHISPER_MODEL_SHA256="1fc70f774d38eb169993ac391eea357ef47c88757ef72ee5943879b7e8e2bc69"
TMP=""
ACTUAL=""
if [; then
;
fi
Wav2vec2 alignment encoder (English, ~378 MB)
ASRY_W2V_EN_SHA256="00b7cc69516c1ab63c429e63a2b543e4d42bb77441ec5b98ee935de175b00de1"
TMP=""
ACTUAL=""
if [; then
;
fi
(Asry's build.rs can fetch these for you. The two opt-ins are
independent: ASRY_FETCH_MODEL=1 fetches the whisper checkpoint,
ASRY_FETCH_W2V fetches the wav2vec2 alignment encoders — you do
not need the 1.6 GB whisper checkpoint just to align. The script
enforces the same SHA-256 pins. Plain cargo build makes no network
requests.)
Running the forced-alignment tests
ASRY_FETCH_W2V selects per language, so you only download what
you intend to run:
| Value | Fetches |
|---|---|
en |
English only — 378 MB. What you want. |
en,ja |
a comma-separated subset |
1 |
every language — ~10 GB. all is a synonym. |
(unset) / 0 |
nothing |
Valid codes: en, ja, zh, ko, es, fr, de, it, pt. A
typo is a hard build error, not a silent no-op.
Fetch a language's fixture and its alignment tests simply run — no
--ignored, no second step:
# ort is load-dynamic
ASRY_FETCH_W2V=en
Skip the fetch and those same tests are reported as ignored, with
the exact command that would enable each one. They are never reported
as passed: build.rs emits asry_w2v_<code> only once that
language's model and tokenizer are on disk and match their SHA-256
pins, and a test is #[ignore]d unless its own language's cfg is set.
Force one to run without its fixture (-- --ignored) and it fails
loudly rather than finding some way to report green.
That matters more than it sounds. These tests spent the project's
entire history behind an option_env! guard that returned early when
the fixture was absent — which was every CI run and every default
cargo test. Eleven tests, each claiming to load a 378 MB ONNX
encoder, reported ok in 0.00s without opening a file. A test that
reports success without executing is worse than no test: it occupies
the slot a real gate would fill.
Run an end-to-end alignment
use Path;
use ;
use ;
let aligner = from_paths?;
let alignment_set = new
.with_fallback
.register
.build;
let whisper_ctx = new;
let mut asr_source = new?;
let mut transcriber = new;
let abort_flag = new;
while let Some = transcriber.poll_command
while let Some = transcriber.poll_event
# Ok::
ORT_DYLIB_PATH overrides the default libonnxruntime lookup if
you keep the dylib elsewhere. The alignment feature uses ort
in load-dynamic mode — cargo build --features alignment
succeeds on a clean toolchain (no system libonnxruntime needed
at build time), but you must supply one at run time.
Async users (tokio, smol) wrap WhisperAsrSource::run_chunk and
run_one_alignment in spawn_blocking and wire shutdown via their
own cancellation tokens flipping abort_flag. Calling the chunk's
run_options.terminate() from another thread cancels in-flight
ORT inference mid-call; the alignment pipeline polls abort_flag
between coarse stages too.
Cargo features
| Feature | Default | What it enables |
|---|---|---|
std |
yes | std-backed implementations of crate types. Chains std to mediatime, smol_str, and serde when present. |
runner |
yes | WhisperAsrSource + the in-house whispercpp 0.2.x bindings + the temperature retry ladder + the real-zlib compression-ratio gate (via miniz_oxide). Implies std. |
emissions |
no | Ort-free forced alignment for a caller who owns the acoustic encoder. Drives EmissionsAligner (builder → prepare → your encoder → finish) over the validated seam types — Emissions, SpeechSpans / SampleSpan, SpeechCoverage, OutputClock, PreparedChunk — plus the Sans-I/O OOV surface (detect_oov, default_oov_decisions / wildcard_all_decisions / fail_closed_all_decisions, OovEvent / OovDecision / ResolvedOov), the per-language normalizers (default_normalizer_for, EnglishNormalizer, …), and the EmissionsError / SpanError taxonomy. The raw algorithm functions are crate-internal; the type-guarded surface is the only public one. No ort, no whisper.cpp. Reachable at asry::emissions::*; default-features = false, features = ["emissions"] for a consumer bringing its own acoustic encoder. |
alignment |
no | wav2vec2 forced alignment via ort (load-dynamic) layered on emissions. Lights up Aligner, AlignmentSet, run_one_alignment. Implies runner + emissions. |
serde |
no | Derive serde::{Serialize, Deserialize} on public state-machine types (Transcript, Word, AsrParams, …). Independent — does not imply runner; combine with any feature set, e.g. default-features = false, features = ["emissions", "serde"]. |
metal |
no | Apple-only: enables whispercpp/metal so the encoder runs on the unified-memory Metal backend. Implies runner. |
coreml |
no | Apple-only: enables whispercpp/coreml so the encoder additionally dispatches to ANE if the caller has produced a CoreML companion .mlmodelc. Implies runner. |
bench-internals |
no | Re-exports pub(crate) alignment internals (scalar/SIMD normaliser variants, the trellis/beam DP — get_trellis, backtrack_beam, align_to_word_segments — and LogProbsTV) under asry::__bench so the SIMD baseline bench can call them directly. Doc-hidden; never enable in shipping builds. Implies alignment. |
parity-dump-emission |
no | Diagnostic-only: writes wy_seg<N>.{emission,trellis}.bin + a wy_seg<N>.tokens.json companion to ASRY_PARITY_DUMP_TRELLIS whenever set. Implies alignment. Do NOT enable in shipping builds. |
The CTC parity tests run as part of the regular test suite — the OOV policy is per-test runtime data (no Cargo feature):
# 8/8 — tests 1-6 + 8 use `default_oov_decisions` (asry
# default); test 7 (`4,9` digits-comma WhisperX issue #1372)
# uses `wildcard_all_decisions` to opt into WhisperX 1:1
# behaviour for pronounced symbols.
These tests port WhisperX's
tests/test_word_timestamp_interpolation.py 1:1 onto asry's
CTC pipeline. The alignment-pipeline lib tests (everything under
src/runner/aligner/ + src/runner/alignment_pool/) pin the
same algorithmic invariants stage-by-stage; median IoU 0.9955–0.9990
across 854 word pairs vs. WhisperX's recorded outputs (measured
during initial calibration; see trellis_beam.rs:305-330).
Audio parity fixtures (~283 MB)
The 14 WAV clips + RTTM speaker annotations asry's
end-to-end parity tests reference live out-of-tree at
Findit-AI/audio-fixtures so they don't
bloat asry's git history. Populate them locally with:
The script shallow-clones the sibling repo and lays files out
under tests/parity/fixtures/<name>/{clip_16k.wav,reference.rttm}
(the layout existing tests expect). Idempotent + cleans the
clone unless ASRY_FIXTURES_KEEP_CLONE=1. CI runs the same
script when ASRY_FETCH_FIXTURES=1 is set on the workflow
run; default cargo test stays network-free.
License
asry is under the terms of both the MIT license and the
Apache License (Version 2.0).
See LICENSE-APACHE, LICENSE-MIT for details.
Copyright (c) 2026 FinDIT studio authors.
Bundled-asset attributions propagate to downstream binaries
asry parses one third-party asset into every compiled
binary via include_str! at build time:
| File | License | Source |
|---|---|---|
assets/wav2vec2_base_960h_tokenizer.json (parsed at build into Rust constants under OUT_DIR; bundled when alignment is on) |
Apache-2.0 | facebook/wav2vec2-base-960h (tokenizer.json) |
The full SPDX expression for an alignment-enabled build is
therefore (MIT OR Apache-2.0) AND Apache-2.0. When you
redistribute a binary that depends on asry, reproduce the
attribution somewhere a recipient can find — for instance, in
your application's "About" or third-party-licenses page.
Models you BYO at runtime (Whisper ggml checkpoints, wav2vec2
encoder ONNX, language-specific aligners) carry their own
licenses — see the source links above and on each HuggingFace
repo. Mirror copies under
huggingface.co/FinDIT-Studio
re-export upstream weights without modification; the upstream
license applies.