Sahaf
Local PDF & EPUB to Markdown converter with automatic digital/scanned detection, OCR support, smart splitting, and page-range selection. Converts books to clean, self-contained Markdown files with embedded images using Marker and Surya OCR (90+ languages). No cloud APIs — runs entirely on your hardware.
Features
- PDF & EPUB support — handles both formats natively
- Automatic PDF classification — detects digital, scanned, or mixed PDFs via PyMuPDF
- High-accuracy conversion — works with both Marker 1.x and Marker 2.x, whichever you install
- Built-in OCR — Surya OCR supports 90+ languages (Turkish, English, Arabic, etc.)
- Page/chapter range selection — convert only a specific section of the book (e.g. pages 19-88)
- Smart splitting — split output into N parts, cutting at heading/paragraph boundaries instead of mid-sentence
- Self-contained output — images embedded as base64 directly in Markdown, no separate files
- Split preview — see exactly how parts will be divided before downloading
- Bilingual UI — Turkish / English interface with one-click toggle
- Dark/light theme — lavender-toned design, persistent toggle
- Drag & drop UI — clean single-page web interface
Install
pip install sahaf
Or from source:
git clone https://github.com/arikusi/sahaf.git
cd sahaf
pip install -e .
Models are downloaded automatically on first conversion.
Choosing Marker 1 or Marker 2
Both lines work — Sahaf detects which one is installed and configures it accordingly. The difference only matters for scanned PDFs; digital PDFs and EPUBs convert the same either way.
Marker 1.x — OCR in-process, nothing else to install
pip install sahaf "marker-pdf<2"
OCR runs inside the Python process with torch, on any CUDA GPU including pre-Ampere cards (GTX 16xx, RTX 20xx). The safe choice for scanned books.
Marker 2.x — faster and more accurate, needs an OCR server
pip install sahaf
Marker 2 reads the PDF text layer and only OCRs what needs it (76.0% on olmOCR-bench in balanced mode). But its OCR model is served out of process, so scanned pages need one of:
- Docker plus the NVIDIA Container Toolkit, on an Ampere or newer GPU — the server
runs in bfloat16, which needs compute capability 8.0+. On Arch:
sudo pacman -S nvidia-container-toolkit && sudo nvidia-ctk runtime configure --runtime=docker && sudo systemctl restart docker - llama.cpp (
llama-serveron your PATH) — works anywhere, but CPU inference is slow on book-length documents
Sahaf classifies each PDF before converting, so under Marker 2 a digital book runs in
fast mode and never starts that server.
Quick Start
sahaf
Open http://localhost:8000 in your browser.
Uploads and converted files land in ~/.local/share/sahaf. To keep them elsewhere:
SAHAF_DATA_DIR=./sahaf-data sahaf
How It Works
- Upload — drag & drop a PDF or EPUB file
- Classify — PyMuPDF analyzes PDF type; EPUB chapters are counted
- Select range (optional) — pick specific pages or chapters to convert
- Convert — Marker processes PDF; ebooklib + markdownify handles EPUB
- Split (optional) — choose how many parts to split the output into
- Download — get a single
.mdor a ZIP with split parts, all images embedded inline
API
| Method | Path | Description |
|---|---|---|
POST |
/api/upload |
Upload PDF/EPUB, returns task_id |
GET |
/api/classify/{task_id} |
Detect PDF type + page count, or EPUB chapter count |
POST |
/api/convert/{task_id}?page_from=&page_to= |
Start conversion (optional page range) |
GET |
/api/status/{task_id} |
Poll conversion progress |
GET |
/api/result/{task_id} |
Get markdown + image list |
GET |
/api/download/{task_id} |
Download .md with embedded images |
GET |
/api/download/{task_id}/zip?parts=N |
Download ZIP with N split .md files |
GET |
/api/split-preview/{task_id}?parts=N |
Preview split structure before download |
Tech Stack
- Backend: FastAPI + Uvicorn
- PDF Classification: PyMuPDF
- PDF Conversion: Marker (marker-pdf 1.x or 2.x) + Surya OCR
- EPUB Conversion: ebooklib + markdownify
- Smart Splitting: Custom algorithm — heading/HR/paragraph boundary detection
- Frontend: Vanilla HTML/CSS/JS + marked.js
- i18n: TR/EN with client-side toggle
Requirements
- Python 3.10 – 3.13 (Marker's dependency stack pins
pillow<11, which has no 3.14 wheels) - 4-6GB RAM (when Marker models are loaded)
- GPU strongly recommended for scanned PDFs — those go through the OCR model; CPU inference is slow on book-length documents
- 4GB+ VRAM — below 8GB, Sahaf automatically switches Marker to memory settings that fit, so a 6GB card converts scanned books without any configuration. Set
FOUNDATION_CHUNK_SIZEor the*_BATCH_SIZEvariables yourself to override - Under Marker 2, digital PDFs run without the OCR server at all
- EPUB conversion is lightweight — no GPU needed, runs instantly
License
GPL-3.0
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 sahaf-0.2.3.tar.gz.
File metadata
- Download URL: sahaf-0.2.3.tar.gz
- Upload date:
- Size: 48.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6e2e044ad74c8d06ce644dd898ba07e9e3d1b2a10bb4b9c0d75079e560099765
|
|
| MD5 |
1108ebe34ad82c6cf21c22fc506182e5
|
|
| BLAKE2b-256 |
ecfbfe2e7e41042f3fc2c7769fdd94a37766c0025243de94dda19eb65af766b7
|
Provenance
The following attestation bundles were made for sahaf-0.2.3.tar.gz:
Publisher:
publish.yml on arikusi/sahaf
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
sahaf-0.2.3.tar.gz -
Subject digest:
6e2e044ad74c8d06ce644dd898ba07e9e3d1b2a10bb4b9c0d75079e560099765 - Sigstore transparency entry: 2325252218
- Sigstore integration time:
-
Permalink:
arikusi/sahaf@bfa7073884671e55205d54ed4befd099b921f343 -
Branch / Tag:
refs/tags/v0.2.3 - Owner: https://github.com/arikusi
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@bfa7073884671e55205d54ed4befd099b921f343 -
Trigger Event:
release
-
Statement type:
File details
Details for the file sahaf-0.2.3-py3-none-any.whl.
File metadata
- Download URL: sahaf-0.2.3-py3-none-any.whl
- Upload date:
- Size: 44.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
dcca4633a67d85a0c38612dabe319781ee3744e33575ceb9dee172e55ed2d5c4
|
|
| MD5 |
dab3545098393ed8e5269b4fbddfc3b6
|
|
| BLAKE2b-256 |
f39de4138d7b7712e3152890c7762cbce1c9cf5ae9c99d1823446eeb33aaeede
|
Provenance
The following attestation bundles were made for sahaf-0.2.3-py3-none-any.whl:
Publisher:
publish.yml on arikusi/sahaf
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
sahaf-0.2.3-py3-none-any.whl -
Subject digest:
dcca4633a67d85a0c38612dabe319781ee3744e33575ceb9dee172e55ed2d5c4 - Sigstore transparency entry: 2325252751
- Sigstore integration time:
-
Permalink:
arikusi/sahaf@bfa7073884671e55205d54ed4befd099b921f343 -
Branch / Tag:
refs/tags/v0.2.3 - Owner: https://github.com/arikusi
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@bfa7073884671e55205d54ed4befd099b921f343 -
Trigger Event:
release
-
Statement type: