A self-hosted web app for extracting Arabic (and English) text from images and PDFs using local AI models on your own GPU. No accounts, no cloud, no API keys — your computer is the backend, and you can share it with friends through an ngrok tunnel.
Highlights
| Model | Best for | Size |
|---|---|---|
| QARI-OCR v0.3 (NAMAA-Space/Qari-OCR-v0.3-VL-2B-Instruct) | Printed Arabic — books, documents, screenshots. Keeps diacritics (tashkeel). | ~4.4 GB |
| Arabic-English Handwritten OCR v3 (sherif1313/Arabic-English-handwritten-OCR-v3) | Handwriting — notes, letters, manuscripts (AR + EN). Runs 4-bit quantized. | ~7.5 GB |
| EasyOCR (ar+en) | Fast printed text — signs, labels, simple docs. | ~120 MB |
Models download automatically on first use (with a progress bar in the UI). Only one large model is kept in GPU memory at a time; switching models swaps them automatically — a 10 GB GPU (e.g. RTX 3080) runs everything.
Windows
start.bat
Linux / macOS
./start.sh
That's it. The first run creates a Python environment and installs everything (it detects whether you have an NVIDIA GPU and picks the right PyTorch build). Then open http://localhost:41720.
To stop the server, press Ctrl+C in its window, or run stop.bat
(Windows) / ./stop.sh (Linux / macOS) — they kill whatever is listening on
the app's port.
Optional: copy .env.example to .env to change the port or point history at
SQL Server. With no .env the app uses a local SQLite file — it works out of
the box.
Accepted files: any image Pillow can decode — JPG/JFIF, PNG, WEBP, HEIC/HEIF (iPhone photos), AVIF, BMP, TIFF, GIF, ICO… — plus PDF documents (each page is rendered and read separately). Files are validated by content, not extension, and phone photos are auto-rotated via EXIF.
The port 41720 was picked deliberately: it's not a well-known service port
and sits below the Windows ephemeral range (49152+), so nothing else should
ever claim it. Change it with OCR_PORT in .env if you ever need to.
allow-lan-access.bat once, as administrator (right-click
→ Run as administrator). It adds a firewall rule for TCP 41720.
Linux: open the port in your firewall if you use one (e.g. sudo ufw allow 41720/tcp).http://192.168.1.23:41720.winget install ngrok.ngrok on Windows) and add your
authtoken (ngrok config add-authtoken <token> — free account at ngrok.com).ngrok http 41720
https://xxxx.ngrok-free.app URL to your friends.Notes:
.env, database auto-created) stores job
history. If SQL Server is unreachable or not configured, the app automatically falls
back to SQLite (data/history.db) and keeps working. The active backend shows as a
badge in the UI header.data/uploads/<job-id>/ with generated thumbnails,
and removed when the job is deleted from history.| Effort | What it changes |
|---|---|
| Fast سريع | Lower image resolution fed to the model, shorter output budget |
| Balanced متوازن | Default resolution/tokens |
| Quality جودة | Highest resolution + longest output budget (slowest) |
All settings live in .env (copy from .env.example):
OCR_PORT, OCR_MSSQL_SERVER, OCR_MSSQL_DATABASE, OCR_MSSQL_USER,
OCR_MSSQL_PASSWORD, OCR_MSSQL_DRIVER. Everything is optional —
without SQL Server credentials, history is stored in SQLite.
.env is gitignored; never commit real credentials.
Interactive docs are served at /docs (FastAPI's built-in Swagger UI).
GET /api/models — available models + upload limitsPOST /api/jobs — multipart: files (images/PDFs), model, effort; header X-Client-IdGET /api/jobs/{id} — live status, progress, partial text, resultsDELETE /api/jobs/{id} — cancelGET /api/history, DELETE /api/history/{id} — per-client historyGET /api/healthExample:
curl -X POST http://localhost:41720/api/jobs \
-H "X-Client-Id: my-script" \
-F "[email protected]" -F "model=qari" -F "effort=balanced"
nvidia-smi should work), delete the .venv folder and start again so the
CUDA PyTorch build gets installed.git pull — the start scripts re-install
automatically when backend/requirements.txt changes.Issues and PRs are welcome — model suggestions especially (the model registry
in backend/ocr/ makes new engines easy to add: subclass
OCRModel, register it in backend/ocr/__init__.py).
CI runs a lint (ruff check backend) and an API smoke test
(ci/smoke_test.py) that works without a GPU.
A self-hosted web app for extracting Arabic (and English) text from images and PDFs using local AI models on your own GPU. No accounts, no cloud, no API keys — your computer is the backend, and you can share it with friends through an ngrok tunnel.
Highlights
| Model | Best for | Size |
|---|---|---|
| QARI-OCR v0.3 (NAMAA-Space/Qari-OCR-v0.3-VL-2B-Instruct) | Printed Arabic — books, documents, screenshots. Keeps diacritics (tashkeel). | ~4.4 GB |
| Arabic-English Handwritten OCR v3 (sherif1313/Arabic-English-handwritten-OCR-v3) | Handwriting — notes, letters, manuscripts (AR + EN). Runs 4-bit quantized. | ~7.5 GB |
| EasyOCR (ar+en) | Fast printed text — signs, labels, simple docs. | ~120 MB |
Models download automatically on first use (with a progress bar in the UI). Only one large model is kept in GPU memory at a time; switching models swaps them automatically — a 10 GB GPU (e.g. RTX 3080) runs everything.
Windows
start.bat
Linux / macOS
./start.sh
That's it. The first run creates a Python environment and installs everything (it detects whether you have an NVIDIA GPU and picks the right PyTorch build). Then open http://localhost:41720.
To stop the server, press Ctrl+C in its window, or run stop.bat
(Windows) / ./stop.sh (Linux / macOS) — they kill whatever is listening on
the app's port.
Optional: copy .env.example to .env to change the port or point history at
SQL Server. With no .env the app uses a local SQLite file — it works out of
the box.
Accepted files: any image Pillow can decode — JPG/JFIF, PNG, WEBP, HEIC/HEIF (iPhone photos), AVIF, BMP, TIFF, GIF, ICO… — plus PDF documents (each page is rendered and read separately). Files are validated by content, not extension, and phone photos are auto-rotated via EXIF.
The port 41720 was picked deliberately: it's not a well-known service port
and sits below the Windows ephemeral range (49152+), so nothing else should
ever claim it. Change it with OCR_PORT in .env if you ever need to.
allow-lan-access.bat once, as administrator (right-click
→ Run as administrator). It adds a firewall rule for TCP 41720.
Linux: open the port in your firewall if you use one (e.g. sudo ufw allow 41720/tcp).http://192.168.1.23:41720.winget install ngrok.ngrok on Windows) and add your
authtoken (ngrok config add-authtoken <token> — free account at ngrok.com).ngrok http 41720
https://xxxx.ngrok-free.app URL to your friends.Notes:
.env, database auto-created) stores job
history. If SQL Server is unreachable or not configured, the app automatically falls
back to SQLite (data/history.db) and keeps working. The active backend shows as a
badge in the UI header.data/uploads/<job-id>/ with generated thumbnails,
and removed when the job is deleted from history.| Effort | What it changes |
|---|---|
| Fast سريع | Lower image resolution fed to the model, shorter output budget |
| Balanced متوازن | Default resolution/tokens |
| Quality جودة | Highest resolution + longest output budget (slowest) |
All settings live in .env (copy from .env.example):
OCR_PORT, OCR_MSSQL_SERVER, OCR_MSSQL_DATABASE, OCR_MSSQL_USER,
OCR_MSSQL_PASSWORD, OCR_MSSQL_DRIVER. Everything is optional —
without SQL Server credentials, history is stored in SQLite.
.env is gitignored; never commit real credentials.
Interactive docs are served at /docs (FastAPI's built-in Swagger UI).
GET /api/models — available models + upload limitsPOST /api/jobs — multipart: files (images/PDFs), model, effort; header X-Client-IdGET /api/jobs/{id} — live status, progress, partial text, resultsDELETE /api/jobs/{id} — cancelGET /api/history, DELETE /api/history/{id} — per-client historyGET /api/healthExample:
curl -X POST http://localhost:41720/api/jobs \
-H "X-Client-Id: my-script" \
-F "[email protected]" -F "model=qari" -F "effort=balanced"
nvidia-smi should work), delete the .venv folder and start again so the
CUDA PyTorch build gets installed.git pull — the start scripts re-install
automatically when backend/requirements.txt changes.Issues and PRs are welcome — model suggestions especially (the model registry
in backend/ocr/ makes new engines easy to add: subclass
OCRModel, register it in backend/ocr/__init__.py).
CI runs a lint (ruff check backend) and an API smoke test
(ci/smoke_test.py) that works without a GPU.