Skip to content

Repository files navigation

Utter

Utter is a local text-to-speech command-line tool for Apple Silicon Macs. It uses Qwen3-TTS CustomVoice through MLX-Audio and writes mono PCM-16 WAV files without sending synthesis text to a cloud API.

The project is under active development. The current CLI supports explicit model installation, model status inspection, voice listing, and local speech synthesis:

uv sync
uv run utter models pull
uv run utter voices
uv run utter speak "This is a flower." --voice Ryan -o flower.wav
afplay flower.wav

Install As A User Tool

Install the CLI once to make utter available from any project:

uv tool install --python 3.12 \
  "utter-mlx @ git+https://github.com/maskedsyntax/utter.git@master"
uv tool update-shell
utter models pull

For reproducible installs, replace master with a release tag or commit SHA. The distribution is named utter-mlx because utter belongs to an unrelated project on PyPI; the executable and Python package remain utter.

See AGENT_INTEGRATION.md for a copy-ready agent policy, exit-code contract, and Python/Node subprocess examples. Calling the global CLI with --json is the recommended integration for OpenCode, Claude Code, Cursor, VS Code, and other agents that can execute shell commands.

Requirements

  • macOS on Apple Silicon
  • Python 3.12
  • uv
  • Approximately 3.1 GB for the pinned model snapshot
  • Approximately 4.3-7.8 GB of measured MLX allocation during synthesis, depending on generated length

Intel Macs, Linux, Windows, CUDA, cloud synthesis, voice cloning, and streaming are not supported.

Development Setup

uv sync --all-groups
uv run utter --help
uv run pytest

The dependency graph is pinned in uv.lock.

The default test run does not load or download the model. Run the installed real-model integration tests explicitly:

UTTER_RUN_MODEL_TESTS=1 uv run pytest -m model

These tests exercise both the Python adapter and the packaged CLI, block socket network calls during synthesis, and validate the generated WAV. To validate a completely fresh multi-gigabyte download in isolated caches:

UTTER_RUN_DOWNLOAD_TEST=1 uv run pytest -m download

The fresh-download test can take a long time and consume approximately 3.1 GB, so it is never enabled by the normal suite.

Generate the fixed multi-voice listening corpus with:

uv run python scripts/quality_review.py --force

See QUALITY_REVIEW.md for the playback order, listening criteria, and review worksheet. Generated audio is stored under ignored artifacts/quality-review/.

Run the target-machine performance benchmark with:

HF_HUB_OFFLINE=1 TRANSFORMERS_OFFLINE=1 \
  uv run python scripts/benchmark_m4.py

Measured M4 results are recorded in BENCHMARK_M4.md.

Install The Model

uv run utter models pull

This downloads and validates the exact pinned model revision in an Utter-owned cache:

~/Library/Caches/utter/models/Qwen3-TTS-12Hz-1.7B-CustomVoice-8bit/

The command is resumable and idempotent. Interrupted downloads remain in a partial directory for a later pull to resume, but are never marked ready or used for synthesis. Normal utter speak execution loads only the validated local path with Hugging Face and Transformers offline modes enabled; it does not implicitly download a missing model.

Inspect installation state in human-readable or JSON form:

uv run utter models status
uv run utter models status --json

Status reports the pinned repository and revision, quantization, cache path, installed/complete/ready states, disk usage, runtime versions, and any detected issues. A missing model is a successful status query with ready: false.

The lower-level Phase 0 diagnostic remains available for runtime development:

uv run python scripts/runtime_spike.py --force

List Voices

uv run utter voices
uv run utter voices --json

Voice IDs are exact and case-sensitive. The current presets are Vivian, Serena, Uncle_Fu, Dylan, Eric, Ryan, Aiden, Ono_Anna, and Sohee.

Generate Speech

uv run utter speak "This is a flower." \
  --voice Ryan \
  --language en \
  -o flower.wav

Style instructions are optional:

uv run utter speak "Welcome back." \
  --voice Aiden \
  --style "Speak calmly and warmly." \
  -o welcome.wav

Text can also be read as strict UTF-8 from stdin:

printf '%s' "This is a flower." | \
  uv run utter speak --stdin --voice Ryan -o flower.wav

Positional text and --stdin cannot be used together, and empty stdin is an error.

Use --force to atomically replace an existing destination. Without it, Utter refuses to overwrite files.

Pressing Ctrl+C during model loading or synthesis exits with status 130. Utter does not publish a partial destination, and any temporary WAV created before an interruption is removed.

Supported language codes are en, zh, ja, ko, de, fr, ru, pt, es, and it. The default is en.

JSON Output

uv run utter speak "This is a flower." \
  --voice Ryan \
  -o flower.wav \
  --json

In JSON mode stdout contains only the final JSON result. Model loading, progress, warnings, and errors are written to stderr.

Current Limitations

  • Short-to-medium text is the current target. Utter does not yet segment long documents.
  • The model is loaded for each CLI invocation; there is no warm daemon.
  • WAV is the only output format.
  • Audio generation is stochastic and may vary between runs.

License

Utter is licensed under the MIT License. The pinned Qwen model is identified as Apache-2.0, and MLX-Audio and MLX are MIT-licensed. Exact pinned artifact and runtime notices are recorded in THIRD_PARTY_NOTICES.md, which is included in built wheels and source distributions.

About

Local Text-to-Speech CLI for Apple Silicon

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages