Intelligent scanned PDF organizer — splits bulk scans into separate, well-named documents using AI
Project description
riordino
Intelligent scanned PDF organizer — Splits a bulk-scanned PDF into separate, well-named documents using AI.
riordino takes one or more PDFs (the kind you get from scanning an entire stack of mixed paperwork at once) and treats them as a single bulk to analyze.
How It Works
┌────────────────┐
│ Input PDF(s) │ (bulk scans with mixed documents)
└──────┬─────────┘
▼
1. Load & merge input PDFs, render pages
▼
2. Detect and remove blank pages
▼
3. Detect and correct rotation (Tesseract OSD)
▼
4. Analyze each page with Gemini (batched)
→ title, date, type, subject, priority, description
▼
5. Aggregate pages into logical document groups
▼
6. Determine correct page order within each group
▼
7. Split PDF and write output files
▼
┌──────────────────────────────────────────────┐
│ Output: separate PDFs + JSON metadata each │
└──────────────────────────────────────────────┘
Features
- Blank page removal — detects and strips scanner-introduced blank pages using pixel variance analysis
- Automatic rotation correction — uses Tesseract OSD to detect and fix page orientation
- AI-powered analysis — Google Gemini extracts structured metadata from each page (title, date, type, priority, and more)
- Smart document grouping — pages are aggregated into logical documents based on content, subject, dates, and page numbering
- Intelligent page ordering — reconstructs the correct reading order within each document
- Descriptive filenames — output files are named with dates, subjects, and descriptions (e.g.,
2024-03_ccss_certificate.pdf) - Selective API retries — transient Gemini failures are retried with exponential backoff; invalid structured responses fail fast
- Configurable language support —
--languageflag constrains Tesseract and Gemini to specific languages (23 languages supported) - Selective pipeline control — skip individual steps with
--skip-blanks,--skip-rotation,--skip-analysis,--skip-aggregation,--skip-ordering - Dependency checks — verifies Tesseract, language packs, and API keys before running
- Dry-run mode — preview the plan without writing any files
- Step-by-step debugging — optionally save all intermediate outputs for inspection
Prerequisites
- Python 3.14+
- Tesseract OCR with OSD data
- Google API key with access to the Gemini API
Installing Tesseract
# Arch Linux
sudo pacman -S tesseract tesseract-data-osd
# Ubuntu / Debian
sudo apt install tesseract-ocr tesseract-ocr-osd
# macOS
brew install tesseract
Install language data packages for each language you plan to use (riordino checks for these at startup):
# Arch Linux (example: German, French, Italian, Polish)
sudo pacman -S tesseract-data-deu tesseract-data-fra tesseract-data-ita tesseract-data-pol
# Ubuntu / Debian
sudo apt install tesseract-ocr-deu tesseract-ocr-fra tesseract-ocr-ita tesseract-ocr-pol
Installation
git clone https://github.com/YOUR_USERNAME/riordino.git
cd riordino
python -m venv .venv
source .venv/bin/activate
pip install .
Create a .env file in the project root:
GOOGLE_API_KEY=your_api_key_here
RIORDINO_LANGUAGES=en,de,fr,it,pl
RIORDINO_LANGUAGES sets the default for --language. If omitted, defaults to en.
Docker
docker build -t riordino .
# Install additional language packs at build time:
docker build -t riordino --build-arg LANGS="deu fra ita" .
docker run --rm \
-e GOOGLE_API_KEY \
-v "$PWD":/data \
riordino /data/scan.pdf -o /data/output/
Development setup
pip install -e '.[dev]' # installs ruff, mypy, pytest
Usage
# Single PDF
python riordino.py scan.pdf
# Multiple PDFs (merged into one bulk for analysis)
python riordino.py scan1.pdf scan2.pdf scan3.pdf
This processes the input PDF(s) and writes the split documents to the same directory as the first input file.
CLI Reference
| Option | Default | Description |
|---|---|---|
input_pdf |
(required) | Path(s) to scanned PDF file(s) — multiple files are merged into one bulk |
-o, --output-dir |
Same as input file | Directory for output files |
-b, --blank-threshold |
0.001 |
Pixel variance threshold for blank page detection (0.0–1.0) |
-n, --dry-run |
off | Show the processing plan without writing any files |
--dpi |
150 |
DPI for page rendering (72–600) |
--model |
gemini-3.1-flash-lite-preview |
Gemini model to use |
-l, --language |
$RIORDINO_LANGUAGES or en |
Comma-separated ISO 639-1 language codes |
--batch-size |
10 |
Number of pages per LLM analysis batch (1–50) |
--max-retries |
3 |
Maximum API retry attempts on failure (0–10) |
--save-steps |
off | Save intermediate outputs to an _steps/ subdirectory |
--skip-blanks |
off | Skip blank page detection (keep all pages) |
--skip-rotation |
off | Skip rotation detection and correction |
--skip-analysis |
off | Skip LLM page analysis (implies --skip-aggregation) |
--skip-aggregation |
off | Skip LLM document grouping (implies --skip-ordering) |
--skip-ordering |
off | Skip LLM page ordering within documents |
Next Steps
- Add a
--verbose/--quietflag for log level control - Explore local/open-source LLM backends as an alternative to Gemini
- Support OCR-based text extraction as a fallback when Gemini is unavailable
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file riordino-2026.4.8.tar.gz.
File metadata
- Download URL: riordino-2026.4.8.tar.gz
- Upload date:
- Size: 26.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4bdcc159711117c55a36dca21fb839fdf09e2041a9f89c65947820d6d3994f9c
|
|
| MD5 |
409e70a8bb00a3d57b375a4adc6c18d5
|
|
| BLAKE2b-256 |
e693284ef2013ed1b50b6fff5c53121e4d28fe1d2c1648e1b77f74a075d6102e
|
Provenance
The following attestation bundles were made for riordino-2026.4.8.tar.gz:
Publisher:
publish.yml on ale-grassi/riordino
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
riordino-2026.4.8.tar.gz -
Subject digest:
4bdcc159711117c55a36dca21fb839fdf09e2041a9f89c65947820d6d3994f9c - Sigstore transparency entry: 1209539219
- Sigstore integration time:
-
Permalink:
ale-grassi/riordino@4088d17a2e10494d85d41e3f2a860cecbdbee50d -
Branch / Tag:
refs/heads/main - Owner: https://github.com/ale-grassi
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@4088d17a2e10494d85d41e3f2a860cecbdbee50d -
Trigger Event:
push
-
Statement type:
File details
Details for the file riordino-2026.4.8-py3-none-any.whl.
File metadata
- Download URL: riordino-2026.4.8-py3-none-any.whl
- Upload date:
- Size: 22.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5accb041eb862121ffd5ad13eb5dfdaa928ecd8b3c52e22be6b16ffd3b09877b
|
|
| MD5 |
73905e3afb125e38a34f2297121d3899
|
|
| BLAKE2b-256 |
9501794a8c3ebfe6b4bfac355321d1543078e062e0c13588891eb9ea8bea499a
|
Provenance
The following attestation bundles were made for riordino-2026.4.8-py3-none-any.whl:
Publisher:
publish.yml on ale-grassi/riordino
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
riordino-2026.4.8-py3-none-any.whl -
Subject digest:
5accb041eb862121ffd5ad13eb5dfdaa928ecd8b3c52e22be6b16ffd3b09877b - Sigstore transparency entry: 1209539305
- Sigstore integration time:
-
Permalink:
ale-grassi/riordino@4088d17a2e10494d85d41e3f2a860cecbdbee50d -
Branch / Tag:
refs/heads/main - Owner: https://github.com/ale-grassi
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@4088d17a2e10494d85d41e3f2a860cecbdbee50d -
Trigger Event:
push
-
Statement type: