Files
PaperPod/README.md
T
ilia 2c28a623fb Add OCR naming and PNG-to-PDF export (modules 5-7, single-page only)
- ocr/: Tesseract-based vendor/date/total extraction. PSM 6 (single
  uniform text block) instead of the default fixes receipts where
  item-name/price columns were otherwise split into separate blocks and
  dropped. Vendor line picked by median (not max) word height within the
  top fraction of the crop, breaking ties by topmost line - max() was
  fooled by single descender/ascender glyphs (commas, parens) inflating
  one word's bounding box.
- naming/: merge OCR metadata into YYYY-MM-DD_vendor.pdf filenames,
  degrading gracefully to UNSORTED_<timestamp>.pdf; per-run dedup.
- pdf/: img2pdf-based multi-page-capable assembly (images_to_pdf).
- pipeline.run_export(): OCR + name + PDF each detected crop from a
  detect() report, writes pdf/ and export_summary.csv.
- New `paperpod export <video>` CLI command.
- Verified against synthetic Home Depot/Metro/Petro-Canada receipts:
  3/3 correct vendor, date, and total after tuning.

Known limitation (documented in README): no page-flip/multi-page
grouping yet (that's events/, still unbuilt) - every detected document
becomes its own single-page PDF.
2026-07-07 18:04:06 -04:00

3.8 KiB

PaperPod

Local, privacy-first tool that converts a single overhead video recording of documents (receipts, letters, multi-page stacks) into individual, properly named PDFs ready for a Paperless-ngx consume folder. All processing runs offline on your machine.

How it works

  1. Record one continuous overhead video, placing documents under the camera one at a time (optionally saying out loud what each one is).
  2. PaperPod finds "stable windows" where nothing is moving, picks the sharpest frame in each, detects the document outline, and produces a perspective-corrected crop.
  3. (Upcoming) Spoken descriptions (faster-whisper) or OCR (Tesseract) name each document; multi-page stacks are grouped into single PDFs; a review step lets you 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, sharpness Built
events/ State machine: placed / page-flipped / cleared, pod grouping Planned
transcribe/ Local speech-to-text (faster-whisper) Planned
ocr/ OCR fallback naming (Tesseract): vendor, date, total Built
naming/ Final filename assembly + summary CSV Built
pdf/ PDF assembly (img2pdf) Built — single-page only, no consume-folder staging
review_cli/ Pre-export review (rename/merge/split) Planned

Known limitation: there is no page-flip / multi-page grouping yet (that's events/). Every detected document currently becomes its own single-page PDF, even if it was physically one page of a stack or one side of a double-sided document. Don't point export at the Paperless-ngx consume folder for multi-page documents until events/ exists — you'll get one PDF per page instead of one PDF per document.

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 candidates
  • frames/window_NNN_full.png — the full best frame per window (debugging)
  • report.json — video metadata, motion events, stable windows, detections
  • motion_scores.csv — per-sample motion scores, for tuning motion.threshold

export runs detect and then, for every detected document, additionally writes:

  • pdf/<name>.pdf — one single-page PDF per detected document, named YYYY-MM-DD_vendor.pdf from OCR-extracted date/vendor, or UNSORTED_<timestamp>.pdf if OCR couldn't find anything usable
  • export_summary.csv — timestamp, pod_id, page_count, source_of_name (ocr/none), OCR vendor/date/total/confidence, final filename

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 floor, Canny thresholds, 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.

Tests

python -m pytest

Tests are self-contained: they synthesize frames and tiny videos on the fly, no sample assets required.