# Aletheia CMP audiobook receiver

The receiver accepts EPUB/FB2 files, queues one synthesis job at a time, reports progress, and returns an AAC/M4A file. The HTTP process starts with no Qwen model in memory. `Qwen3TTSModel` and `torch` are imported only when a job reaches the synthesis stage; the model is deleted and the CUDA cache is cleared before M4A encoding.

## Deployed layout

- CMP host: `192.168.0.112`, service root `/home/sevenhill/apps/aletheia-audiobook`
- API: `http://0.0.0.0:8765`
- jobs and SQLite state: `/home/sevenhill/apps/aletheia-audiobook/audiobook-jobs`
- API token: `/home/sevenhill/apps/aletheia-audiobook/service/service-token.txt`
- public route: `https://argus.kusoft.xyz/aletheia-tts/`
- voice: Qwen3-TTS 0.6B CustomVoice, `Ryan`, Russian

The token is intentionally not stored in this repository. The Android build reads it from `%USERPROFILE%\.aletheia\audiobook-api-token.txt` and embeds it in `BuildConfig` for this private installation.

## Runtime

The CMP receiver uses `/home/sevenhill/apps/qwen3-tts-venv`. It starts as a lightweight FastAPI process and loads the 0.6B Qwen model only after a queued audiobook reaches the synthesis stage. The model is deleted and CUDA cache cleared before M4A encoding.

The `systemd --user` service starts only the receiver process. It does not preload Qwen or reserve GPU memory; the model is loaded by an audiobook request. After a job finishes, systemd replaces the receiver process so Qwen and Triton CUDA contexts are fully released before the receiver waits for the next request.

When an audiobook enters synthesis, the receiver temporarily stops and runtime-masks Ollama, then loads one tested 0.6B Qwen worker on each of GPU 0, 1, and 2. Up to three independent chunks are generated in parallel. After the job, the receiver exits so systemd can release all CUDA contexts; its fresh idle process restores Ollama. Existing WAV chunks are never regenerated during resume.

Per-batch timings are appended to `audiobook-jobs\performance.jsonl`. The log separates the autoregressive talker, speech-tokenizer decoder, and wrapper time so runtime optimizations can be benchmarked without changing the generated audio path.

The CMP firewall must permit TCP 8765 only from the Raspberry Pi address. Caddy uses a path handler that removes the public prefix before proxying:

```caddyfile
argus.kusoft.xyz {
    encode zstd gzip
    handle_path /aletheia-tts/* {
        reverse_proxy 192.168.0.112:8765
    }
    handle {
        reverse_proxy 127.0.0.1:5105
    }
}
```

Validate with `caddy validate` before reloading Caddy. `/health` is public and reports `modelLoaded`; all `/v1/audiobooks` endpoints require the bearer token.

## API

- `POST /v1/audiobooks` — multipart fields `book`, `title`, `author`
- `GET /v1/audiobooks/{id}` — status, stage, processed/total characters, chapter, duration and chapter markers
- `GET /v1/audiobooks/{id}/file` — completed M4A
- `DELETE /v1/audiobooks/{id}` — cancel an active job or remove a completed job

Interrupted receiver processes requeue unfinished jobs on the next start. Existing WAV chunks are reused, so synthesis resumes at the first missing chunk.
