PaperPod
Offline overhead video → named PDFs for Paperless. Motion windows, perspective crop, OCR — nothing leaves your machine.
Public mirror: github.com/Gitilia/PaperPod
How it works
- Record one continuous overhead video, placing documents under the camera one at a time (optionally saying out loud what each one is).
- PaperPod finds "stable windows" where nothing is moving, detects the document outline, and produces a perspective-corrected crop. Detection scores candidate contours by shape (filled, convex, plausible aspect ratio) rather than just picking the largest one, and runs on a resolution-independent downscale so it works the same on FHD and 4K phone video. Best-frame selection prefers hands-free frames first (skin detection over the document region), sharpness second — so a finger holding the page flat doesn't end up in the scan when a clean frame exists elsewhere in the event.
- The crop is auto-rotated upright (0/90/180/270) — no need to place documents facing a particular way.
- Tesseract OCR (run against an enhanced crop) extracts a vendor/date/total
for receipts, a form code + organization name for recognized Canadian tax
slips (T4, T4A, T5008, ...), a printed document title (Transaction History,
Invoice, Direct Deposit, ...), and a printed "PAGE X OF Y" marker where
present. Enhancement always runs for OCR (and optionally on the saved
crop when
document.enhanceis on): illumination normalize → CLAHE → gamma darkening for pale grey / dot-matrix ink (e.g. bank branch statements). Crops narrower than ~1800px also get a 1.5× cubic upscale first so thin ribbon strokes have enough pixels; native ~2k webcam captures (roughly 2000px-wide crops) skip that step, since OCR results there are identical with or without the upscale. - Consecutive captures whose page markers advance under the same page total are grouped into one multi-page PDF (a bank statement's 17 pages become one PDF, not 17). Duplicate captures of the same page keep the better read.
- (Upcoming) Spoken descriptions (faster-whisper) as an alternate naming source; a review step to rename/merge/split before export.
Status
| Module | Purpose | Status |
|---|---|---|
capture/ |
Frame sampling + audio extraction from video files | Built |
vision/ |
Motion detection, document contours, perspective crop, hands-free best-frame selection | Built |
events/ |
State machine: placed / page-flipped / cleared, pod grouping | Planned (page-marker grouping in pipeline.py covers paginated documents) |
transcribe/ |
Local speech-to-text (faster-whisper) | Planned |
ocr/ |
OCR naming (Tesseract): vendor, date, total, doc title, page markers | Built |
naming/ |
Final filename assembly + summary CSV | Built |
pdf/ |
PDF assembly (img2pdf), multi-page | Built — no consume-folder staging yet |
review_cli/ |
Pre-export review (rename/merge/split) | Planned |
Known limitation: multi-page grouping relies on a printed
"PAGE X OF Y" marker (bank statements, most system-generated letters).
Multi-page documents without printed page numbers still export as one
PDF per page until the motion-based events/ state machine exists.
Setup
Requires Python 3.11+ and ffmpeg (brew install ffmpeg).
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
Usage
# Generate a synthetic test video (no camera needed)
python sample_data/make_sample_video.py
# Inspect a video
python -m paperpod probe sample_data/videos/synthetic_sample.mp4
# Detect stable windows + document candidates
python -m paperpod detect sample_data/videos/synthetic_sample.mp4
# Detect, OCR-name, and export each document as its own PDF
python -m paperpod export sample_data/videos/receipts_sample.mp4
# Extract the audio track (16 kHz mono WAV, whisper-ready)
python -m paperpod extract-audio my_recording.mp4
detect writes to output/<video-name>/:
crops/window_NNN.png— perspective-corrected document candidatesframes/window_NNN_full.png— the full best frame per window (debugging)report.json— video metadata, motion events, stable windows, detectionsmotion_scores.csv— per-sample motion scores, for tuningmotion.threshold
export runs detect and then groups, names, and writes:
pdf/<name>.pdf— one PDF per document (multi-page when printed "PAGE X OF Y" markers chain consecutive captures together), namedYYYY-MM-DD_vendor[_doc-title].pdffrom OCR-extracted date/vendor/title (or<year>_<form_code>_<org_name>for recognized tax slips). When the document's own date can't be read, the scan date is used instead — files always sort chronologically, nothing is namedUNSORTEDanymore (export_summary.csvrecordssource_of_name: scan_datefor those)export_summary.csv— timestamp, pod_id, page_count, page_windows, source_of_name (ocr/llm/scan_date/none), OCR vendor/form_code/date/total/confidence, doc title, detection confidence, rotation applied, final filename
Before naming, each crop is auto-rotated to be right-side-up (Tesseract's built-in orientation detection, falling back to a 4-way OCR-confidence sweep when a crop has too little text for that to work) — you don't need to worry about which way documents face when placing them.
Nothing is copied into a Paperless-ngx consume directory yet — review the
pdf/ folder yourself before moving files anywhere.
Tuning
All thresholds live in config.yaml (motion sensitivity, stable window
duration, contour size/shape filters, Canny thresholds, detection
downscale width, auto-rotate/enhance toggles, speech matching window,
output paths). Plot motion_scores.csv to pick a motion.threshold that
separates your camera's noise floor from real hand movement.
Recording tips (from real-world testing): shoot in 4K, keep hands out of frame once a document is placed, and use a plain dark, matte (non-glossy) surface under documents — busy wood grain or shiny surfaces make contour detection and OCR meaningfully harder. Document detection and orientation correction are tuned against real phone-camera footage (receipts on a wood table and multi-page CRA tax slips), not just synthetic test videos.
Tests
python -m pytest
Tests are self-contained: they synthesize frames and tiny videos on the fly, no sample assets required.
License
MIT — see LICENSE.