Some checks are pending
Test and SonarQube / quality (push) Waiting to run
The NFS blocker from TODO.md's deployment entry is resolved (export set up by the user on the DGX); picks up from there and completes the deployment end to end. - Mounted the DGX's TTS_Voices export read-write on the NUC at /mnt/dgx-tts-voices, persisted in /etc/fstab -- confirmed writable and confirmed the NUC instance sees the DGX's real voice library (344 voices), not a separate copy. - Deployed by rsyncing the working tree from the DGX (excluding .git, config/, logs/ and other local-only paths) and building the image directly on the NUC, rather than pulling ghcr.io/martin-b78/... -- that registry copy turned out to be both several versions stale and private with no credentials configured on the NUC, and the source was already right there to build from. - Found and fixed a real gap while wiring it up: vision_ocr_url and vision_ocr_fallback_url default to host.docker.internal, which resolves to whichever host the container is running on -- correct on the DGX itself, silently wrong on the NUC (would have pointed at the NUC's own nonexistent OCR engine). Pinned both explicitly to the DGX's LAN IP via environment overrides. - The container crash-looped on first start (sqlite3.OperationalError: unable to open database file) because docker compose had created config/ and logs/ root-owned under this session's root SSH access to the NUC, while the image's app user is uid 1000 -- chown -R 1000:1000 fixed it. - New docker-compose.nuc.yml, tracked here as the reviewable reference for that host (docker-compose.yml stays the DGX's own -- different build/network/socket-proxy shape). Deployed to the NUC as its own docker-compose.yml, since that host has no .git checkout to diff against, only the rsynced tree. Verified: container stable (no restart loop), HTTP 200, settings confirm all four engine URLs correctly resolve to the DGX, voice count matches the DGX exactly, restart policy is unless-stopped. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
114 lines
6.0 KiB
Markdown
114 lines
6.0 KiB
Markdown
# Project map
|
|
|
|
Living index of where things are and what state they are in.
|
|
Update in the same commit as the change it describes.
|
|
|
|
## Current phase
|
|
|
|
Magazine projects, **P0 and P1 complete** (frontend reachable at Studio → Magazines). Audiobook/Rehearser/voice-library
|
|
features are mature and in daily use.
|
|
|
|
## Deployment
|
|
|
|
Two self-hosted Docker instances, both port 7890:
|
|
|
|
- **DGX Spark** (`192.168.178.8`) — primary. `docker-compose.yml` builds locally and tags the result
|
|
`ghcr.io/martin-b78/tts-voice-creator-clone-and-design-2`, but that registry copy is not kept current on every
|
|
deploy (several versions behind as of 2026-09-14) — treat it as a snapshot, not a source of truth. Python and
|
|
static files are bind-mounted, so a code change needs only a container recreate; rebuild only when `Dockerfile`
|
|
or `requirements.txt` change.
|
|
- **`correspondence-nuc`** (`192.168.178.19`) — secondary, live since 2026-09-14. No TTS/OCR engines of its own;
|
|
every generation call crosses the LAN to the DGX (`docker-compose.nuc.yml`, tracked here, pins
|
|
`vision_ocr_url`/`vision_ocr_fallback_url` explicitly to the DGX's IP — their default resolves relative to
|
|
whichever host the container runs on, silently wrong here otherwise). Voice library is the DGX's own directory,
|
|
NFS-mounted read-write (`/etc/exports` on the DGX, `/etc/fstab` on the NUC) — never a separate copy that could
|
|
drift. Deployed by rsyncing the working tree (excluding `.git`, `config/`, `logs/`) and building on the NUC
|
|
itself, since the `ghcr.io` image is both stale and private with no registry login there. See TODO.md's
|
|
"P2 — deployment" entry for the full setup and the one accepted limitation (voice-change auto-restart of the
|
|
DGX's TTS containers doesn't reach across hosts — manual restart on the DGX, same as before this existed).
|
|
|
|
## Layout
|
|
|
|
| Path | Role |
|
|
|---|---|
|
|
| `server.py` | FastAPI app, router registration, auth middleware |
|
|
| `core/` | domain logic — config, database, audio, voice, routing, magazine |
|
|
| `routes/` | HTTP boundary, one module per feature area |
|
|
| `static/js/` | frontend, one module per feature area (no build step for most) |
|
|
| `tests/` | `test_*.py` (unittest) and `*.spec.js` (Playwright) |
|
|
| `architecture/` | SOPs for subsystems whose design is not obvious from code |
|
|
| `workflows/` | ComfyUI graphs for character portrait generation |
|
|
|
|
## Feature areas
|
|
|
|
| Area | Server | Frontend | State |
|
|
|---|---|---|---|
|
|
| Voice library / clone / design | `routes/library.py`, `core/voice.py` | `library.js` | mature |
|
|
| Read Aloud (books, PDFs) | `routes/reader.py` | `reader.js` | mature |
|
|
| Audiobook casting | `routes/conversation.py` | `audiobook*.js` | mature |
|
|
| Rehearser | `routes/rehearsals_db.py` | `rehearser-*.js` | mature |
|
|
| Studio (phase wizard) | — | `studio.js` | mature |
|
|
| TTS engines / routing | `routes/tts.py`, `core/routing.py` | `engines.js` | mature |
|
|
| STT | `routes/stt.py` | — | mature |
|
|
| **Magazines** | `core/magazine.py`, `routes/magazine.py` | `magazine.js`, `s-magazine.html` | **P0 + P1** |
|
|
|
|
## Data model
|
|
|
|
Documents (books and magazines alike) live under
|
|
`<CONFIG_DIR>/reader_library/<doc_id>/`, discriminated by `meta.json → kind`:
|
|
|
|
- `kind: "pdf"` / text — Read Aloud book. Text is extracted in the browser and
|
|
not persisted; `meta.json` holds counts and a flat `idx` resume position.
|
|
- `kind: "magazine"` — adds `layout.json`, `articles.json`, `pages/`, `images/`,
|
|
`podcast/`, and a per-article `progress` map. See
|
|
[architecture/magazine-ingestion.md](architecture/magazine-ingestion.md).
|
|
|
|
Settings, presets, routes and the SQLite database also live under `CONFIG_DIR`,
|
|
which is the writable volume.
|
|
|
|
## Environment
|
|
|
|
| Variable | Purpose |
|
|
|---|---|
|
|
| `CONFIG_DIR` | writable state (settings, library, database) |
|
|
| `VOICES_DIR` / `OUTPUT_DIR` | voice WAV library |
|
|
| `TTS_CONTAINER_NAMES` | containers restarted after a voice change |
|
|
| `DOCKER_HOST` | points at the socket proxy; the raw socket is not mounted |
|
|
| `llm_url` / `llm_model` / `llm_api_key` | settings.json, OpenAI-compatible LLM |
|
|
|
|
## Pending decisions
|
|
|
|
- Container registry: ghcr costs quota against a private package (≈427 MB per
|
|
push, 500 MB free on the Free plan). Local registry vs. ghcr vs. no re-pull is
|
|
**unresolved** — the compose files currently point at ghcr.
|
|
## Known limitations
|
|
|
|
- `gitea` backup remote is 157 commits behind `origin`; `main` tracks `gitea`.
|
|
- Magazine sidebar exclusion is a narrow-column geometry heuristic
|
|
(`mark_sidebars`), not certain — review always shows every sidebar block.
|
|
- Magazine word-level highlighting is a character-length estimate against
|
|
real measured audio duration, not true forced alignment.
|
|
- No formal cross-engine OCR accuracy benchmark exists (two engines wired:
|
|
ocr-baidu-unlimited-ocr primary, KDL Frontier Parser nano fallback).
|
|
- Figure boundaries are re-derived from the page render (`snap_visual_bboxes`)
|
|
because vision OCR grounds figures loosely; it corrects roughly 100 of the
|
|
147 figure regions on `c't KI Wissen 2026` and deliberately declines the
|
|
rest. A pale chart or line drawing on white is below its ink threshold and
|
|
keeps the engine's box — draw those by hand in review. The pass is
|
|
idempotent (safe to re-run via "Fix figure boundaries" any number of
|
|
times), including the one confirmed pair that cannot mathematically
|
|
converge — two adjacent figures sharing a dark caption strip settle on the
|
|
same answer every run rather than continuing to drift between two states.
|
|
- No `npm run qa` aggregate command yet; Python tests run via `unittest`,
|
|
browser tests via `playwright test`.
|
|
- The app has no authentication for the browser UI and no per-user data
|
|
isolation — single-tenant/LAN-trust only (one global settings/database/voice
|
|
library, no accounts). See `architecture/multi-tenant-and-identity.md` and
|
|
TODO.md's "Deferred — public multi-user readiness" section.
|
|
|
|
## Next logical step
|
|
|
|
Magazine P2 per TODO.md: image descriptions, table/chart handling, EPUB 3
|
|
Media Overlay export, and a formal OCR-engine benchmark (see
|
|
architecture/magazine-ingestion.md).
|