├── .python-version ├── reader3.png ├── .gitignore ├── pyproject.toml ├── README.md ├── templates ├── library.html └── reader.html ├── server.py ├── reader3.py └── uv.lock /.python-version: -------------------------------------------------------------------------------- 1 | 3.10 2 | -------------------------------------------------------------------------------- /reader3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/karpathy/reader3/HEAD/reader3.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Python-generated files 2 | __pycache__/ 3 | *.py[oc] 4 | build/ 5 | dist/ 6 | wheels/ 7 | *.egg-info 8 | 9 | # Virtual environments 10 | .venv 11 | 12 | # Custom 13 | *_data/ 14 | *.epub 15 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "reader3" 3 | version = "0.1.0" 4 | description = "Simple EPUB reader web app" 5 | readme = "README.md" 6 | requires-python = ">=3.10" 7 | dependencies = [ 8 | "beautifulsoup4>=4.14.2", 9 | "ebooklib>=0.20", 10 | "fastapi>=0.121.2", 11 | "jinja2>=3.1.6", 12 | "uvicorn>=0.38.0", 13 | ] 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # reader 3 2 | 3 |  4 | 5 | A lightweight, self-hosted EPUB reader that lets you read through EPUB books one chapter at a time. This makes it very easy to copy paste the contents of a chapter to an LLM, to read along. Basically - get epub books (e.g. [Project Gutenberg](https://www.gutenberg.org/) has many), open them up in this reader, copy paste text around to your favorite LLM, and read together and along. 6 | 7 | This project was 90% vibe coded just to illustrate how one can very easily [read books together with LLMs](https://x.com/karpathy/status/1990577951671509438). I'm not going to support it in any way, it's provided here as is for other people's inspiration and I don't intend to improve it. Code is ephemeral now and libraries are over, ask your LLM to change it in whatever way you like. 8 | 9 | ## Usage 10 | 11 | The project uses [uv](https://docs.astral.sh/uv/). So for example, download [Dracula EPUB3](https://www.gutenberg.org/ebooks/345) to this directory as `dracula.epub`, then: 12 | 13 | ```bash 14 | uv run reader3.py dracula.epub 15 | ``` 16 | 17 | This creates the directory `dracula_data`, which registers the book to your local library. We can then run the server: 18 | 19 | ```bash 20 | uv run server.py 21 | ``` 22 | 23 | And visit [localhost:8123](http://localhost:8123/) to see your current Library. You can easily add more books, or delete them from your library by deleting the folder. It's not supposed to be complicated or complex. 24 | 25 | ## License 26 | 27 | MIT -------------------------------------------------------------------------------- /templates/library.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 |No processed books found. Run reader3.py on an epub first.
.
94 | The browser resolves this to /read/{book_id}/images/pic.jpg.
95 | """
96 | # Security check: ensure book_id is clean
97 | safe_book_id = os.path.basename(book_id)
98 | safe_image_name = os.path.basename(image_name)
99 |
100 | img_path = os.path.join(BOOKS_DIR, safe_book_id, "images", safe_image_name)
101 |
102 | if not os.path.exists(img_path):
103 | raise HTTPException(status_code=404, detail="Image not found")
104 |
105 | return FileResponse(img_path)
106 |
107 | if __name__ == "__main__":
108 | import uvicorn
109 | print("Starting server at http://127.0.0.1:8123")
110 | uvicorn.run(app, host="127.0.0.1", port=8123)
111 |
--------------------------------------------------------------------------------
/templates/reader.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |