Self-hosted live captioning from steveseguin/captionninja and steveseguin/caption-local. Upstream publishes no images, so images/build.sh builds them from pinned commits.
CAPTION.Ninja + Caption Local
Self-hosted live captioning: CAPTION.Ninja's capture/editor/overlay pages and private caption relay, plus Caption Local Whisper speech-to-text. Nothing is sent to caption.ninja, Google or any cloud STT provider.
- Category: Apps
- Status: 1, alpha
- Image:
git.autonomic.zone/recipe-maintainers/caption-ninja-*, 1, built from upstream source (see Images) - Healthcheck: Yes
- Backups: No (no user data; the model volume is a cache)
- Email: No
- Tests: 3
- SSO: No
Services
| Service | Hostname | What |
|---|---|---|
app |
$DOMAIN |
nginx serving the caption.ninja pages |
relay |
wss://$DOMAIN/relay |
private caption relay (Node, ws), rooms source and output |
stt |
$STT_DOMAIN |
Caption Local (faster-whisper, CPU), also serves its own capture page |
Captions flow: capture (microphone → STT) publishes to room source → editor
reviews and forwards to room output → overlay (OBS browser source / audience).
Basic usage
- Set up Docker Swarm,
abraandcoop-cloud/traefik - Make the images available on the server (see Images)
abra app new caption-ninja --secretsabra app config YOURAPPDOMAIN: setDOMAINandSTT_DOMAIN(both need DNS). The STT host needs RAM for the model: about 1.5 GB forsmall. SetCAPTION_THREADSto at most your core count.abra app deploy YOURAPPDOMAIN. On first startsttdownloads the Whisper model into themodelsvolume, so it can take several minutes to turn healthy.
Using it
Read the tokens (keep them private; read tokens are for viewers, write tokens publish):
abra app run YOURAPPDOMAIN relay -- sh -c 'for s in src_read src_write out_read out_write; do echo "$s=$(cat /run/secrets/$s)"; done'
abra app run YOURAPPDOMAIN stt -- cat /run/secrets/stt_api_key
- Open
https://$STT_DOMAIN/, enter thestt_api_keyservice token, then pick a microphone. - Under Send captions to caption.ninja → Use a private relay, enter:
relay
wss://$DOMAIN/relay, caption websitehttps://$DOMAIN/, source roomsource+src_writetoken, editor output roomoutput. Enable sharing. - Open the generated editor link and enter
src_readandout_write. - In the editor, create the view-only OBS/overlay link with
out_read.
Alternatively use https://$DOMAIN/capture-local.html with service address
https://$STT_DOMAIN.
Note: the other caption.ninja pages (index.html, translate.html, …) still
default to the public relay wss://api.caption.ninja. Only capture-local.html,
editor.html and overlay.html support the private relay.
Images
Upstream publishes no Docker images, so images/build.sh builds them from pinned
upstream commits. It needs Docker, git and no registry credentials:
scp -r images SERVER:caption-ninja-build/
ssh SERVER 'bash caption-ninja-build/images/build.sh'
The images are tagged with their registry names, so a single-node swarm can deploy
straight from the local tags. To publish them to the registry from a machine
that has docker login git.autonomic.zone:
for img in $(ssh SERVER "docker images --format '{{.Repository}}:{{.Tag}}' | grep caption-ninja-"); do
ssh SERVER docker save "$img" | docker load && docker push "$img"
done
To upgrade, bump the commits in images/build.sh and the tags in compose.yml.
Security notes
- Caption Local only accepts
Host: localhost(upstream expects an SSH tunnel), so Traefik rewrites the Host header for$STT_DOMAIN;sttis still only reachable through Traefik, and every API call needs the token. - The STT token is a single shared token, not per-user auth. Anyone with it can use
your CPU for transcription. Rotate it with
abra app secret+ redeploy. - Relay origins are
https://$DOMAINandhttps://$STT_DOMAIN; add more exact origins withRELAY_EXTRA_ORIGINS(e.g.https://steveseguin.github.io). - To revoke relay tokens, generate new secret versions and redeploy (restart disconnects all clients and clears the 2-minute replay buffer).