Skip to content

Repository files navigation

cans

Veronica

Put the cans on.

Type a line. She speaks it.

The cans booth

watch with sound

Install

macOS, Apple Silicon. No Python, no uv.

brew install --HEAD veronica-agent/tap/cans

The mouth is a native Qwen3-TTS worker. First cans doctor (or first cans) downloads it once, about 1.6 GB, into ~/.cans/native.

cans doctor
cans
curl
curl -fsSL https://raw.githubusercontent.com/veronica-agent/cans/main/install.sh | sh

Until a tagged release exists, that script needs Go and installs cans onto PATH. Then cans doctor.

From source
git clone https://github.com/veronica-agent/cans.git
cd cans
just install

just install (same as just run install) is go install into $(go env GOBIN), or $(go env GOPATH)/bin if GOBIN is empty. cans doctor fetches the mouth. just uninstall removes the binary. just uninstall --all also wipes ~/.cans (the native mouth).

Commands

cans
cans say "Put the cans on."
cans keep take.wav -text "the words in the wav"
cans doctor

Keep needs the clip and the words spoken in it. Throat stays put for the session.

The CLI is Go. The mouth is a native Qwen3-TTS worker that clones a wav. No Python.

Scripting

The script owns the document. cans speaks what it is handed.

A script piping lines into cans

--stream reads stdin a line at a time and speaks each line through one worker: one model load for the whole document. Blank lines are skipped and do not take an index.

# one wav per paragraph, script decides what a paragraph is
awk -v RS='' '{gsub(/\n/," "); print}' chapter.md | cans say --stream -o 'out/%03d.wav'

# a line at a time, with the script's own naming
i=0
while IFS= read -r line; do
  i=$((i+1))
  cans say "$line" -o "$(printf 'out/%03d.wav' "$i")"
done < lines.txt

# metadata for a build step
cans say --stream --json -o 'out/%03d.wav' < lines.txt | jq -r 'select(.error==null) | .wav' > manifest.txt
Flag Effect
-o, --out Write the wav here. Under --stream the path takes one %d, as in out/%03d.wav.
--stream Read stdin line by line, one utterance per line, one worker for all of them. Text on argv is a usage error.
--json One JSON record per utterance on stdout; --stream adds "line". wav is present only with -o.
--play Play the wav as well as writing it. Needs -o.
--nowait Do not queue behind another cans: give up at once.
--wait <dur> Queue for at most that long. Without either flag, cans waits.
- Read one utterance from stdin. Text on argv is a usage error.
Exit Meaning
0 Spoke it.
1 Runtime failure, or a line in a stream failed.
2 Usage error.
75 Another cans holds the mouth and the wait was refused or ran out.
130 Interrupted.

stdout carries data — a JSON record, a wav path, or ttfa_ms=N, the worker's total synthesis time for that line. Prose goes to stderr.

Ctrl-C stops the stream: the line being spoken is dropped, finished wavs stay, exit 130. A second Ctrl-C stops at once.

Without -o the wav is a temp file removed after playback. --json without -o omits wav so a script is not handed a path that will not survive. Pass -o if the file is needed.


Built with Festival

About

Put the cans on.

Topics

Resources

Stars

4 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages