Read PDF books in augmented reality on Snap Spectacles. Upload books from a web browser, then open them hands-free on your glasses with page-turning via finger pinch.
LensReader_Trailer.mp4
- Pair your Spectacles to the web app with a one-time code
- Upload PDFs from any browser at your backend URL
- Open a book in the Spectacles library UI — pages are streamed to the glasses
- Read in AR: pinch left or right to turn pages, pinch the Library button to return
Snap Spectacles (Lens Studio)
└── PairingUI.ts — device pairing flow
└── LibraryUI.ts — bookshelf grid, cover images, book loading
└── PageMovement.ts — in-AR reading, pinch to turn pages
└── BackendConfig.ts — shared backend URL + modules
Backend (FastAPI + Docker + Caddy)
└── /api/auth/ — register, login, change password
└── /api/pairing/ — device pairing and verification
└── /api/bookshelf/ — upload, list, serve pages and covers
└── static/index.html — web UI
| Tool | Version |
|---|---|
| Lens Studio | 5.x |
| Docker + Docker Compose | any recent |
| A Linux server with a public IP | (for remote deploy) |
| A domain name pointed at that IP | (for HTTPS via Caddy) |
git clone https://github.com/nigelhartman/spectacles_book_viewer.git
cd spectacles_book_viewer/BackendEdit Caddyfile and replace the hostname with your own domain:
your.domain.com {
reverse_proxy book-viewer-backend:8000
}
Make sure your domain's DNS A record points to your server's IP address. Caddy will automatically obtain a TLS certificate via Let's Encrypt on first start.
The deploy script uploads the backend and restarts services over SSH:
cd Backend
chmod +x deploy.sh
./deploy.shIt will ask for your SSH key path (default: ~/.ssh/hetzner_private) and enter the passphrase once. Then it:
- Uploads
app/,Dockerfile,docker-compose.yml,Caddyfile, and helper scripts - Runs
docker compose up -d --build - Health-checks each service and your HTTPS endpoint
First deploy note: Caddy may take ~30 seconds to obtain its TLS certificate. The health check will warn if the endpoint is not yet responding — just wait and retry.
Open https://your.domain.com in a browser. You should see the LensReader web UI.
Register an account, then upload a PDF (≤ 25 MB, ≤ 250 pages).
Open book_viewer.esproj in Lens Studio 5.x.
In the scene hierarchy, find the BackendConfig SceneObject. In the Inspector panel, set Backend Url to your backend's base URL:
https://your.domain.com
No trailing slash.
In the scene hierarchy, find the LibraryUI SceneObject. In the Inspector, assign a plain unlit material to the Cover Material input. This material is cloned per book slot and used to display cover images.
Connect your Spectacles and use Lens Studio's Push to Device button, or publish as a personal lens via My Lenses.
To run the backend locally without a domain:
cd Backend
docker compose up --buildThe API is available at http://localhost:8000.
In Lens Studio, set Backend Url to your machine's local network IP (e.g. http://192.168.1.x:8000). Spectacles must be on the same Wi-Fi network.
The first time the lens runs:
- The Spectacles display a 6-character pairing code and your backend URL
- Open the web UI, log in, and enter the code in the Pair Device section
- A number appears on the web page — select the matching number on the Spectacles to confirm
- The device is now paired and the library opens automatically
The pairing token is stored in Spectacles persistent storage. On subsequent runs the lens checks if it is still paired and skips straight to the library.
To unpair: open Settings (⚙️) in the library UI and tap Reset Lens.
- Passwords require ≥ 7 characters and at least one special character
- Usernames require ≥ 6 characters
- PDF uploads are limited to 25 MB and 250 pages, PDF format only
- All book data is scoped to the authenticated user
- A daily cron job at midnight cleans up all stored books and resets the database
- HTTPS is enforced via Caddy + Let's Encrypt
spectacles_book_viewer/
├── Assets/
│ └── Scripts/
│ ├── BackendConfig.ts # backend URL + module inputs
│ ├── PairingUI.ts # pairing flow UI
│ ├── LibraryUI.ts # bookshelf and book loading
│ └── PageMovement.ts # AR reading and page turning
├── Backend/
│ ├── app/
│ │ ├── main.py
│ │ ├── auth.py
│ │ ├── database.py
│ │ ├── routers/
│ │ └── static/index.html
│ ├── Dockerfile
│ ├── docker-compose.yml
│ ├── Caddyfile
│ ├── requirements.txt
│ ├── deploy.sh
│ ├── entrypoint.sh
│ └── cleanup.sh
└── book_viewer.esproj
MIT