Replaces local filesystem document storage with S3-compatible object storage. Adds embedded SeaweedFS 'weed mini' for local development and Docker deployments. Updates document upload flow to use presigned URLs with a server fallback proxy. Refactors auth configuration to use BASE_URL and AUTH_SECRET. BREAKING CHANGE: Renamed BETTER_AUTH_URL to BASE_URL and BETTER_AUTH_SECRET to AUTH_SECRET. Removed legacy document upload/content endpoints. Requires S3 environment variables (auto-configured for embedded SeaweedFS). |
||
|---|---|---|
| .github | ||
| drizzle | ||
| public | ||
| scripts | ||
| src | ||
| tests | ||
| .env.example | ||
| .gitattributes | ||
| .gitignore | ||
| Dockerfile | ||
| drizzle.config.pg.ts | ||
| drizzle.config.sqlite.ts | ||
| empty-module.ts | ||
| eslint.config.mjs | ||
| LICENSE | ||
| next.config.ts | ||
| NOTICE.txt | ||
| package.json | ||
| playwright.config.ts | ||
| pnpm-lock.yaml | ||
| postcss.config.mjs | ||
| README.md | ||
| tailwind.config.ts | ||
| tsconfig.json | ||
📄🔊 OpenReader WebUI
OpenReader WebUI is an open source text to speech document reader web app built using Next.js, offering a TTS read along experience with narration for EPUB, PDF, TXT, MD, and DOCX documents. It supports multiple TTS providers including OpenAI, Deepinfra, and custom OpenAI-compatible endpoints like Kokoro-FastAPI and Orpheus-FastAPI
- 🎯 Multi-Provider TTS Support
- Kokoro-FastAPI: Supporting multi-voice combinations (like
af_heart+af_bella) - Orpheus-FastAPI
- Custom OpenAI-compatible: Any TTS API with
/v1/audio/voicesand/v1/audio/speechendpoints - Cloud TTS Providers (requiring API keys)
- Deepinfra: Kokoro-82M + models with support for cloned voices and more
- OpenAI API (
): tts-1, tts-1-hd, and gpt-4o-mini-tts w/ instructions
- Kokoro-FastAPI: Supporting multi-voice combinations (like
- 🛜 (Updated) Server-side Sync and Storage
- (New) External Library Import enables importing documents to the browser's storage from a folder mounted on the server
- (Updated) Sync documents between the browser and server to get them on other browsers or devices
- 🎧 Server-side Audiobook Export in m4b/mp3, with resumable, chapter-based export and regeneration
- 📖 Read Along Experience providing real-time text highlighting during playback (PDF/EPUB)
- (New) Word-by-word highlighting uses word-by-word timestamps generated server-side with whisper.cpp (optional)
- 🧠 Smart Sentence-Aware Narration merges sentences across pages/chapters for smoother TTS
- 🚀 Optimized Next.js TTS Proxy with audio caching and optimized repeat playback
- 🎨 Customizable Experience
- 🎨 Multiple app theme options
- ⚙️ Various TTS and document handling settings
- And more ...
🐳 Docker Quick Start
Prerequisites
- Recent version of Docker installed on your machine
- A TTS API server (Kokoro-FastAPI, Orpheus-FastAPI, Deepinfra, OpenAI, etc.) running and accessible
Note: If you have good hardware, you can run Kokoro-FastAPI with Docker locally (see below).
1. 🐳 Start the Docker container
Minimal (auth disabled, embedded storage is ephemeral, no library import):
docker run --name openreader-webui \
--restart unless-stopped \
-p 3003:3003 \
-p 8333:8333 \
ghcr.io/richardr1126/openreader-webui:latest
Fully featured (persistent storage, embedded SeaweedFS weed mini for documents, optional auth):
docker run --name openreader-webui \
--restart unless-stopped \
-p 3003:3003 \
-p 8333:8333 \
-v openreader_docstore:/app/docstore \
-v /path/to/your/library:/app/docstore/library:ro \
-e API_BASE=http://host.docker.internal:8880/v1 \
-e API_KEY=none \
-e BASE_URL=http://localhost:3003 \
-e AUTH_SECRET=<paste_the_output_of_openssl_here> \
ghcr.io/richardr1126/openreader-webui:latest
You can remove the /app/docstore/library mount if you don't need server library import.
You can remove either BASE_URL or AUTH_SECRET to keep auth disabled.
Quick notes:
API_BASEshould point to your TTS API server's base URL (for local Docker Kokoro, usehttp://host.docker.internal:8880/v1).- Expose
-p 8333:8333for direct browser access to embedded SeaweedFS presigned URLs. - If port
8333is not exposed, uploads still work via/api/documents/blob/upload/fallback. - To enable auth, set both
BASE_URLandAUTH_SECRET.
Docker networking and blob behavior (Click to expand)
Ports
3003serves the OpenReader app and API routes.8333serves embedded SeaweedFS S3 for browser direct blob access.
Upload behavior
- Primary upload path: browser uploads to presigned URL from
/api/documents/blob/upload/presign. - Fallback upload path:
/api/documents/blob/upload/fallbackif direct upload fails. - Content serving path:
/api/documents/blob(server-served bytes/snippets).
Which Docker setup should I use? (Click to expand)
| Goal | Recommended setup |
|---|---|
| Fast local setup, no persistence | Minimal docker run example |
| Persistent docs/audiobooks and optional auth | Fully featured docker run example with /app/docstore volume |
| Browser direct blob uploads/downloads | Publish both 3003 and 8333 |
Private blob endpoint (no 8333 published) |
Keep using app APIs; uploads use fallback proxy when direct presigned upload is unreachable |
Common Docker environment variables (Click to expand)
| Variable | Purpose | Example / Notes |
|---|---|---|
API_BASE |
Default TTS API base URL (server-side) | http://host.docker.internal:8880/v1 |
API_KEY |
Default TTS API key | none or your provider key |
BASE_URL |
Enables auth when set with AUTH_SECRET |
External URL for this app, e.g. http://localhost:3003 or https://reader.example.com |
AUTH_SECRET |
Enables auth when set with BASE_URL |
Generate with openssl rand -base64 32 |
AUTH_TRUSTED_ORIGINS |
Extra allowed auth request origins | Comma-separated list; leave empty to trust only BASE_URL |
POSTGRES_URL |
Use Postgres for server DB (metadata + auth tables) instead of SQLite | If set, startup migrations target Postgres |
GITHUB_CLIENT_ID |
Optional GitHub OAuth sign-in | Requires GITHUB_CLIENT_SECRET |
GITHUB_CLIENT_SECRET |
Optional GitHub OAuth sign-in | Requires GITHUB_CLIENT_ID |
Blob and embedded storage environment variables (Click to expand)
| Variable | Purpose | Example / Notes |
|---|---|---|
USE_EMBEDDED_WEED_MINI |
Start SeaweedFS before app startup | default: true when unset in shared entrypoint |
WEED_MINI_DIR |
Data directory for embedded weed mini |
default: docstore/seaweedfs |
WEED_MINI_WAIT_SEC |
Startup wait timeout for embedded weed mini |
default: 20 |
S3_BUCKET |
S3 bucket used for document blobs | default: openreader-documents in embedded mode |
S3_REGION |
S3 region used by AWS SDK | default: us-east-1 in embedded mode |
S3_ENDPOINT |
Custom endpoint for S3-compatible providers | default: http://<BASE_URL host>:8333 when BASE_URL is set, otherwise detected LAN host |
S3_ACCESS_KEY_ID |
S3 access key | auto-generated in embedded mode if unset |
S3_SECRET_ACCESS_KEY |
S3 secret key | auto-generated in embedded mode if unset |
S3_FORCE_PATH_STYLE |
Force path-style addressing | default: true in embedded mode |
S3_PREFIX |
Prefix for stored object keys | default: openreader |
Docker volume mounts (Click to expand)
| Mount | Type | Recommended | Purpose | Example |
|---|---|---|---|---|
/app/docstore |
Docker named volume | Yes (if you want persistence) | Persists embedded SeaweedFS blob data (docstore/seaweedfs), SQLite metadata DB (docstore/sqlite3.db when POSTGRES_URL is unset), audiobook export artifacts, and migration/runtime files |
-v openreader_docstore:/app/docstore |
/app/docstore/library |
Bind mount (host folder) | Optional + :ro |
Read-only source directory for Server Library Import; files are imported/copied into browser storage, not modified in place | -v /path/to/your/library:/app/docstore/library:ro |
To import from the mounted library: Settings → Documents → Server Library Import
Note: Every file in the mounted library is imported into the client browser's storage. Keep the library reasonably sized to avoid performance issues.
Visit http://localhost:3003 to run the app and set your settings.
2. ⚙️ Configure the app settings in the UI
- Set the TTS Provider and Model in the Settings modal
- Set the TTS API Base URL and API Key if needed (more secure to set in env vars)
- Select your model's voice from the dropdown (voices try to be fetched from TTS Provider API)
3. ⬆️ Updating Docker Image
docker stop openreader-webui || true && \
docker rm openreader-webui || true && \
docker image rm ghcr.io/richardr1126/openreader-webui:latest || true && \
docker pull ghcr.io/richardr1126/openreader-webui:latest
🗣️ Local Kokoro-FastAPI Quick-start (CPU or GPU)
You can run the Kokoro TTS API server directly with Docker. We are not responsible for issues with Kokoro-FastAPI. For best performance, use an NVIDIA GPU (for GPU version) or Apple Silicon (for CPU version).
Kokoro-FastAPI (CPU)
docker run -d \
--name kokoro-tts \
--restart unless-stopped \
-p 8880:8880 \
-e ONNX_NUM_THREADS=8 \
-e ONNX_INTER_OP_THREADS=4 \
-e ONNX_EXECUTION_MODE=parallel \
-e ONNX_OPTIMIZATION_LEVEL=all \
-e ONNX_MEMORY_PATTERN=true \
-e ONNX_ARENA_EXTEND_STRATEGY=kNextPowerOfTwo \
-e API_LOG_LEVEL=DEBUG \
ghcr.io/remsky/kokoro-fastapi-cpu:v0.2.4
Adjust environment variables as needed for your hardware and use case.
Kokoro-FastAPI (GPU)
docker run -d \
--name kokoro-tts \
--gpus all \
--user 1001:1001 \
--restart unless-stopped \
-p 8880:8880 \
-e USE_GPU=true \
-e PYTHONUNBUFFERED=1 \
-e API_LOG_LEVEL=DEBUG \
ghcr.io/remsky/kokoro-fastapi-gpu:v0.2.4
Adjust environment variables as needed for your hardware and use case.
⚠️ Important Notes:
- For best results, set the
-e API_BASE=for OpenReader's Docker tohttp://kokoro-tts:8880/v1- For issues or support, see the Kokoro-FastAPI repository.
- The GPU version requires NVIDIA Docker support and works best with NVIDIA GPUs. The CPU version works best on Apple Silicon or modern x86 CPUs.
Local Development Installation
Prerequisites
-
Node.js (recommended: use nvm)
-
pnpm (recommended) or npm
npm install -g pnpm -
A TTS API server (Kokoro-FastAPI, Orpheus-FastAPI, Deepinfra, OpenAI, etc.) running and accessible
-
SeaweedFS
weedbinary (required)brew install seaweedfsNote: Verify install with
weed version.
Optionally required for different features:
-
FFmpeg (required for audiobook m4b creation only)
brew install ffmpeg -
libreoffice (required for DOCX files)
brew install libreoffice -
whisper.cpp (optional, required for word-by-word highlighting)
# clone and build whisper.cpp (no model download needed – OpenReader handles that) git clone https://github.com/ggml-org/whisper.cpp.git cd whisper.cpp cmake -B build cmake --build build -j --config Release # point OpenReader to the compiled whisper-cli binary echo WHISPER_CPP_BIN=\"$(pwd)/build/bin/whisper-cli\"Note: The
WHISPER_CPP_BINpath should be set in your.envfile for OpenReader to use word-by-word highlighting features.
Steps
-
Clone the repository:
git clone https://github.com/richardr1126/OpenReader-WebUI.git cd OpenReader-WebUI -
Install dependencies:
With pnpm (recommended):
pnpm i # or npm i -
Configure the environment:
cp .env.example .env # Edit .env with your configuration settingsAuth is recommended for contributors and is enabled when both values are set:
-
Set
BASE_URLto your local URL (default:http://localhost:3003) -
Generate a
AUTH_SECRETand paste it into.env:openssl rand -base64 32 -
(Optional) If you use both localhost and LAN URL, set
AUTH_TRUSTED_ORIGINS(leave empty to trust onlyBASE_URL):AUTH_TRUSTED_ORIGINS=http://localhost:3003,http://192.168.0.116:3003
The embedded weed mini object store is enabled by default for local development, and started through the shared entrypoint. The ACCESS_KEY_ID and SECRET_ACCESS_KEY are auto-generated if unset, but for stable credentials across restarts, you can generate and set them in
.env:-
(Optional) Generate
S3_ACCESS_KEY_IDandS3_SECRET_ACCESS_KEYand paste them into.envfor stable credentials:S3_ACCESS_KEY_ID=<`openssl rand -hex 16`> S3_SECRET_ACCESS_KEY=<`openssl rand -hex 32`> -
(Optional) Connect to external S3-compatible storage instead of embedded
weed mini:USE_EMBEDDED_WEED_MINI=false # Required S3_BUCKET=your-bucket S3_REGION=us-east-1 S3_ACCESS_KEY_ID=your-access-key S3_SECRET_ACCESS_KEY=your-secret-key # Optional / provider-specific S3_ENDPOINT= S3_FORCE_PATH_STYLE= S3_PREFIX=openreaderNotes:
- For AWS S3: usually leave
S3_ENDPOINTempty andS3_FORCE_PATH_STYLEempty/false. - For MinIO/SeaweedFS/R2/B2 S3: set
S3_ENDPOINTto your endpoint URL and setS3_FORCE_PATH_STYLE=truewhen required by that provider.
- For AWS S3: usually leave
Notes:
-
The base URL for the TTS API should be accessible and relative to the Next.js server
-
To disable auth, remove either
BASE_URLorAUTH_SECRET. -
If S3 credentials are unset, they are auto-generated per startup.
-
-
Run DB migrations:
-
Production / Docker: Migrations run automatically on startup via
pnpm start. -
Development: When using
pnpm dev, it needs to be run explicitly:pnpm migrate
Note: If you set
POSTGRES_URLin.env, migrations will target Postgres instead of local SQLite.Generating migrations (contributors):
pnpm generategenerates migrations for both SQLite and Postgres (requiresPOSTGRES_URL).
Manual Drizzle Kit runs:
- SQLite migrate:
npx drizzle-kit migrate --config drizzle.config.sqlite.ts - Postgres migrate:
npx drizzle-kit migrate --config drizzle.config.pg.ts - SQLite generate:
npx drizzle-kit generate --config drizzle.config.sqlite.ts - Postgres generate:
npx drizzle-kit generate --config drizzle.config.pg.ts
-
-
Start the development server:
With pnpm (recommended):
pnpm dev # or npm run devor build and run the production server:
With pnpm:
pnpm build # or npm run build pnpm start # or npm startVisit http://localhost:3003 to run the app.
💡 Feature requests
For feature requests or ideas you have for the project, please use the Discussions tab.
🙋♂️ Support and issues
If you encounter issues, please open an issue on GitHub following the template (which is very light).
👥 Contributing
Contributions are welcome! Fork the repository and submit a pull request with your changes.
❤️ Acknowledgements
This project would not be possible without standing on the shoulders of these giants:
- Kokoro-82M model
- Kokoro-FastAPI
- Better Auth
- SQLite
- PostgreSQL
- SeaweedFS (
weed mini) - whisper.cpp
- ffmpeg
- react-pdf npm package
- react-reader npm package
Docker Supported Architectures
- linux/amd64 (x86_64)
- linux/arm64 (Apple Silicon, Raspberry Pi, SBCs, etc.)
Stack
- Framework: Next.js 15 (App Router), React 19, TypeScript
- Containerization / Runtime: Docker (linux/amd64 + linux/arm64), with a shared entrypoint that can bootstrap embedded SeaweedFS before app startup
- Next.js Client:
- UI: Tailwind CSS, Headless UI, @tailwindcss/typography
- Interactions:
react-dnd,react-dropzone - Authentication: Better Auth client SDK (
better-auth/react, anonymous client plugin) - Local storage/cache: Dexie.js (IndexedDB) for documents, cache, and app settings
- Document rendering:
- PDF: react-pdf, pdf.js
- EPUB: react-reader, epubjs
- Markdown/Text: react-markdown, remark-gfm
- Text preprocessing / matching: compromise, cmpstr
- Next.js Server:
- APIs: Next.js Route Handlers for document sync, blob/content access, migration flows, audiobook export, and TTS/Whisper proxying
- Authentication: Better Auth server handlers/adapters for session and auth routing
- Text preprocessing / NLP utilities: compromise via shared
lib/nlphelpers used in server processing paths - Metadata database: Drizzle ORM, SQLite (
better-sqlite3) by default, optional PostgreSQL (pg) - Blob/object storage: embedded SeaweedFS (
weed mini) by default, or external S3-compatible storage via AWS SDK v3 (@aws-sdk/client-s3, presigned URLs, upload fallback proxy) - Audio/processing pipeline: OpenAI-compatible TTS providers (OpenAI, DeepInfra, Kokoro, Orpheus, custom), ffmpeg for audiobook assembly, optional whisper.cpp for word-level timestamps
- Tooling / Testing: ESLint, TypeScript, Playwright end-to-end tests, and Drizzle migrations/generation scripts
License
This project is licensed under the MIT License.