Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 100 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
# AGENTS.md — tts-client-python

## Project Summary

Python gRPC client for Techmo TTS (Text-to-Speech) service. Provides a CLI tool (`tts_client`) and a Python library for speech synthesis, voice listing, custom recordings, and pronunciation lexicons. Communicates via gRPC using protobuf definitions from the `submodules/tts-service-api` submodule.

**Version:** `tts_client_python/VERSION.py`
**Python:** 3.8–3.14

## Repo Layout

```
tts_client_python/ # main package
tts_client.py # CLI entry point & arg parsing
general.py # gRPC client, audio player/saver, synthesis functions
recordings.py # custom recording CRUD
lexicons.py # pronunciation lexicon CRUD
wave_utils.py # WAV/A-Law/Mu-Law file writing; AudioFormat enum
VERSION.py # single version source
proto/ # generated — never in git
tests/ # pytest test suite
conftest.py # fixtures, sounddevice safety mock
test_cli.py # CLI arg validation
test_channel.py # gRPC channel creation
test_helpers.py # helper/validator functions
test_wave_utils.py # WAV encoding
test_integration.py # live service tests (excluded by default)
submodules/
tts-service-api/ # proto definitions (git submodule)
setup.py # custom build commands: build_grpc, build_py, develop, install
pyproject.toml # build backend; grpcio-tools constraint
setup.sh # one-time: submodules + pre-commit hooks
install.sh # venv creation + pip install
```

## Environment Setup

```bash
./setup.sh # init submodules + pre-commit (run once after clone)
./install.sh # create .venv, install package + test deps via uv
source .venv/bin/activate
sudo apt-get install libportaudio2 # required for sounddevice
```

**Never mix `setup.sh` and `install.sh` concerns.** `setup.sh` = git/hooks only. `install.sh` = Python env only.

## Generating Proto Stubs

Proto stubs are never committed. Regenerate with:
```bash
python setup.py build_grpc
```
This is also triggered automatically by `uv pip install -e ".[test]"`.

Generated files: `tts_client_python/proto/techmo_tts_pb2.py`, `techmo_tts_pb2_grpc.py`

## Running Tests

```bash
pytest # unit tests (default, excludes integration)
pytest tests/test_helpers.py # single file
pytest -m integration # requires TTS_SERVICE_ADDRESS=host:port env var
uvx --with "tox-uv>=1" tox # full Python 3.8–3.14 matrix
```

Unit tests do not mock gRPC calls — `test_helpers.py` imports proto stubs directly, so generated stubs must exist before running tests. Only `test_channel.py` uses mocking (for the gRPC channel). sounddevice is mocked at session start only if it causes a SIGSEGV. Integration tests require a live TTS service.

## Key Conventions

- **Proto files**: `*_pb2.py`, `*_pb2_grpc.py` — never commit, always generate.
- **Version**: only in `tts_client_python/VERSION.py`.
- **Dependency bounds**: Python 3.8 has tighter grpcio/protobuf constraints; do not widen without verifying.
- **Audio playback**: only PCM16 is supported by `AudioPlayer`; other encodings write files only.
- **Commit messages**: conventional format (`feat:`, `fix:`, `chore:`, `ci:`, `test:`, `refactor:`); no Claude attribution.

## Architecture Notes

- `GrpcRequestConfig` (general.py) holds the gRPC channel, stub, timeout, and metadata. All API calls receive one.
- `create_channel()` builds insecure or TLS channels depending on CLI flags.
- `synthesize()` is the high-level public function — creates the channel/stub/request, then dispatches to `internal_synthesize()` (unary gRPC call) or `internal_synthesize_streaming()` (streaming gRPC call) based on the `response` argument.
- `AudioSaver` handles all file output formats; `AudioPlayer` streams PCM16 to PortAudio.
- `recordings.py` and `lexicons.py` are thin gRPC wrappers using the same `GrpcRequestConfig` pattern.
- `lxml` is listed as a dependency in `setup.py` but is not currently imported in the codebase — it may be a leftover or intended for future SSML validation.

## CI/CD

GitHub Actions (`.github/workflows/test.yml`):
- Matrix: Python 3.8–3.13 (required pass), 3.14 RC (allowed failure)
- Steps per job: checkout with submodules → install libportaudio2 → install uv → generate protos → tox
- Integration tests activate automatically if `TTS_SERVICE_ADDRESS` secret is set in the repo.

No `.gitlab-ci.yml` exists in this repo — CI runs only on GitHub Actions.

## Dependency Constraints

| Package | Python 3.8 | Python 3.9+ |
|---------|-----------|-------------|
| grpcio | `>=1.70.0,<1.71.0` | `>=1.70.0,<2.0` |
| protobuf | `>=5.29.0,<6.0` | `>=5.29.0` |
| grpcio-tools (build) | `>=1.70.0,<1.71.0` | same |
83 changes: 83 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# CLAUDE.md — tts-client-python

## Project Overview

Python gRPC client for Techmo's Text-to-Speech service. Provides a CLI (`tts_client`) and importable library for streaming/non-streaming speech synthesis, voice management, custom recordings, and pronunciation lexicons.

- **Package:** `tts_client_python`
- **Version:** tracked in `tts_client_python/VERSION.py`
- **Python support:** 3.8–3.14
- **gRPC API proto:** `submodules/tts-service-api/proto/techmo_tts.proto`

## Dev Environment Setup

```bash
./setup.sh # one-time: init submodules + pre-commit hooks
./install.sh # creates .venv and installs package with test deps (uv required)
source .venv/bin/activate
```

`setup.sh` — submodules + pre-commit only. `install.sh` — venv + pip only. Never mix these concerns.

System dependency: `sudo apt-get install libportaudio2` (needed by sounddevice; missing it produces a warning but tests still run — conftest handles the absence gracefully).

## Proto Stubs

Generated files (`tts_client_python/proto/*_pb2.py`, `*_pb2_grpc.py`) are **never committed**.
Generate manually: `python setup.py build_grpc`
They are regenerated automatically during `uv pip install -e ".[test]"`.

## Key Source Files

| File | Purpose |
|------|---------|
| `tts_client_python/tts_client.py` | CLI entry point, argument parsing |
| `tts_client_python/general.py` | Core gRPC client, audio player/saver, synthesis logic |
| `tts_client_python/recordings.py` | Custom recording CRUD via gRPC |
| `tts_client_python/lexicons.py` | Pronunciation lexicon CRUD via gRPC |
| `tts_client_python/wave_utils.py` | WAV/A-Law/Mu-Law file writing |
| `tts_client_python/VERSION.py` | Single source of version truth |

## Testing

```bash
pytest # unit tests only (default)
pytest -m integration # requires TTS_SERVICE_ADDRESS=host:port
uvx --with "tox-uv>=1" tox # full matrix (Python 3.8–3.14)
```

Integration tests are excluded by default (`pytest.ini`). They require a running TTS service at `TTS_SERVICE_ADDRESS`.

`conftest.py` mocks sounddevice only if importing it causes a SIGSEGV (signal-kill crash, e.g. headless PortAudio init). Python-level import errors are intentionally left to fail naturally so dependency problems remain visible.

## Dependency Constraints

Python 3.8 requires tighter bounds:
- `grpcio>=1.70.0,<1.71.0` (3.8) vs `grpcio>=1.70.0,<2.0` (3.9+)
- `protobuf>=5.29.0,<6.0` (3.8) vs `protobuf>=5.29.0` (3.9+)
- `grpcio-tools>=1.70.0,<1.71.0` in `pyproject.toml` (build dep)

Do not widen these bounds without verifying Python 3.8 compatibility.

## CI — GitHub Actions

`.github/workflows/test.yml` runs a matrix across Python 3.8–3.13 (required) and 3.14 (allowed failure). Each job:
1. Checks out with submodules
2. Installs `libportaudio2`
3. Installs `uv`
4. Generates proto stubs: `python setup.py build_grpc`
5. Runs tox

## Versioning

Bump the version in `tts_client_python/VERSION.py` only. This is read by `setup.py` at build time via `exec()`. The package `__init__.py` is empty — there is no `tts_client_python.__version__` attribute.

## Audio Encoding

Supported encodings: `PCM16`, `OGG_VORBIS`, `OGG_OPUS`, `A_LAW`, `MU_LAW`. Audio playback (sounddevice) supports PCM16 only. Other formats are save-to-file only.

## Commit Rules

- Never commit `*_pb2.py`, `*_pb2_grpc.py`, or any protoc-generated file.
- Never include `Co-Authored-By: Claude` in commit messages.
- Conventional commits: `feat:`, `fix:`, `chore:`, `ci:`, `docs:`, `test:`, `refactor:`.
Loading