Metadata-Version: 2.4
Name: sahaf
Version: 0.2.2
Summary: PDF to Markdown converter with OCR support — local, fast, accurate.
License-Expression: GPL-3.0-or-later
Project-URL: Homepage, https://github.com/arikusi/sahaf
Project-URL: Repository, https://github.com/arikusi/sahaf
Project-URL: Issues, https://github.com/arikusi/sahaf/issues
Project-URL: Changelog, https://github.com/arikusi/sahaf/blob/main/CHANGELOG.md
Keywords: pdf,epub,markdown,ocr,converter,marker,surya
Classifier: Development Status :: 3 - Alpha
Classifier: Framework :: FastAPI
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Science/Research
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Text Processing :: Markup :: Markdown
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Requires-Python: <3.14,>=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: fastapi>=0.115.0
Requires-Dist: uvicorn[standard]>=0.30.0
Requires-Dist: python-multipart>=0.0.9
Requires-Dist: pymupdf>=1.24.0
Requires-Dist: marker-pdf<3.0.0,>=1.10.0
Requires-Dist: ebooklib>=0.18
Requires-Dist: markdownify>=0.11.0
Provides-Extra: dev
Requires-Dist: pytest>=8.0; extra == "dev"
Requires-Dist: pytest-cov>=5.0; extra == "dev"
Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
Requires-Dist: httpx>=0.27; extra == "dev"
Requires-Dist: ruff>=0.4; extra == "dev"
Dynamic: license-file

# Sahaf

[![CI](https://github.com/arikusi/sahaf/actions/workflows/ci.yml/badge.svg)](https://github.com/arikusi/sahaf/actions/workflows/ci.yml)
[![PyPI](https://img.shields.io/pypi/v/sahaf)](https://pypi.org/project/sahaf/)
[![Downloads](https://img.shields.io/pypi/dm/sahaf)](https://pypi.org/project/sahaf/)
[![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0)

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.

<p align="center">
  <video src="https://github.com/user-attachments/assets/76b2484c-b69b-4cf2-8436-1ef2ae3cef20" width="480" autoplay loop muted>
  </video>
</p>

## 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

```bash
pip install sahaf
```

Or from source:

```bash
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**

```bash
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**

```bash
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-server` on 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

```bash
sahaf
```

Open `http://localhost:8000` in your browser.

Uploads and converted files land in `~/.local/share/sahaf`. To keep them elsewhere:

```bash
SAHAF_DATA_DIR=./sahaf-data sahaf
```

## How It Works

1. **Upload** — drag & drop a PDF or EPUB file
2. **Classify** — PyMuPDF analyzes PDF type; EPUB chapters are counted
3. **Select range** *(optional)* — pick specific pages or chapters to convert
4. **Convert** — Marker processes PDF; ebooklib + markdownify handles EPUB
5. **Split** *(optional)* — choose how many parts to split the output into
6. **Download** — get a single `.md` or 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
- **More than 6GB VRAM for Marker 1 on GPU** — its model set fills a 6GB card before inference starts and dies with a CUDA OOM. On a smaller card run `TORCH_DEVICE=cpu sahaf`
- 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
