Kaku
Kaku (書く, to write down) is a small server you run on a machine with an NVIDIA GPU. It keeps the Japanese podcast and audiobook libraries in your Audiobookshelf transcribed with Whisper large-v3 (through faster-whisper), and hands the apps timed transcripts split into sentences:
- Kiku shows the transcript line by line as you listen, and you tap words in it. Without Kaku, episodes play without their text.
- Yomu uses the transcript to match an audiobook to its EPUB for read-along.
- Miru can ask Kaku for Japanese subtitles when Jimaku has none. Jellyfin episodes are transcribed only when you ask, never on their own.
Kaku only reads from Audiobookshelf and Jellyfin; it never changes anything on them.
What it does on its own
Section titled “What it does on its own”- Every 5 minutes (and straight away when you change which libraries it covers), Kaku looks for podcast episodes and audiobooks that don’t have a transcript yet and queues them.
- It works through one at a time. What you ask for in an app comes first, then episodes added in the last week, then the rest of the backlog, newest first.
- For each one it downloads the audio, transcribes it, saves the transcript and deletes the audio.
- If an episode’s audio file changes, it’s transcribed again. A failed job is retried twice, then waits until something asks for it again.
- Whisper loads on the first job and is unloaded after 10 minutes with nothing to do, giving the GPU memory back to anything else that uses it.
As a rough guide, on a mid-range consumer GPU a 24-minute episode takes about 30 seconds and a 6.6-hour audiobook about 25 minutes.
Requirements
Section titled “Requirements”- An NVIDIA GPU with about 4 GB of free memory while Kaku works (about 3.7 GB with the model loaded, up to about 5 GB at peak).
- An NVIDIA driver that supports CUDA 12.8. The image is built on CUDA 12.8, which RTX 50-series (Blackwell) cards need.
- Docker with the NVIDIA Container Toolkit, so containers can use the
nvidiaruntime. - Audiobookshelf 2.26.0 or later, with an API key for Kaku. Kaku needs it even if you only want Miru’s subtitles.
- Optional: Jellyfin, with an API key for Kaku, for Miru’s subtitles.
- About 3 GB of disk for the Whisper model, downloaded from Hugging Face on the first job, plus room for the transcripts.
Running it
Section titled “Running it”Kaku lives in the Server/kaku folder
of the repository.
-
Copy the example settings:
Terminal window cd Server/kakucp env.example .env -
Fill in
.env. It holds keys, so keep it private (Git ignores it).Setting What to put AUDIOBOOKSHELF_URLAudiobookshelf’s address as Kaku reaches it, for example http://audiobookshelf.example.com:13378AUDIOBOOKSHELF_API_KEYAn API key from Audiobookshelf’s Settings ▸ API Keys KAKU_API_TOKENAny long random string, for example from openssl rand -hex 32. The apps send it with every requestJELLYFIN_URL,JELLYFIN_API_KEYOptional: Jellyfin’s address and an API key made for Kaku in Jellyfin’s Dashboard ▸ API Keys KAKU_PORTThe port on the host (8000 unless you change it) TZYour time zone, for example Asia/Tokyo -
Build and start it:
Terminal window docker compose up -d --buildThe database and transcripts are kept in
./data, and the Whisper model in./models, both next to the compose file. -
Check that it’s up:
Terminal window curl http://kaku.example.com:8000/healthThis is the only address that works without the token. It shows the version, whether the model is loaded, GPU memory, the job counts and whether Jellyfin is set up.
Turning libraries on
Section titled “Turning libraries on”Every library starts switched off, so an English library is never transcribed by accident. There’s no screen for this yet: you use two requests with your token.
List Audiobookshelf’s libraries:
curl -H "Authorization: Bearer YOUR_TOKEN" http://kaku.example.com:8000/librariesEach library has an id, its name, its mediaType (podcast or book) and whether it’s enabled.
Then turn on the Japanese ones by sending their ids:
curl -X PUT -H "Authorization: Bearer YOUR_TOKEN" -H "Content-Type: application/json" \ -d '{"enabled": ["first-library-id", "second-library-id"]}' \ http://kaku.example.com:8000/librariesThe list you send replaces the old one: libraries left out are switched off. Kaku then queues their backlog. To see what it’s working on:
curl -H "Authorization: Bearer YOUR_TOKEN" http://kaku.example.com:8000/jobsConnecting the apps
Section titled “Connecting the apps”Each app signs in to Kaku on its own, with Kaku’s address and the token (KAKU_API_TOKEN):
| App | Where |
|---|---|
| Kiku | Settings ▸ Listening ▸ Transcripts |
| Yomu | Settings ▸ Reading ▸ Read-Along ▸ Kaku |
| Miru | Settings ▸ Watching ▸ Subtitles ▸ Kaku |
Kiku and Miru also offer it in the Extras step of their first-launch setup.
Once connected:
- Kiku shows which episodes have a transcript. Opening one that’s still waiting moves it to the front of Kaku’s queue. While you stream, Kaku also cuts the audio clips for your cards.
- Miru: open the Subtitles sheet on an episode and choose Transcribe with Kaku, or Transcribe the Rest of the Season for the later episodes too. The sheet shows the queue place and progress. Kaku uses the episode’s Japanese audio track and refuses an episode without one.
Keep it private
Section titled “Keep it private”The compose file publishes plain HTTP. That’s fine on your home network, but before you use Kaku away from home, put it behind HTTPS (a reverse proxy) or a VPN such as Tailscale. The token is sent with every request, and nothing else protects Kaku.
Updating
Section titled “Updating”Pull the new code, then build and start it again:
git pullcd Server/kakudocker compose up -d --build./data and ./models are kept, so transcripts and the model survive.
When an update changes how lines are split into sentences, you can split the transcripts you already
have again, from the words they store, without the GPU. Back up data/transcripts first, then:
docker compose exec kaku python3 -m kaku.resplit --dry-run # see what would changedocker compose exec kaku python3 -m kaku.resplitOther settings
Section titled “Other settings”These aren’t in the compose file; add them under environment: to use them:
| Setting | Default | What it does |
|---|---|---|
KAKU_MODEL |
large-v3 |
The Whisper model |
KAKU_POLL_SECONDS |
300 |
How often Kaku looks for new episodes |
KAKU_IDLE_UNLOAD_SECONDS |
600 |
How long the model stays loaded with nothing to do |
Known limits
Section titled “Known limits”- Some shows’ transcripts get speaker names written into the text without punctuation. Those lines are only split at pauses.
- Word timings are faster-whisper’s own, so a word’s start can be a little off.
