Spaces:
Sleeping
Sleeping
| title: CoreReader | |
| emoji: 📉 | |
| colorFrom: yellow | |
| colorTo: yellow | |
| sdk: docker | |
| pinned: false | |
| # CoreReader Backend (Modal) | |
| This folder is a backend-only deployment target for CoreReader / LN-TTS on **Modal**. | |
| It runs a FastAPI server that: | |
| - Scrapes NovelCool chapters + chapter index | |
| - Runs Kokoro ONNX TTS (**CPU-only**) | |
| - Streams **sentence-atomic** PCM16 mono audio over WebSocket | |
| - one binary WebSocket message per sentence (with a short trailing pause) | |
| - sentence audio includes a tiny fade-in/out to avoid boundary clicks | |
| ## Endpoints | |
| - GET /health | |
| - GET /voices | |
| - GET /novel_index?url=... | |
| - GET /novel_details?url=... (best-effort cover URL) | |
| - GET /novel_meta?url=... | |
| - GET /novel_chapter?url=...&n=... | |
| - WS /ws | |
| ## Use from the Flutter app | |
| After deploying, Modal prints a base URL like: | |
| - https://<user>--corereader-backend-fastapi-app.modal.run | |
| In Settings → WebSocket base URL, set: | |
| - wss://<user>--corereader-backend-fastapi-app.modal.run | |
| The app connects to `/ws` automatically. | |
| ## Notes | |
| - Models are downloaded on first cold start (if missing) and cached in a **Modal Volume**. | |
| Subsequent cold starts reuse the cached model files. | |
| - Offline downloads in the app use WS `play` with `realtime=false` so synthesis runs as fast as possible. | |
| ## Deploy | |
| Deploy the ASGI app: | |
| ```bash | |
| modal deploy modal_app.py | |
| ``` | |
| This deployment is configured for **3 CPU cores** and **4 GiB RAM**. | |
| ### One-time warmup (optional) | |
| To avoid paying download time on a user’s first session, run a one-time warmup after deploy: | |
| ```bash | |
| curl -sS https://<user>--corereader-backend-fastapi-app.modal.run/health | |
| ``` | |
| Protocol note: | |
| - WS `chapter_info` includes `sentence_total` (best-effort) so the client can render accurate download progress rings. | |
| - WS `sentence` events include `ms_start` (timeline), `char_start`/`char_end` (character offsets within the paragraph), and `chunk_bytes`/`chunk_samples` (size of the next sentence PCM chunk) so the client can highlight exactly what is being spoken. | |
| - `/novel_index` chapter numbers are parsed from title or URL (more robust for Chapter 1 / Prologue edge-cases). | |
| ## Speed architecture | |
| The `speed` field in the WS `play` command is the **TTS render speed** — it controls how fast Kokoro speaks. It is **baked into the synthesised PCM** at synthesis time. | |
| A separate **Playback Speed** (reader fast-forward, like YouTube 1.5×) is applied by the Flutter client via SoLoud's `setRelativePlaySpeed()`. The backend does not know about it. Highlight sync stays accurate at any playback speed because the client schedules sentence highlights at exact PCM sample positions and triggers them when `SoLoud.getStreamTimeConsumed()` reaches that sample — no additional correction needed. | |
| ## Deploy to Azure (Container Apps) | |
| This Docker image can be deployed to Azure Container Apps. | |
| 1) Create a resource group + registry: | |
| - az group create -n corereader-rg -l westeurope | |
| - az acr create -n <acrName> -g corereader-rg --sku Basic | |
| 2) Build + push to ACR: | |
| - az acr build -r <acrName> -t corereader-backend:v1 . | |
| 3) Deploy a public Container App (binds to PORT, default 7860): | |
| - az extension add --name containerapp --upgrade | |
| - az containerapp env create -g corereader-rg -n corereader-env -l westeurope | |
| - loginServer=$(az acr show -n <acrName> -g corereader-rg --query loginServer -o tsv) | |
| - az containerapp create -g corereader-rg -n corereader-backend --environment corereader-env \ | |
| --image "$loginServer/corereader-backend:v1" \ | |
| --ingress external --target-port 7860 --registry-server "$loginServer" | |
| 4) Get the URL: | |
| - fqdn=$(az containerapp show -g corereader-rg -n corereader-backend --query properties.configuration.ingress.fqdn -o tsv) | |
| Paste into the Flutter app Settings: | |
| - wss://$fqdn | |