Skip to content

Add ReazonSpeech as the Japanese transcription engine - #16

Open
tomoima525 wants to merge 2 commits into
mainfrom
claude/reazonspeech-ja-engine-tfjqlc
Open

tomoima525 wants to merge 2 commits into
mainfrom
claude/reazonspeech-ja-engine-tfjqlc

Conversation

@tomoima525

Copy link
Copy Markdown
Owner

What

When Language is set to Japanese, sotto now transcribes with ReazonSpeech (k2 Zipformer, INT8 ONNX via sherpa-onnx, Apache-2.0, CPU-only) instead of Whisper — the approach hayamimi uses, where it measures CER 5.8% vs whisper-turbo's 13.8% on real Japanese speech. Universal/English always use Whisper.

Design decisions (as discussed): default-on for ja sessions, ReazonSpeech end-to-end (drafts + finals), lazy ~440MB download from the sherpa-onnx GitHub release tarball, LLM cleaner for punctuation, exposed as a Japanese Engine menu.

How

  • reazon.py (new): ReazonTranscriber, a duck-type drop-in for Transcriber (warmup/transcribe). modified_beam_search decoding (hayamimi: CER 8.6→5.8 for +25% decode time); strips ReazonSpeech's TV-subtitle brackets; no no_speech_prob exists so only the duration/RMS pre-gates apply (transducers return empty on silence rather than hallucinating). Recordings over 30s are cut at speech pauses by the existing energy VAD and decoded piecewise — offline zipformer decoding is meant for utterance-scale audio.
  • models.py: download + extract the release tarball into ~/Library/Application Support/sotto/models/, idempotent and lock-serialized (a menu prefetch can't race a dictation's lazy load), with tarfile's data filter. Model files resolved by glob like hayamimi, since basenames vary between exports.
  • pipeline.py: the engine is resolved per call, so Language/engine menu changes apply without a restart. If the model fails to download/load, Whisper stands in until the model shows up cached — and the streaming refine pass, which short-circuits when ReazonSpeech decodes the live segments (a second pass over the same audio with the same model adds nothing), resumes under the fallback. Net effect for ja streaming: stop latency is just the LLM pass.
  • app.py: "Japanese Engine" submenu; picking ReazonSpeech or switching Language to Japanese prefetches the model in the background with notifications; boot downloads it when the config needs it and never bricks startup on a failed fetch.
  • cli.py: sotto transcribe --engine reazonspeech|whisper and the same on stream for A/B testing on your own voice; sotto download --reazonspeech.
  • Punctuation: ReazonSpeech output is unpunctuated, so the cleanup LLM restores 。/、 — its gate fires on any unpunctuated text, meaning ja dictations always pay the ~1s LLM pass. With cleanup toggled off you get raw unpunctuated text (documented).

Tradeoffs documented in the README

  • English words mixed into Japanese speech come out worse than under Whisper (the menu offers Whisper for heavy code-switchers).
  • +~440MB disk, +~0.3GB RAM while the ja engine is resident.

Testing

  • python3 -m py_compile on all modules; uv lock resolves sherpa-onnx 1.13.7 (macOS arm64 wheels verified on PyPI for cp312).
  • No mlx/sherpa runtime in this Linux environment, so on-device checks to run: uv run sotto transcribe --language ja --engine reazonspeech vs --engine whisper (accuracy A/B), uv run sotto stream --language ja (draft cadence + no refine line), and a menu-driven download of the model.

🤖 Generated with Claude Code

https://claude.ai/code/session_01UitLA3APeum1DaiyajF46A


Generated by Claude Code

When Language is set to Japanese, transcribe with ReazonSpeech (the k2
Zipformer from reazon-research/reazonspeech-k2-v2, INT8 ONNX via
sherpa-onnx, CPU-only) instead of Whisper. On real Japanese speech it
measures well under half whisper-turbo's character error rate (hayamimi's
scorecard: CER 5.8% vs 13.8%) while decoding many times faster than
realtime, which also collapses streaming mode's stop latency: the live
segments are already decoded at full accuracy, so the refine pass
short-circuits and only the LLM cleanup remains.

- reazon: ReazonTranscriber, a duck-type drop-in for Transcriber
  (modified_beam_search; strips ReazonSpeech's TV-subtitle brackets;
  segments recordings over 30s with the energy VAD and joins the pieces)
- models: download/extract the sherpa-onnx GitHub release tarball
  (~440MB) into Application Support, idempotent and thread-safe
- pipeline: engine resolved per call so menu changes apply live; on
  load/download failure Whisper stands in (and refine resumes) until the
  model shows up cached
- app: "Japanese Engine" menu; selecting ReazonSpeech (or switching
  Language to Japanese) prefetches the model with notifications; boot
  downloads it when configured and survives a failed fetch
- cli: --engine on transcribe/stream for A/B testing, download
  --reazonspeech
- ReazonSpeech emits no punctuation, so the cleanup LLM restores it (its
  gate always fires on unpunctuated text); documented in the README along
  with the mixed EN/JA caveat and the Whisper menu alternative

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UitLA3APeum1DaiyajF46A
sherpa-onnx 1.13 split its native libraries (libonnxruntime and friends)
into a separate sherpa-onnx-core package, and declares that dependency
dynamically per platform -- uv's resolver misses it, so `uv sync`
installed the Python binding without its dylibs and importing sherpa_onnx
failed with "Library not loaded: @rpath/libonnxruntime.dylib". Declare
sherpa-onnx-core explicitly so it lands in the lockfile.

Also extract the ReazonSpeech tarball into a staging directory and rename
it into place, so an interrupted extraction can never leave a truncated
.onnx at the final path (which would fail every later load with an opaque
onnxruntime/protobuf error until manually deleted).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UitLA3APeum1DaiyajF46A
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants