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

110 lines
5.2 KiB
Markdown

# CAPTION.Ninja + Caption Local
Self-hosted live captioning from [captions.coop/captions-deploy](https://git.coopcloud.tech/captions.coop/captions-deploy):
the captions.coop forks of [CAPTION.Ninja](https://github.com/steveseguin/captionninja)
(capture/editor/overlay pages, private caption relay, local Bergamot translation)
and [Caption Local](https://github.com/steveseguin/caption-local) (Whisper speech-to-text).
Nothing is sent to caption.ninja, Google or any cloud STT/translation provider.
<!-- metadata -->
* **Category**: Apps
* **Status**: 1, alpha
* **Image**: [`git.autonomic.zone/recipe-maintainers/caption-ninja-*`](https://git.autonomic.zone/recipe-maintainers/-/packages), 1, built from captions-deploy (see [Images](#images))
* **Healthcheck**: Yes
* **Backups**: No (no user data; the model volume is a cache)
* **Email**: No
* **Tests**: 3
* **SSO**: No
<!-- endmetadata -->
## 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`][abra] and [`coop-cloud/traefik`][cc-traefik]
2. Make the images available on the server (see [Images](#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](https://git.coopcloud.tech/captions.coop/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).
[abra]: https://git.coopcloud.tech/coop-cloud/abra
[cc-traefik]: https://git.coopcloud.tech/coop-cloud/traefik