Skip to main content

A terminal EPUB reader and personal library built with Textual.

Project description

bookreader-tui

PyPI Python License test

A terminal EPUB reader and personal library, built with Textual.

bookreader-tui library screen

Open an EPUB in a focused two-column or two-page TUI, remember where you left off, organize what you've read into a small SQLite library, and render figures inline in kitty, iTerm2, WezTerm, or any sixel-capable terminal — automatically, with placeholders elsewhere.

pipx install bookreader-tui && bookreader path/to/book.epub

Why bookreader-tui?

  • vs Calibre — no Qt, no GUI process, no separate viewer. Lives in your terminal where the rest of your workflow already is.
  • vs epy — full library: collections, ratings, wishlist, reading-time stats, bookmarks with notes, multi-book session continuity.
  • vs mdcat / piping to a pager — proper EPUB rendering (chapters, TOC, two-page mode, inline images) instead of a flat text dump.
  • vs reading on your phone — keyboard-driven, distraction-free, themed, scriptable. Your reading position syncs by EPUB identifier so the same book at a different path resumes in the same place.

Features

  • Distraction-free reader — centered reading column with a typographically sane width cap, TOC sidebar, optional two-page spread.
  • Persistent position — picks up exactly where you left off, per book, identified by EPUB dc:identifier.
  • Library — SQLite-backed, with collections (Currently Reading, Finished, Want to Read), ratings, bookmarks with notes, per-book reading time.
  • Wishlist books — track titles you intend to read before you have the file. Attach a real EPUB later.
  • Inline images — kitty / iTerm2 / WezTerm / sixel via textual-image; auto-detected, runtime-toggleable with I.
  • Three built-in themes (dark, light, sepia) plus the standard Textual command palette.
  • Pure Python, no external services — your library lives in ~/.local/share/bookreader/. Nothing leaves your machine.

Install

The PyPI package is named bookreader-tui. The bare bookreader name was already taken on PyPI; the import path and the console script are still bookreader.

Recommended: pipx or uv tool

For end-user CLI tools, prefer an isolated install over pip install into your system Python — that path is blocked on most modern Linux distros by PEP 668 anyway.

# pipx (most popular)
pipx install bookreader-tui

# OR uv tool (faster, same idea)
uv tool install bookreader-tui

Either gives you a global bookreader command with its dependencies sandboxed in their own venv.

pip (inside a venv)

If you're already managing a virtualenv:

python -m venv .venv
source .venv/bin/activate          # Linux/macOS
# .venv\Scripts\activate           # Windows
pip install bookreader-tui

From source

git clone https://github.com/prajwalmahajan101/BookReader
cd BookReader
pipx install -e .                  # or: pip install -e . inside a venv

Requirements

  • Python ≥ 3.11
  • A terminal with at least 80 columns. 120+ recommended.
  • For inline images: kitty, iTerm2, WezTerm, Ghostty, or a sixel-capable terminal (foot, mlterm, xterm with --enable-sixel-graphics, Konsole, Windows Terminal).

Quick start

# Open a book directly — gets added to your library on first open
bookreader path/to/book.epub

# Land on the library home; pick a book with Enter
bookreader

# One-off read without touching the library
bookreader --no-library path/to/book.epub

That's it. Position, bookmarks, and reading time persist automatically.

Inline images — terminal setup

bookreader ships inline-image rendering via textual-image. On launch it sniffs your terminal and enables images if it recognizes a graphics-capable one.

What works out of the box (no config)

Terminal Protocol used Notes
kitty Kitty Graphics (TGP) Best fidelity. Auto-detected via TERM=xterm-kitty or KITTY_WINDOW_ID.
Ghostty Kitty Graphics (TGP) Native support; behaves like kitty.
iTerm2 iTerm2 inline images macOS. Auto-detected via TERM_PROGRAM=iTerm.app.
WezTerm Kitty / iTerm / Sixel Supports all three; auto-detected via TERM_PROGRAM=WezTerm.
Konsole Kitty (partial) Recent versions only.
VS Code terminal iTerm2 inline images Works for static images.

Sixel terminals (force-enable)

foot, mlterm, xterm (built with --enable-sixel-graphics), mintty, Contour, Windows Terminal. Auto-detection doesn't cover these — set the override:

BOOKREADER_IMAGES_ENABLED=1 bookreader path/to/book.epub

Terminals without graphics support

Anything else (e.g. plain xterm, gnome-terminal, Terminal.app, tmux without a graphics-capable host) renders figures as [image: alt] placeholders. Everything else works normally.

Toggle at runtime

Press I inside the reader to flip images on/off mid-session — useful for comparing or when an image is breaking your layout.

Screenshots

Scroll mode Two-page spread
reader paged reader

Key bindings

Reader

Key Action
j / k Scroll line down / up
space / b Page down / up
n / p Next / previous chapter
g / G Top / bottom of chapter
t Toggle TOC sidebar
2 Toggle two-page mode
m Add a bookmark (with optional note)
' List bookmarks — Enter jumps
c Toggle completion of the current book
C Open Collections overview
W Open Wishlist overview
I Toggle inline image rendering
T Cycle theme
? Show key hints
q Save and back (or quit)

Scrolling past the end of a chapter flows into the next one; going back from the start flows into the previous chapter's end.

Library

Key Action
Enter / i Open the highlighted book
a Add a book (prompts for path)
A Add a wishlist entry (title + author)
C Browse books grouped by collection
W Browse the wishlist
d / Delete Remove the highlighted book
c Toggle completion
15 Set rating; 0 clears
Tab Switch focus between sidebar and table
T / ? / q Theme / help / quit

CLI subcommands

bookreader add path/to/book.epub                          # add without opening
bookreader add --wishlist --title "T" --author "A"        # phantom entry, no file
bookreader attach <book-id> path/to/book.epub             # promote a wishlist row
bookreader list                                           # print every book
bookreader stats                                          # minutes read per book
bookreader --version

Configuration

All settings are environment variables prefixed BOOKREADER_. Set them in your shell rc, your .env, or per-invocation:

Variable Default Effect
BOOKREADER_IMAGES_ENABLED auto-detect 1 to force inline images on; 0 to force off.
BOOKREADER_READING_WIDTH 110 Single-column reading width in cells (60–200).
BOOKREADER_THEME dark dark, light, or sepia.
BOOKREADER_TWO_PAGE_DEFAULT 0 1 to launch in two-page mode.
BOOKREADER_LINE_SCROLL 1 Lines per j/k press (1–10).
BOOKREADER_PAGE_SCROLL_PCT 90 Page jump as a percentage of viewport (10–100).
BOOKREADER_SHOW_TOC_DEFAULT 1 0 to start with the TOC sidebar collapsed.
NO_COLOR unset Honoured by textual — falls back to a safe palette.

File locations (XDG)

What Default path
Library DB ~/.local/share/bookreader/library.db
Reading-position cache ~/.local/state/bookreader/positions.json
JSON bookmark fallback ~/.local/state/bookreader/bookmarks.json
Log file (rotating) ~/.local/state/bookreader/log/bookreader.log

Honors XDG_DATA_HOME and XDG_STATE_HOME if set. Safe to back up the whole ~/.local/share/bookreader/ directory.

Upgrading from 0.x JSON store

On first launch the library service migrates ~/.local/state/bookreader/positions.json entries that match books already added; the JSON is renamed to positions.json.migrated once data flows. No manual step needed.

Troubleshooting

bookreader: command not found You installed with pip install bookreader-tui directly into your system Python and PEP 668 blocked it, or you installed into a venv that isn't activated. Use pipx install bookreader-tui (recommended) or activate the venv.

Images render as [image: alt] placeholders even in kitty Check that the env var hasn't been turned off: echo $BOOKREADER_IMAGES_ENABLED. If it's set to 0, unset it or set to 1. Inside the reader, press I to force-toggle. If it still doesn't render, your kitty session may be inside tmux/screen without graphics passthrough — start kitty directly to test.

Images render at low quality The image is being upscaled into a column that's too narrow. Raise BOOKREADER_READING_WIDTH (try 140 or 160) and re-launch.

q quits while I'm typing in a wishlist title It shouldn't — modals isolate keystrokes. If you hit this, please open an issue with your terminal + Textual version.

My EPUB has no images at all Confirm: bookreader list shows it. Then python -c "from ebooklib import epub; b=epub.read_epub('path.epub'); print(sum(1 for i in b.get_items() if i.media_type.startswith('image/')))". If that prints 0, the EPUB itself has no embedded images.

Library got corrupted Stop the app. Move the file aside: mv ~/.local/share/bookreader/library.db ~/.local/share/bookreader/library.db.bak. Re-launch — a fresh library is created. Use bookreader add <path> to repopulate.

I want a clean slate rm -rf ~/.local/share/bookreader ~/.local/state/bookreader. Removes the library, positions, and logs.

Development

git clone https://github.com/prajwalmahajan101/BookReader
cd BookReader
python -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"
pre-commit install

# The full local gate (matches CI):
pytest -q
ruff format --check . && ruff check src/ tests/
mypy src/

Project layout

src/bookreader/
  core/      # config, paths, logging, exceptions    (no I/O)
  epub/      # parsing + rendering                   (no UI, no DB)
  state/     # JSON position + bookmark fallback
  library/   # SQLite library: db, repo, service, migrations
  ui/        # Textual app, screens, widgets, themes
tests/
  unit/         # parsers, renderer, config
  integration/  # service + screen pilot tests
docs/adr/    # architecture decision records

UI never imports a repository directly — it talks to a service.

Releasing

Tag-driven, fully automated:

# Bump version in src/bookreader/__init__.py and pyproject.toml,
# add a [X.Y.Z] section to CHANGELOG.md, commit, then:
git tag -a vX.Y.Z -m "Release X.Y.Z"
git push origin main vX.Y.Z

.github/workflows/release.yml builds the sdist + wheel, verifies the tag matches pyproject.toml, publishes to PyPI via trusted publishing, and creates a GitHub Release with the CHANGELOG-extracted notes and dist files attached.

Project background

Built solo as a phase-driven exercise: each phase is a feature branch with its own ADR, atomic commits, and merge-clean history.

  • Phase 1 — Reader Core
  • Phase 1.5 — Two-page mode
  • Phase 2 — SQLite Library
  • Phase 3 — Polish (bookmarks, sessions, phantom books, inline images)
  • Phase 4 — Library curation (collections + wishlist overview)
  • Phase 5 — UX polish + first PyPI release as bookreader-tui

Architecture decisions live in docs/adr/. Version-by-version notes in CHANGELOG.md.

License

Apache-2.0 — Copyright 2026 Prajwal Mahajan.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

bookreader_tui-1.0.0.tar.gz (68.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

bookreader_tui-1.0.0-py3-none-any.whl (81.5 kB view details)

Uploaded Python 3

File details

Details for the file bookreader_tui-1.0.0.tar.gz.

File metadata

  • Download URL: bookreader_tui-1.0.0.tar.gz
  • Upload date:
  • Size: 68.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for bookreader_tui-1.0.0.tar.gz
Algorithm Hash digest
SHA256 6cb59377747be689f9ae6fb58681db05be16c198ea421af83375196f39aae749
MD5 9eb7204b5fe89b7418808507995cf587
BLAKE2b-256 3cc36d1ee4305116995f84759d305407ec91689f48348bec5f15f15982c10032

See more details on using hashes here.

Provenance

The following attestation bundles were made for bookreader_tui-1.0.0.tar.gz:

Publisher: release.yml on prajwalmahajan101/BookReader

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file bookreader_tui-1.0.0-py3-none-any.whl.

File metadata

  • Download URL: bookreader_tui-1.0.0-py3-none-any.whl
  • Upload date:
  • Size: 81.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for bookreader_tui-1.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 66dc923d04d6800d0aba895a2a59add1b3e52517756ece4d32b513acb39f2d05
MD5 d403d55b713d7173b2851bdced353459
BLAKE2b-256 4f1f1229423157d0cf9f76fb18cdd08e0778ab6b03a5a8a40291cb592bfda4ab

See more details on using hashes here.

Provenance

The following attestation bundles were made for bookreader_tui-1.0.0-py3-none-any.whl:

Publisher: release.yml on prajwalmahajan101/BookReader

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page