diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..97fdb7c --- /dev/null +++ b/AGENTS.md @@ -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 | diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..7bbb452 --- /dev/null +++ b/CLAUDE.md @@ -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:`.