openreader/README.md
Richard R 81d249ed52 feat(storage): implement S3/SeaweedFS blob storage for documents
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).
2026-02-10 12:29:15 -07:00

20 KiB
Raw Blame History

GitHub Stars GitHub Forks GitHub Watchers GitHub Issues GitHub Last Commit GitHub Release

Discussions

📄🔊 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/voices and /v1/audio/speech endpoints
    • 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
  • 🛜 (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_BASE should point to your TTS API server's base URL (for local Docker Kokoro, use http://host.docker.internal:8880/v1).
  • Expose -p 8333:8333 for direct browser access to embedded SeaweedFS presigned URLs.
  • If port 8333 is not exposed, uploads still work via /api/documents/blob/upload/fallback.
  • To enable auth, set both BASE_URL and AUTH_SECRET.
Docker networking and blob behavior (Click to expand)

Ports

  • 3003 serves the OpenReader app and API routes.
  • 8333 serves 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/fallback if 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 to http://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 weed binary (required)

    brew install seaweedfs
    

    Note: 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_BIN path should be set in your .env file for OpenReader to use word-by-word highlighting features.

Steps

  1. Clone the repository:

    git clone https://github.com/richardr1126/OpenReader-WebUI.git
    cd OpenReader-WebUI
    
  2. Install dependencies:

    With pnpm (recommended):

    pnpm i # or npm i
    
  3. Configure the environment:

    cp .env.example .env
    # Edit .env with your configuration settings
    

    Auth is recommended for contributors and is enabled when both values are set:

    • Set BASE_URL to your local URL (default: http://localhost:3003)

    • Generate a AUTH_SECRET and 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 only BASE_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_ID and S3_SECRET_ACCESS_KEY and paste them into .env for 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=openreader
      

      Notes:

      • For AWS S3: usually leave S3_ENDPOINT empty and S3_FORCE_PATH_STYLE empty/false.
      • For MinIO/SeaweedFS/R2/B2 S3: set S3_ENDPOINT to your endpoint URL and set S3_FORCE_PATH_STYLE=true when required by that provider.

    Notes:

    • The base URL for the TTS API should be accessible and relative to the Next.js server

    • To disable auth, remove either BASE_URL or AUTH_SECRET.

    • If S3 credentials are unset, they are auto-generated per startup.

  4. 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_URL in .env, migrations will target Postgres instead of local SQLite.

    Generating migrations (contributors):

    • pnpm generate generates migrations for both SQLite and Postgres (requires POSTGRES_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
  5. Start the development server:

    With pnpm (recommended):

    pnpm dev # or npm run dev
    

    or build and run the production server:

    With pnpm:

    pnpm build # or npm run build
    pnpm start # or npm start
    

    Visit 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:

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:
  • 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/nlp helpers 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.