- images/build.sh builds all three images from one pinned captions-deploy commit (submodules pin the captions.coop forks; web/Dockerfile is the UI). - app: UI image configured by env (landing page, site-config.js), translation models downloaded into a volume on start (TRANSLATION_MODELS). - stt: RELAY_AUTOFILL=source|all|off feeds the capture page's /relay-config. - Drops the recipe's own nginx.conf and web Dockerfile.
CAPTION.Ninja + Caption Local
Self-hosted live captioning from captions.coop/captions-deploy: the captions.coop forks of CAPTION.Ninja (capture/editor/overlay pages, private caption relay, local Bergamot translation) and Caption Local (Whisper speech-to-text). Nothing is sent to caption.ninja, Google or any cloud STT/translation provider.
- Category: Apps
- Status: 1, alpha
- Image:
git.autonomic.zone/recipe-maintainers/caption-ninja-*, 1, built from captions-deploy (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, landing page at /, translation models at /models |
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.appdownloads the translation models (~660 MB, or the pairs inTRANSLATION_MODELS) in the background; translation works once that finishes.
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
https://$DOMAIN/ is a landing page linking to everything below.
- Open
https://$STT_DOMAIN/, enter thestt_api_keyservice token, then pick a microphone. WithRELAY_AUTOFILL(defaultsource) the page fills in the private relay, source room and publishing token itself and turns sharing on. WithRELAY_AUTOFILL=off, open Send captions → Use a private relay and enter relaywss://$DOMAIN/relay, caption websitehttps://$DOMAIN/, source roomsource+src_writetoken, editor output roomoutput, then enable sharing. - Open the generated editor link and enter
src_readandout_write(RELAY_AUTOFILL=allputs them in the link, at the cost of giving every service-token holder the audience room's publishing token). - 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
Nobody publishes Docker images, so images/build.sh builds all three from a pinned
captions-deploy commit
(its submodules pin the forks; web/Dockerfile is the UI image). 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 DEPLOY_COMMIT/VERSION in images/build.sh, rebuild, and update 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).