Turn notebook videos into reviewable Markdown.
Local capture history · Optional OpenAI OCR · Gap & duplicate review · Markdown / JSON export
Notebook Lens turns a video of your paper notebook into clean, searchable Markdown. Instead of retyping pages by hand or letting handwritten journals pile up unread, you record a short clip flipping through the pages — and the app pulls out one sharp image per page, optionally transcribes the handwriting with OpenAI vision, and gives you back a date-organized journal you can read, search, edit, and keep next to the original page images. It runs locally, sends nothing unless you switch OCR on, and saves every capture so you can reopen and correct it later.
Point your phone at a notebook, slowly turn the pages, and Notebook Lens gives you:
- Page images extracted at likely page-turn boundaries
- Handwriting transcription, page by page, via OpenAI vision (optional)
- Markdown and JSON exports you can read, search, and edit
Notebook pages can contain sensitive personal information. Read this before running OCR:
- When OCR is enabled, each extracted page image is sent to the OpenAI model you configure, along with the OCR prompt and any local ambiguity-dictionary context.
- Generated Markdown, JSON, page images, and the ambiguity dictionary may contain
private text. They are written under
output/and ignored by git. - Run with
--no-ocr(or leaveOPENAI_API_KEYunset) to extract page images locally without sending anything to OpenAI. - Keep
.env,output/, andambiguity_dictionary.mdout of source control — the bundled.gitignorealready excludes them.
This project sends data only to OpenAI, and only when you enable OCR with your own API key. There is no other telemetry or network egress.
- Upload one or more notebook videos in a Streamlit app (or use the CLI)
- Use
ffmpegand image scoring to keep only unique, settled page frames - Optionally send each page to OpenAI for handwritten-text extraction
- Render a date-grouped Markdown journal plus a JSON export
- Save each capture under
output/captures/with a friendly name, review state, page images, Markdown, and JSON - Reopen past captures to review images and edit text
- Validate generated Markdown anchors and local page-image links
- Cap pages processed per run so one bad threshold can't explode cost
- Track ambiguous words in a local Markdown dictionary you can edit and reuse
- Python 3.11+
ffmpegon yourPATH- An OpenAI API key (
OPENAI_API_KEY) — only if you want OCR
OCR defaults to OpenAI's gpt-4.1-mini — a vision-capable model picked
because it reads handwriting well while staying cheap enough to run page by page
across a whole notebook without a surprising bill. It's the cost/quality sweet
spot for this workload; reach for a larger model only when a notebook's
handwriting is unusually hard to read. Override it any time with the
NOTES_OCR_MODEL environment variable, or the model box in the app.
Notebook Lens runs two ways: an interactive Streamlit app (app.py) and a
scriptable command-line tool (process_video.py). Set up the environment once:
python3 -m venv .venv
source .venv/bin/activate
pip install -e '.[dev]'
cp .env.example .env # then add your OPENAI_API_KEY (optional, for OCR)streamlit run app.pyOpen the local URL Streamlit prints and upload a video. Each movie becomes a separate capture in the sidebar history and can be renamed after processing.
python process_video.py path/to/notebook.mov \
--max-pages 40 \
--image-link-mode relative-encoded \
--notes-placement appendixUse --no-ocr (either entry point) to extract pages without calling OpenAI.
The default export is built to render in common Markdown readers:
- entry headings use
Entry YYYY-MM-DDso TOC anchors don't start with a number - image links default to URL-encoded relative paths
- OCR notes default to an appendix so the journal body stays readable
- output validation rejects raw HTML, broken TOC links, and missing page images
Image link modes: relative-encoded (default, portable), relative
(readable), absolute-file (machine-local file:// URLs).
OCR note placement: appendix, inline, or hidden.
- Extraction assumes a slow page turn with a reasonably stable camera.
- Threshold tuning matters: for handheld video, start around
0.05. Lower captures more frames; higher is stricter. - With no API key, the app still extracts pages so you can validate the video stage.
- Each run updates
ambiguity_dictionary.md— fill inCanonicalandContext, then rerun to normalize future output.
pytestMIT © 2026 dgrissen2