Files
caption-ninja/README.md
T
trav 4fcdb2e225 feat: build from captions.coop/captions-deploy, translation models, relay auto-fill
- 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.
2026-10-13 14:49:42 -07:00

5.2 KiB

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.

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

  1. Set up Docker Swarm, abra and coop-cloud/traefik
  2. Make the images available on the server (see Images)
  3. abra app new caption-ninja --secrets
  4. abra app config YOURAPPDOMAIN: set DOMAIN and STT_DOMAIN (both need DNS). The STT host needs RAM for the model: about 1.5 GB for small. Set CAPTION_THREADS to at most your core count.
  5. abra app deploy YOURAPPDOMAIN. On first start stt downloads the Whisper model into the models volume, so it can take several minutes to turn healthy. app downloads the translation models (~660 MB, or the pairs in TRANSLATION_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.

  1. Open https://$STT_DOMAIN/, enter the stt_api_key service token, then pick a microphone. With RELAY_AUTOFILL (default source) the page fills in the private relay, source room and publishing token itself and turns sharing on. With RELAY_AUTOFILL=off, open Send captions → Use a private relay and enter relay wss://$DOMAIN/relay, caption website https://$DOMAIN/, source room source + src_write token, editor output room output, then enable sharing.
  2. Open the generated editor link and enter src_read and out_write (RELAY_AUTOFILL=all puts them in the link, at the cost of giving every service-token holder the audience room's publishing token).
  3. 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; stt is 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://$DOMAIN and https://$STT_DOMAIN; add more exact origins with RELAY_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).