Anki add-on: Local TTS for Japanese (VOICEVOX)

Synthesize Japanese TTS audio at review time through a local VOICEVOX server — no cloud, no per-character fees, no pre-generated media files. Why Cloud TTS handles Japanese poorly, and the good engines (ElevenLabs, OpenAI, Azure...) charge per character. Existing Anki TTS add-ons also bake the voice choice into card templates, which makes switching presets painful. Local TTS lets you: Run VOICEVOX (free, local, dramatically better Japanese quality) Pick voices via a live "Pick voice from server" dialog with a built-in test play Route presets centrally: per-deck, per-note-type, or per-language, edited from one dialog — no template re-saves Global cleanup + regex rules in a dedicated Rules tab, with optional per-preset overrides Cache synthesized audio (Opus, ~12× smaller than WAV) keyed only by provider+options+processed text, so tweaking rules doesn't reinvalidate everything; never touches collection.media Use Anki's built-in {{tts ja_JP voices=LocalTTS:Field}} tag — no custom template syntax Requirements Anki 24.x or newer (desktop only — local engines can't run on AnkiMobile / AnkiWeb) A running VOICEVOX server. Quickest setup: docker run --rm -p 50021:50021 voicevox/voicevox_engine:cpu-latest See voicevox.hiroshiba.jp for native Mac / Windows installers. Optional: ffmpeg on PATH for Opus cache compression (falls back to WAV). Quick start Start VOICEVOX. Install this addon, restart Anki. Tools → Local TTS settings… → confirm the endpoint matches your VOICEVOX URL. In your card template, add {{tts ja_JP voices=LocalTTS:Expression}} (replace Expression with the actual field name holding Japanese text). Review a card — cache miss ~1–2s, cache hit instant. Settings guide All settings live under Tools → Local TTS settings… . Save applies on next playback — no restart. General Enable Local TTS — master switch. Default preset — used when no per-deck / per-notetype / per-language rule matches. ffmpeg path — leave blank to auto-detect on PATH and common install locations. Without ffmpeg the cache falls back to WAV (~12× larger). Cache size limit — disk budget for synthesized audio; LRU-evicted when full. Clear cache now — delete every cached file. Providers VOICEVOX endpoint (default http://localhost:50021 ) — shared across every preset that uses regular VOICEVOX. Change once when you move the server. VOICEVOX Nemo endpoint (default http://localhost:50121 ) — the Nemo engine is a sibling project with character-less, neutral voices aimed at business / educational TTS. Same HTTP API, runs as its own binary on a different port. Boot both engines if you want regular voices on some cards and Nemo voices on others; routing decides which preset plays where. Presets A preset is provider + voice ID + per-voice options. Pick voice from server… (new-preset only) — live-queries VOICEVOX with a built-in test play. Speed / pitch / intonation / volume — each has a "Use global" checkbox; tick off to pin a preset-specific value, leave on to inherit from Voice defaults. Optional checkboxes to override the global cleanup or regex rules with a per-preset block. Rules (global, every preset inherits unless overridden) Cleanup — ruby mode (base / reading), bracket-reading mode (base / reading / keep both), bracket pairs, and a toggle for collapsing furigana-add-on space artifacts. Always-on: HTML strip, Anki cloze braces, whitespace normalize. Numbers — Read numbers as words (on by default). Rewrites ASCII / full-width digit runs to classical kanji before synthesis ( 1990 → 千九百九十 , 7月 → 七月 ), so VOICEVOX reads numbers as numbers instead of digit-by-digit ("nana tsuki" → "shichi-gatsu"). Off → digits are sent verbatim. Regex rules — ordered pattern → replacement substitutions applied after cleanup. Validate button compiles every pattern and reports errors. Broken patterns are skipped at runtime, never crash playback. Split marker — insert this character (default ・ ) in card text where you want a short pause instead of VOICEVOX's default ~0.15s comma pause. Pause length is configurable (0 = no audible gap). VOICEVOX renders digits on either side as separate numbers (e.g. 三・四倍 reads "san, yon-bai", not "sanjuuyon-bai") and prosody flows continuously across the join. Optional Auto-mark digit-、-digit pauses checkbox rewrites every 、 between two digits (half-width / full-width / CJK) to the marker, so you don't have to type it in cards. Commas in non-digit contexts ( 三月、四月 , 日本、英語 ) are untouched. Voice defaults — global baseline for speed / pitch / intonation / volume. Each preset inherits unless it pins its own value. Routing Decides which preset plays when a TTS tag fires. First match wins: By deck → preset By note type → preset By language → preset (uses the language root, e.g. ja for ja_JP ) Default preset (set under General) A quick switcher at Tools → Local TTS · Routes lets you change the default / per-language / per-deck preset without opening Settings. Audio export (off by default) Persists generated audio into a note field so it plays on AnkiMobile / AnkiDroid, which can't run a local engine — the one sanctioned exception to "never touches collection.media ". Exported audio is transcoded to MP3 (needs ffmpeg ) rather than reused from the cache as-is, because iOS cannot decode the cache's Opus-in-Ogg files at all — MP3 is guaranteed to play on every Anki client. Configure a source field → audio field mapping; while reviewing, a played field's text is matched against exactly one source field before anything is written, so e.g. a second example-sentence field's audio can never land in the first one's audio field. Auto-fill only writes to an empty audio field, so existing edits are never clobbered. "Save audio for this card" synthesizes every mapped field on the current note and overwrites, for backfilling cards already reviewed — trigger it from the Tools menu, a configurable shortcut (default Ctrl+Shift+O , i.e. Cmd+Shift+O on macOS; bound application-wide so it works during review), or a 🔊 button in the note editor toolbar (Browser / Add / review answer side). To avoid double playback on desktop, wrap the live tag in a conditional: {{^Field_audio}}{{tts ja_JP voices=LocalTTS:Field}}{{/Field_audio}}{{Field_audio}} . Every step logs to user_files/log/local_tts.log for troubleshooting. Current state Only VOICEVOX is wired up so far. The architecture is provider-agnostic (Piper / Style-Bert-VITS2 / generic HTTP designed to slot in), but nothing else is implemented yet. Heavily vibe-coded. No maintenance guarantees. I built this for myself. PRs welcome — see the GitHub repo. Source / contributing https://github.com/tzsikp/anki_local_tts MIT licensed.

GitHub repository: tzsikp/anki_local_tts
Repository description: An Anki add-on that synthesizes audio at review time through local AI TTS engines.
Languages: Python
Topics: anki-addon, tts