Metadata-Version: 2.4
Name: macos-say-server
Version: 0.1.0
Summary: Expose macOS say as an HTTP server that provides text-to-speech audio generation.
License-File: LICENSE
Requires-Python: >=3.9
Requires-Dist: fastapi[standard]>=0.116.0
Requires-Dist: uvicorn>=0.35.0
Description-Content-Type: text/markdown

# macos-say-server

Expose macOS say as an HTTP server that provides text-to-speech audio generation.

## Features

- `GET /health` for liveness checks.
- `GET /voices` to list the voices available from `say -v '?'`.
- `GET /cache/stats` to inspect current cache usage and limits.
- `GET /speak?text=...&voice=...` to return AIFF audio directly.
- `POST /speak` with JSON `{ "text": "...", "voice": "..." }` to return AIFF audio.
- Disk-backed AIFF cache keyed by `text + voice`, with lazy TTL cleanup.

## Run

```bash
uv sync
uv run macos-say-server
```

The server binds to `127.0.0.1:2576` by default.

Use startup arguments to override bind settings:

```bash
uv run macos-say-server --host 127.0.0.1 --port 2576
```

You can also run the package module directly:

```bash
uv run python -m macos_say_server --host 127.0.0.1 --port 2576
```

## Build

```bash
uv build
```

## Cache

The service stores generated audio under `.cache/audio` by default.

- Cache key: hash of `text + voice`
- Expiration: `CACHE_TTL_SECONDS`, default `86400` seconds
- Cleanup: lazy cleanup during requests, at most once per `CACHE_CLEANUP_INTERVAL_SECONDS`, default `300` seconds
- On cache hit, the file timestamp is refreshed, so TTL is sliding rather than fixed from first generation
- Capacity control: optional LRU-style pruning using file modification time
- File count limit: `CACHE_MAX_FILES`, default `0` meaning unlimited
- Total size limit: `CACHE_MAX_BYTES`, default `0` meaning unlimited

When a cache limit is configured, the oldest entries are removed first. Because cache hits refresh the file timestamp, frequently used items stay warm and cold items are evicted first.

`GET /cache/stats` returns:

- `cache_dir` and whether it currently exists
- `total_files` and `total_bytes`
- `expired_files` under the current TTL view
- configured `ttl_seconds`, cleanup interval, and capacity limits
- oldest and newest cache entry timestamps

Startup arguments:

```bash
uv run macos-say-server \
	--cache-dir .cache/audio \
	--cache-ttl-seconds 86400 \
	--cache-cleanup-interval-seconds 300 \
	--cache-max-files 0 \
	--cache-max-bytes 0
```

## Examples

```bash
curl http://127.0.0.1:2576/health
curl http://127.0.0.1:2576/voices
curl http://127.0.0.1:2576/cache/stats
curl -G http://127.0.0.1:2576/speak --data-urlencode "text=hello from macos say server" --output speech.aiff
curl -i -G http://127.0.0.1:2576/speak --data-urlencode "text=hello from macos say server" --output speech.aiff
curl -X POST http://127.0.0.1:2576/speak \
	-H "content-type: application/json" \
	-d '{"text":"hello from json","voice":"Samantha"}' \
	--output speech.aiff
```

If you inspect response headers, `X-Cache: MISS` means the file was newly synthesized, and `X-Cache: HIT` means it was served from cache.
