A self-hosted web reader that shows a book two ways at once: the readable text, and the scanned page it came from. An audio track plays alongside, highlighting the sentence being read.
Point it at a folder of books, open a browser, read. Nothing leaves your machine, and nothing in your library is modified.
A great many books are only readable as scans, or only readable in a language you do not have. Both problems have solutions that work in isolation and fall apart together: a translation loses the diagrams, an OCR pass loses the page, an audiobook loses your place. ReadCommons keeps them in one view, so a book that was practically closed to you becomes one you can actually read.
- Two views of one book. Text for reading and translation, the original scan for what the text layer loses: schematics, tables, handwriting, marginalia. Switching views keeps your position.
- Audio synchronised to the sentence. The spoken sentence is highlighted. Click any sentence to move the audio there.
- Variable speed without pitch artefacts, and a position that survives a reload or a move to another device.
- A library with full text search. SQLite FTS5, so search is instant and needs no extra service.
You need Docker.
git clone https://github.com/readcommons/readcommons.git
cd readcommons
cp .env.example .env # point READCOMMONS_LIBRARY_ROOT at your books
docker compose -f docker/compose.yml upOpen http://localhost:8710/app/. That is the reader. http://localhost:8710/
is the presentation page, a plain HTML page describing the project for whoever
lands on the address without knowing what it is; set
READCOMMONS_PUBLIC_SITE=false and / goes straight to the reader instead.
Your books are mounted read only. Everything ReadCommons derives goes to a separate volume, and the originals are never touched.
Details, other install paths and troubleshooting: docs/INSTALL.md.
Out of the box there is no login, which is the right default for a reader bound to loopback on your own machine. The moment the address is reachable from anywhere else, that default is wrong: the API hands out the full text and the audio of every book in the library, to whoever asks.
One account, two settings. Produce the hash, paste the line it prints into
.env, add a user name, restart:
python -m readcommons hash-password # asks twice, prints the hash lineREADCOMMONS_AUTH_USER=you
READCOMMONS_AUTH_PASSWORD_HASH=pbkdf2_sha256$600000$...
READCOMMONS_COOKIE_SECURE=true # behind TLSThe password itself is never written anywhere: only a PBKDF2 hash is stored,
and the session that follows a login is a signed cookie, so restarting the
server does not sign you out. Everything under /api/ is then closed, which is
everything that can return a book. The presentation page and the interface
bundle stay public: they are open source and hold no library data.
- It scans your library and indexes what it finds into SQLite FTS5, without writing anything next to your books.
- The text view renders EPUB with foliate-js; the page view renders the original PDF with pdf.js. Both are driven by one position, so they cannot drift apart.
- An optional alignment pass matches the audio track to the text and stores sentence timings in the data directory.
- The player looks up the current sentence from those timings and highlights it, in both views at once.
- FastAPI serves the API, the built interface under
/app/and the presentation page at/from one process, so there is one thing to run and one port to expose.
systemd user units, a Docker deployment, and the nginx blue-green path if you already run nginx: docs/DEPLOY.md.
./scripts/dev.sh starts both servers, ./scripts/check.sh runs exactly what
CI runs. See CONTRIBUTING.md.
MIT. See LICENSE.
