- 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.
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
- 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, picks the sharpest frame in each, detects the document outline, and produces a perspective-corrected crop.
- (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 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, for every detected document, additionally writes:
pdf/<name>.pdf— one single-page PDF per detected document, namedYYYY-MM-DD_vendor.pdffrom OCR-extracted date/vendor, orUNSORTED_<timestamp>.pdfif OCR couldn't find anything usableexport_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.