Syncre commited on
Commit
985cdbe
·
verified ·
1 Parent(s): 6807740

Deploy Arabic Audio Reader worker

Browse files
.export-manifest.json CHANGED
@@ -3,13 +3,14 @@
3
  "Dockerfile": "eb8b1b840c8303bbcc2354a0e60896cf96593ac6828df6179877b19022e97c1c",
4
  "api/index.py": "b0fd5c43eadd241aea79131d12ea40fe032a97f06410ce1b607e81c45f33d6f2",
5
  "app/__init__.py": "7eb70257593da06f682a3ddda54a9d260d4fc514f645237f5ca74b08f8da61a6",
6
- "app/main.py": "19c6b13c9393871d88a0fef05d8fc0f19d14bef88205ffc229bce3e3a522f0e4",
7
  "docs/best-free-arabic-pdf-audio-stack.md": "08234106caacc0207f404b11023656cdc39525b28fedf526e97369edf926c48f",
8
- "docs/father-user-guide.md": "a05534fa8ecc4bee94704b6691947ac189f6767a95fd12eb65ae27c4ede1182f",
9
  "docs/huggingface-model-metadata.md": "4590229078c2048b184787e85e5a00dd687ef5fc90e8d8d0af32538b38363cc2",
10
  "docs/live-deployment-checklist.md": "7fd21a9316c1d018e2bec0620defcaaca2a690f109e51b5902c7d157244834ac",
11
- "docs/ocr-readability-benchmark.md": "f93f09729f5e8bd5f938afad9490b471452ca549d081ff7700161cc1dc961453",
12
- "docs/production-worker-architecture.md": "1264c16b83948385026aca0fab18e7963fa5056a178fa381380659352274b4ff",
 
13
  "docs/recommended-decision-card.json": "97e4607db20ac19cadc9b894d6406517bcb37f8ccc6ecbe6c0c41f5f2463398e",
14
  "docs/recommended-decision-card.md": "f69bbe66d7977a4877f934212862159495ea5a4547997e059f5c4e1b8d6d6cb9",
15
  "docs/recommended-free-stack.md": "6156deac80f5656ff4cd33d726061965b6e2a6fbc8db4ee4123b2b43e42aa40d",
@@ -30,7 +31,7 @@
30
  "scripts/arabic_qwen_ocr_extract.py": "485e9f3cdf2ced92c666b2f483d4aa37a65cb34052a4967beac7183d02c9ddcc",
31
  "scripts/audit_goal_readiness.py": "4fe8f36c4ef9b8e3c492dcef894cabc7afe98b5396e1c4bd15bdcfef3da733d7",
32
  "scripts/baseer_ocr_extract.py": "056ca9cc33591db804639030a16d9635931b720d0d499b444ed6e7d0a653605a",
33
- "scripts/benchmark_ocr.py": "b5ffb17845a7945b2a5c52e38bfabb6d82f3a8fbc8f2cdd5528843e09ad4deb4",
34
  "scripts/benchmark_voices.py": "705bdfb6260fe90a4a68d9d2455953ea7221d282bbf0cc1cc4fa32cd5ed10205",
35
  "scripts/check_deployment_readiness.py": "c371706cf94f807354a1a08f274dc17b1c02d68347b98f70b177b4c14f73bf17",
36
  "scripts/check_research_sources.py": "49bc5a15cddf040f134d21e042d064d64fce2235f2ff1dd01f6b9c69cdf0c3e0",
@@ -97,8 +98,8 @@
97
  "scripts/verify_site.py": "7a09c02f0063f913ac76f0793dcf359684cb6d210c3c851e86934527b277295d",
98
  "scripts/verify_voice.py": "d8fb7e473e47060b2d2f957c5c230807a205e95b1469eef9c32b76d2bc8585b2",
99
  "scripts/verify_worker.py": "73329f87852ce805ab7144df6faaab4e081099f7ebc9a2e66e93735ee7fa82cc",
100
- "static/app.js": "735d2ba288d8f96b7e99d4009d0ad5ef2db845562ea5defb5a6725b3c4dc6993",
101
- "static/index.html": "0877f04c78afa4078c92fea23a93ff2f97851a8c3d17dd005e3c5a56b8508288",
102
  "static/styles.css": "a45485cf99eaae8a46e57437a736ce1ebad2528dbf219c5bc79f124ec3c47164"
103
  },
104
  "source": "ArabicTranslator",
 
3
  "Dockerfile": "eb8b1b840c8303bbcc2354a0e60896cf96593ac6828df6179877b19022e97c1c",
4
  "api/index.py": "b0fd5c43eadd241aea79131d12ea40fe032a97f06410ce1b607e81c45f33d6f2",
5
  "app/__init__.py": "7eb70257593da06f682a3ddda54a9d260d4fc514f645237f5ca74b08f8da61a6",
6
+ "app/main.py": "0b280a15a15a82c22fc1b1ed85a08a54fb647325919a5e37100d3525e177417f",
7
  "docs/best-free-arabic-pdf-audio-stack.md": "08234106caacc0207f404b11023656cdc39525b28fedf526e97369edf926c48f",
8
+ "docs/father-user-guide.md": "2adb2a56c862df395cd77bf80501a937efc7ad72b30455250a97b4193f7003aa",
9
  "docs/huggingface-model-metadata.md": "4590229078c2048b184787e85e5a00dd687ef5fc90e8d8d0af32538b38363cc2",
10
  "docs/live-deployment-checklist.md": "7fd21a9316c1d018e2bec0620defcaaca2a690f109e51b5902c7d157244834ac",
11
+ "docs/ocr-and-voice-ranking.md": "092406ab8c494a160215adea223bbebd364bc7b546c63ed7db0e840ac9318412",
12
+ "docs/ocr-readability-benchmark.md": "12be9cc094e56454666ff71cc0bf9d14f39cbc7acb4c1a49abf10ffd2e4ef7df",
13
+ "docs/production-worker-architecture.md": "bfe77f5d718cbf829a30863277ed2e8af1ba227d13067700cdc75b02fe5245c4",
14
  "docs/recommended-decision-card.json": "97e4607db20ac19cadc9b894d6406517bcb37f8ccc6ecbe6c0c41f5f2463398e",
15
  "docs/recommended-decision-card.md": "f69bbe66d7977a4877f934212862159495ea5a4547997e059f5c4e1b8d6d6cb9",
16
  "docs/recommended-free-stack.md": "6156deac80f5656ff4cd33d726061965b6e2a6fbc8db4ee4123b2b43e42aa40d",
 
31
  "scripts/arabic_qwen_ocr_extract.py": "485e9f3cdf2ced92c666b2f483d4aa37a65cb34052a4967beac7183d02c9ddcc",
32
  "scripts/audit_goal_readiness.py": "4fe8f36c4ef9b8e3c492dcef894cabc7afe98b5396e1c4bd15bdcfef3da733d7",
33
  "scripts/baseer_ocr_extract.py": "056ca9cc33591db804639030a16d9635931b720d0d499b444ed6e7d0a653605a",
34
+ "scripts/benchmark_ocr.py": "48f4dfe4cdf2e8318abe968092cee7671989becaaea41e1b1498e2ac0261cccd",
35
  "scripts/benchmark_voices.py": "705bdfb6260fe90a4a68d9d2455953ea7221d282bbf0cc1cc4fa32cd5ed10205",
36
  "scripts/check_deployment_readiness.py": "c371706cf94f807354a1a08f274dc17b1c02d68347b98f70b177b4c14f73bf17",
37
  "scripts/check_research_sources.py": "49bc5a15cddf040f134d21e042d064d64fce2235f2ff1dd01f6b9c69cdf0c3e0",
 
98
  "scripts/verify_site.py": "7a09c02f0063f913ac76f0793dcf359684cb6d210c3c851e86934527b277295d",
99
  "scripts/verify_voice.py": "d8fb7e473e47060b2d2f957c5c230807a205e95b1469eef9c32b76d2bc8585b2",
100
  "scripts/verify_worker.py": "73329f87852ce805ab7144df6faaab4e081099f7ebc9a2e66e93735ee7fa82cc",
101
+ "static/app.js": "5876611f1c9ce7f8e7df5c17fa57c7ba0632292b521b09ff6679a965f4d4a83d",
102
+ "static/index.html": "2fb1610ca4b5faa964e167c6d5d3d788352244b5ea53549ffe69962fcea58c5a",
103
  "static/styles.css": "a45485cf99eaae8a46e57437a736ce1ebad2528dbf219c5bc79f124ec3c47164"
104
  },
105
  "source": "ArabicTranslator",
app/main.py CHANGED
@@ -119,6 +119,7 @@ OCR_ENGINE_CHOICES = {
119
  "paddleocr-vl",
120
  "surya",
121
  "tesseract",
 
122
  "auto",
123
  "best",
124
  }
@@ -283,10 +284,11 @@ CLOUD_VOICES = {
283
  LOCAL_VOICES = {
284
  "silma-local": {
285
  "id": "silma-local",
286
- "label": "SILMA Arabic - Recommended voice",
287
  "engine": "silma",
288
  "license": "Apache-2.0",
289
- "recommendedFor": "Best quality/speed balance for free Arabic audio on this worker",
 
290
  },
291
  "habibi-msa": {
292
  "id": "habibi-msa",
@@ -303,17 +305,19 @@ LOCAL_VOICES = {
303
  },
304
  "espeak-ar-clear": {
305
  "id": "espeak-ar-clear",
306
- "label": "Local Arabic Clear",
307
  "engine": "espeak-ng",
308
  "voice": "ar+f2",
309
  "license": "GPL-compatible open-source eSpeak NG",
310
- "recommendedFor": "Fast fallback when neural voices are too slow or unavailable",
 
311
  },
312
  "espeak-ar": {
313
  "id": "espeak-ar",
314
- "label": "Local Arabic",
315
  "engine": "espeak-ng",
316
  "voice": "ar",
 
317
  },
318
  "espeak-ar-male": {
319
  "id": "espeak-ar-male",
@@ -323,6 +327,78 @@ LOCAL_VOICES = {
323
  },
324
  }
325
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
326
 
327
  def get_voice_catalog() -> dict[str, object]:
328
  return {
@@ -1251,7 +1327,7 @@ def get_engine_status() -> dict[str, object]:
1251
  "easyocr": {"available": easyocr_ready, "label": "General Arabic OCR"},
1252
  "paddleocr": {
1253
  "available": paddleocr_ready,
1254
- "label": "PaddleOCR Arabic - faster, less readable",
1255
  "trainedFor": "Arabic printed text",
1256
  "model": "arabic_PP-OCRv5_mobile_rec",
1257
  "recommendedFor": "Usable fallback, but the 5-page benchmark produced more fragmented text than Tesseract",
@@ -1313,11 +1389,18 @@ def get_engine_status() -> dict[str, object]:
1313
  },
1314
  "tesseract": {
1315
  "available": bool(tesseract_path),
1316
- "label": "Tesseract Arabic - Recommended readable",
1317
  "trainedFor": "Arabic printed text",
1318
  "recommendedFor": "Best readable output on the 5-page Arabic benchmark; uses OCR_RENDER_ZOOM=2 and TESSERACT_PSM=4 by default",
1319
  },
 
 
 
 
 
 
1320
  "language": os.getenv("OCR_LANGUAGE", "ara"),
 
1321
  },
1322
  "readyForArabic": bool(
1323
  silma_installed or habibi_installed or supertonic_installed or (piper_path and piper_model_ready) or espeak_path
@@ -1338,6 +1421,7 @@ def get_engine_status() -> dict[str, object]:
1338
  "audioStorage": "worker-local retained downloads",
1339
  "benchmarkRule": "Run a representative 5-page Arabic sample before full-book audio.",
1340
  },
 
1341
  "voices": get_voice_catalog(),
1342
  "deployment": {
1343
  "platform": "vercel" if IS_VERCEL else "local",
@@ -2957,6 +3041,8 @@ def ocr_pdf_text(pdf_path: Path, job: Job) -> str:
2957
  engines = [ocr_pdf_text_with_surya, ocr_pdf_text_with_paddleocr, ocr_pdf_text_with_easyocr]
2958
  elif requested == "tesseract":
2959
  engines = []
 
 
2960
  else:
2961
  engines = [ocr_pdf_text_with_paddleocr, ocr_pdf_text_with_easyocr]
2962
 
 
119
  "paddleocr-vl",
120
  "surya",
121
  "tesseract",
122
+ "tesseract-fast",
123
  "auto",
124
  "best",
125
  }
 
284
  LOCAL_VOICES = {
285
  "silma-local": {
286
  "id": "silma-local",
287
+ "label": "1. SILMA Arabic - Most natural",
288
  "engine": "silma",
289
  "license": "Apache-2.0",
290
+ "recommendedFor": "Best Arabic accuracy/naturalness among voices generated from the winning OCR sample",
291
+ "rank": 1,
292
  },
293
  "habibi-msa": {
294
  "id": "habibi-msa",
 
305
  },
306
  "espeak-ar-clear": {
307
  "id": "espeak-ar-clear",
308
+ "label": "2. Local Arabic Clear - Fast fallback",
309
  "engine": "espeak-ng",
310
  "voice": "ar+f2",
311
  "license": "GPL-compatible open-source eSpeak NG",
312
+ "recommendedFor": "Best generated fallback when SILMA is too slow or unavailable",
313
+ "rank": 2,
314
  },
315
  "espeak-ar": {
316
  "id": "espeak-ar",
317
+ "label": "3. Local Arabic - Standard fallback",
318
  "engine": "espeak-ng",
319
  "voice": "ar",
320
+ "rank": 3,
321
  },
322
  "espeak-ar-male": {
323
  "id": "espeak-ar-male",
 
327
  },
328
  }
329
 
330
+ OCR_BENCHMARK_RANKING = [
331
+ {
332
+ "rank": 1,
333
+ "id": "tesseract",
334
+ "label": "1. Tesseract Arabic - Best readable",
335
+ "extraction": "tesseract@2x-psm4",
336
+ "settings": "OCR_RENDER_ZOOM=2 TESSERACT_PSM=4",
337
+ "quality": "good",
338
+ "qualityScore": 11919.05,
339
+ "seconds": 37.30,
340
+ "arabicWords": 3120,
341
+ "note": "Most readable 5-page benchmark output; default for full-book runs.",
342
+ },
343
+ {
344
+ "rank": 2,
345
+ "id": "tesseract-fast",
346
+ "label": "2. Tesseract Arabic - Faster readable",
347
+ "extraction": "tesseract@1.5x-psm6",
348
+ "settings": "OCR_RENDER_ZOOM=1.5 TESSERACT_PSM=6",
349
+ "quality": "good",
350
+ "qualityScore": 11510.50,
351
+ "seconds": 28.88,
352
+ "arabicWords": 3284,
353
+ "note": "Runner-up readable setting; faster, but slightly lower text-quality score.",
354
+ },
355
+ {
356
+ "rank": 3,
357
+ "id": "paddleocr",
358
+ "label": "3. PaddleOCR Arabic - Faster fallback",
359
+ "extraction": "paddleocr",
360
+ "settings": "OCR_ENGINE=paddleocr",
361
+ "quality": "warning",
362
+ "qualityScore": 8105.80,
363
+ "seconds": 106.91,
364
+ "arabicWords": 2251,
365
+ "note": "Usable Arabic OCR fallback, but more fragmented on this book sample.",
366
+ },
367
+ ]
368
+
369
+ VOICE_BENCHMARK_RANKING = [
370
+ {
371
+ "rank": 1,
372
+ "id": "silma-local",
373
+ "label": "1. SILMA Arabic - Most natural",
374
+ "engine": "silma",
375
+ "generated": True,
376
+ "elapsedSeconds": 277.34,
377
+ "sample": "outputs/ranked-voice-benchmark/silma-local.mp3",
378
+ "note": "Only generated neural Arabic voice in the benchmark; best starting point for actual Arabic naturalness.",
379
+ },
380
+ {
381
+ "rank": 2,
382
+ "id": "espeak-ar-clear",
383
+ "label": "2. Local Arabic Clear - Fast fallback",
384
+ "engine": "espeak-ng",
385
+ "generated": True,
386
+ "elapsedSeconds": 0.10,
387
+ "sample": "outputs/ranked-voice-benchmark/espeak-ar-clear.mp3",
388
+ "note": "Fastest clear fallback when the neural voice is too slow or unavailable.",
389
+ },
390
+ {
391
+ "rank": 3,
392
+ "id": "espeak-ar",
393
+ "label": "3. Local Arabic - Standard fallback",
394
+ "engine": "espeak-ng",
395
+ "generated": True,
396
+ "elapsedSeconds": 0.10,
397
+ "sample": "outputs/ranked-voice-benchmark/espeak-ar.mp3",
398
+ "note": "Standard eSpeak Arabic fallback; generated successfully but less natural than SILMA.",
399
+ },
400
+ ]
401
+
402
 
403
  def get_voice_catalog() -> dict[str, object]:
404
  return {
 
1327
  "easyocr": {"available": easyocr_ready, "label": "General Arabic OCR"},
1328
  "paddleocr": {
1329
  "available": paddleocr_ready,
1330
+ "label": "3. PaddleOCR Arabic - Faster fallback",
1331
  "trainedFor": "Arabic printed text",
1332
  "model": "arabic_PP-OCRv5_mobile_rec",
1333
  "recommendedFor": "Usable fallback, but the 5-page benchmark produced more fragmented text than Tesseract",
 
1389
  },
1390
  "tesseract": {
1391
  "available": bool(tesseract_path),
1392
+ "label": "1. Tesseract Arabic - Best readable",
1393
  "trainedFor": "Arabic printed text",
1394
  "recommendedFor": "Best readable output on the 5-page Arabic benchmark; uses OCR_RENDER_ZOOM=2 and TESSERACT_PSM=4 by default",
1395
  },
1396
+ "tesseractFast": {
1397
+ "available": bool(tesseract_path),
1398
+ "label": "2. Tesseract Arabic - Faster readable",
1399
+ "trainedFor": "Arabic printed text",
1400
+ "recommendedFor": "Second-best readable output on the 5-page benchmark; uses OCR_RENDER_ZOOM=1.5 and TESSERACT_PSM=6",
1401
+ },
1402
  "language": os.getenv("OCR_LANGUAGE", "ara"),
1403
+ "ranking": OCR_BENCHMARK_RANKING,
1404
  },
1405
  "readyForArabic": bool(
1406
  silma_installed or habibi_installed or supertonic_installed or (piper_path and piper_model_ready) or espeak_path
 
1421
  "audioStorage": "worker-local retained downloads",
1422
  "benchmarkRule": "Run a representative 5-page Arabic sample before full-book audio.",
1423
  },
1424
+ "voiceRanking": VOICE_BENCHMARK_RANKING,
1425
  "voices": get_voice_catalog(),
1426
  "deployment": {
1427
  "platform": "vercel" if IS_VERCEL else "local",
 
3041
  engines = [ocr_pdf_text_with_surya, ocr_pdf_text_with_paddleocr, ocr_pdf_text_with_easyocr]
3042
  elif requested == "tesseract":
3043
  engines = []
3044
+ elif requested == "tesseract-fast":
3045
+ return ocr_pdf_text_with_tesseract(pdf_path, job, render_zoom=1.5, psm=6)
3046
  else:
3047
  engines = [ocr_pdf_text_with_paddleocr, ocr_pdf_text_with_easyocr]
3048
 
docs/father-user-guide.md CHANGED
@@ -8,7 +8,7 @@ This guide is for the person using the website, not for setup.
8
  2. Enter the access code.
9
  3. Choose the Arabic PDF.
10
  4. Leave **Voice** on the best Arabic voice unless someone tells you to change it.
11
- 5. Leave **Text quality** on **Tesseract Arabic - Recommended readable** for a new scanned book.
12
  6. Leave **Pages** on **Quick test** first.
13
  7. Press **Create Audio**.
14
  8. Wait until the status says the audio is ready.
@@ -18,7 +18,7 @@ This guide is for the person using the website, not for setup.
18
 
19
  ## Which Text Quality To Choose
20
 
21
- Use **Tesseract Arabic - Recommended readable** first. It produced the most readable text in the 5-page Arabic OCR benchmark and is much faster than the comparison modes.
22
 
23
  Use **Arabic OCR comparison - slower** or **Maximum Arabic OCR - slower** on a short sample when the recommended option reads badly. They compare more OCR results and keep the cleanest text, but they can take much longer.
24
 
@@ -28,13 +28,15 @@ Use **KATIB Arabic OCR** when QARI is too slow or too heavy. It is also trained
28
 
29
  Use **Best scan test** only on a short sample. It is useful for deciding which OCR engine works best for one book, but it is too slow for most full books.
30
 
31
- Use **PaddleOCR Arabic - faster, less readable** only when Tesseract is unavailable or a short test sounds better for that book.
 
 
32
 
33
  Use **Tesseract Arabic fallback** when the other options are broken or when a benchmark says Tesseract worked best for that book.
34
 
35
  ## If Something Fails
36
 
37
- If the app says the text quality is poor, do not make full-book audio yet. Try **Tesseract Arabic - Recommended readable**, then **Arabic OCR comparison - slower**, then **Best scan test** on a short sample.
38
 
39
  If the first pages are title pages or blank pages, make a 5-page test PDF from better pages and test that before the full book.
40
 
@@ -42,7 +44,7 @@ If the website is running through a hosted worker, the job can keep running whil
42
 
43
  If the hosted website says downloadable cloud audio needs the worker, use **Read aloud** only as a temporary listening option. That browser voice can help you hear the text, but the normal **Download Audio** button comes back only after the worker is connected.
44
 
45
- If the audio sounds robotic but the text is correct, try another voice. **SILMA Arabic - Recommended voice** is the preferred free worker voice when it is available.
46
 
47
  ## Best Routine For A New Book
48
 
 
8
  2. Enter the access code.
9
  3. Choose the Arabic PDF.
10
  4. Leave **Voice** on the best Arabic voice unless someone tells you to change it.
11
+ 5. Leave **Text quality** on **1. Tesseract Arabic - Best readable** for a new scanned book.
12
  6. Leave **Pages** on **Quick test** first.
13
  7. Press **Create Audio**.
14
  8. Wait until the status says the audio is ready.
 
18
 
19
  ## Which Text Quality To Choose
20
 
21
+ Use **1. Tesseract Arabic - Best readable** first. It produced the most readable text in the 5-page Arabic OCR benchmark and is much faster than the comparison modes.
22
 
23
  Use **Arabic OCR comparison - slower** or **Maximum Arabic OCR - slower** on a short sample when the recommended option reads badly. They compare more OCR results and keep the cleanest text, but they can take much longer.
24
 
 
28
 
29
  Use **Best scan test** only on a short sample. It is useful for deciding which OCR engine works best for one book, but it is too slow for most full books.
30
 
31
+ Use **2. Tesseract Arabic - Faster readable** when speed matters and a short test still sounds correct.
32
+
33
+ Use **3. PaddleOCR Arabic - Faster fallback** only when Tesseract is unavailable or a short test sounds better for that book.
34
 
35
  Use **Tesseract Arabic fallback** when the other options are broken or when a benchmark says Tesseract worked best for that book.
36
 
37
  ## If Something Fails
38
 
39
+ If the app says the text quality is poor, do not make full-book audio yet. Try **1. Tesseract Arabic - Best readable**, then **Arabic OCR comparison - slower**, then **Best scan test** on a short sample.
40
 
41
  If the first pages are title pages or blank pages, make a 5-page test PDF from better pages and test that before the full book.
42
 
 
44
 
45
  If the hosted website says downloadable cloud audio needs the worker, use **Read aloud** only as a temporary listening option. That browser voice can help you hear the text, but the normal **Download Audio** button comes back only after the worker is connected.
46
 
47
+ If the audio sounds robotic but the text is correct, try another voice. **1. SILMA Arabic - Most natural** is the preferred free worker voice when it is available. **2. Local Arabic Clear - Fast fallback** is the next choice when SILMA is too slow.
48
 
49
  ## Best Routine For A New Book
50
 
docs/ocr-and-voice-ranking.md ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Arabic OCR and Voice Ranking
2
+
3
+ Last run: June 8, 2026.
4
+
5
+ PDF benchmark file: `test_pdfs/arabic-reader-5-page-test.pdf`
6
+
7
+ Voice sample text: `outputs/ranked-voice-ocr-sample.txt`
8
+
9
+ The voice samples were generated from the winning OCR output, so every voice read the same cleaned Arabic text.
10
+
11
+ ## OCR Ranking
12
+
13
+ | Rank | Website option | Extraction | Quality | Score | Time | Arabic words | Result |
14
+ | ---: | --- | --- | --- | ---: | ---: | ---: | --- |
15
+ | 1 | `1. Tesseract Arabic - Best readable` | `tesseract@2x-psm4` | good | 11919.05 | 37.30s | 3120 | Best readable output; use by default. |
16
+ | 2 | `2. Tesseract Arabic - Faster readable` | `tesseract@1.5x-psm6` | good | 11510.50 | 28.88s | 3284 | Runner-up; use when it sounds correct and speed matters. |
17
+ | 3 | `3. PaddleOCR Arabic - Faster fallback` | `paddleocr` | warning | 8105.80 | 106.91s | 2251 | Usable fallback, but more fragmented on this book sample. |
18
+
19
+ `EasyOCR` and `Auto fallback` both fell through to the same PaddleOCR output on this setup, so they are not ranked above the direct PaddleOCR option.
20
+
21
+ ## Voice Ranking
22
+
23
+ Generated files:
24
+
25
+ | Rank | Website option | Engine | Generated | Time | File | Result |
26
+ | ---: | --- | --- | --- | ---: | --- | --- |
27
+ | 1 | `1. SILMA Arabic - Most natural` | SILMA | yes | 277.34s | `outputs/ranked-voice-benchmark/silma-local.mp3` | Best Arabic-naturalness starting point because it is the only generated neural Arabic voice in this run. |
28
+ | 2 | `2. Local Arabic Clear - Fast fallback` | eSpeak NG | yes | 0.10s | `outputs/ranked-voice-benchmark/espeak-ar-clear.mp3` | Best fast fallback when SILMA is too slow or unavailable. |
29
+ | 3 | `3. Local Arabic - Standard fallback` | eSpeak NG | yes | 0.10s | `outputs/ranked-voice-benchmark/espeak-ar.mp3` | Standard fallback; generated successfully but is less natural than SILMA. |
30
+
31
+ `Habibi Arabic MSA` and `Supertonic Arabic CPU` were listed but did not generate audio in this local run, so they are unranked until installed and tested on the same OCR sample.
32
+
33
+ ## Recommendation
34
+
35
+ Use `1. Tesseract Arabic - Best readable` with `1. SILMA Arabic - Most natural` for the first full-book attempt. If SILMA is too slow, keep the OCR setting and switch the voice to `2. Local Arabic Clear - Fast fallback`.
docs/ocr-readability-benchmark.md CHANGED
@@ -18,6 +18,12 @@ TESSERACT_PSM=4
18
 
19
  This setting produced the most readable 5-page output while staying practical for full-book jobs.
20
 
 
 
 
 
 
 
21
  | OCR setting | Pages | Seconds | Quality | Score | Arabic words | Fragment line ratio | Extraction |
22
  | --- | ---: | ---: | --- | ---: | ---: | ---: | --- |
23
  | Tesseract 2x PSM 4 | 5 | 37.30 | good | 11919.05 | 3120 | 0.0433 | `tesseract@2x-psm4` |
@@ -39,4 +45,4 @@ The slower comparison modes were tested on the 1-page sample because the full 5-
39
 
40
  PaddleOCR is available and works, but on this book sample it returned many low-information lines and more fragmented Arabic text. It remains a fallback, not the recommendation.
41
 
42
- The live/default website setting should therefore be `Tesseract Arabic - Recommended readable`.
 
18
 
19
  This setting produced the most readable 5-page output while staying practical for full-book jobs.
20
 
21
+ Top 3 tested OCR settings:
22
+
23
+ 1. `Tesseract Arabic - Best readable`: `OCR_ENGINE=tesseract OCR_RENDER_ZOOM=2 TESSERACT_PSM=4`
24
+ 2. `Tesseract Arabic - Faster readable`: `OCR_ENGINE=tesseract-fast OCR_RENDER_ZOOM=1.5 TESSERACT_PSM=6`
25
+ 3. `PaddleOCR Arabic - Faster fallback`: `OCR_ENGINE=paddleocr`
26
+
27
  | OCR setting | Pages | Seconds | Quality | Score | Arabic words | Fragment line ratio | Extraction |
28
  | --- | ---: | ---: | --- | ---: | ---: | ---: | --- |
29
  | Tesseract 2x PSM 4 | 5 | 37.30 | good | 11919.05 | 3120 | 0.0433 | `tesseract@2x-psm4` |
 
45
 
46
  PaddleOCR is available and works, but on this book sample it returned many low-information lines and more fragmented Arabic text. It remains a fallback, not the recommendation.
47
 
48
+ The live/default website setting should therefore be `1. Tesseract Arabic - Best readable`.
docs/production-worker-architecture.md CHANGED
@@ -93,7 +93,7 @@ The worker bundle also includes setup scripts for optional heavy paths, but they
93
 
94
  `Dockerfile.worker` exposes `INSTALL_QARI_OCR=1`, `INSTALL_KATIB_OCR=1`, `INSTALL_ARABIC_QWEN_OCR=1`, `INSTALL_BASEER_OCR=1`, `INSTALL_PADDLEOCR_VL=1`, and `INSTALL_SUPERTONIC=1` build args so stronger workers can install QARI-OCR, KATIB, Arabic-Qwen, Baseer, PaddleOCR-VL, and the optional Supertonic CPU voice without editing the Dockerfile. This keeps the free CPU image practical while making the higher-quality free OCR paths and fast voice comparison path deployable.
95
 
96
- QARI-OCR 0.4 is the strongest Arabic-native OCR upgrade to test for a stronger worker. It is a 4B VLM fine-tuned for Islamic books and Arabic manuscripts, so keep it out of the default free CPU family-site worker unless a short Arabic-book benchmark proves it improves the actual pages and the worker has enough RAM/GPU. KATIB 0.8B and Arabic-Qwen3.5-OCR-v4 are the smaller Arabic-trained OCR upgrades to try when QARI is too heavy. If the worker is too small for QARI, set `QARI_OCR_MODEL=NAMAA-Space/Qari-OCR-v0.3-VL-2B-Instruct` to test the lighter older QARI path. PaddleOCR-VL-1.6 remains the main general document-parser upgrade to watch. In the website, start with `Tesseract Arabic - Recommended readable`; use `Arabic OCR comparison - slower` or `Maximum Arabic OCR - slower` only for short tests or difficult pages.
97
 
98
  The repo includes optional KATIB, Arabic-Qwen, QARI-OCR, and PaddleOCR-VL sidecar scripts for this evaluation path:
99
 
 
93
 
94
  `Dockerfile.worker` exposes `INSTALL_QARI_OCR=1`, `INSTALL_KATIB_OCR=1`, `INSTALL_ARABIC_QWEN_OCR=1`, `INSTALL_BASEER_OCR=1`, `INSTALL_PADDLEOCR_VL=1`, and `INSTALL_SUPERTONIC=1` build args so stronger workers can install QARI-OCR, KATIB, Arabic-Qwen, Baseer, PaddleOCR-VL, and the optional Supertonic CPU voice without editing the Dockerfile. This keeps the free CPU image practical while making the higher-quality free OCR paths and fast voice comparison path deployable.
95
 
96
+ QARI-OCR 0.4 is the strongest Arabic-native OCR upgrade to test for a stronger worker. It is a 4B VLM fine-tuned for Islamic books and Arabic manuscripts, so keep it out of the default free CPU family-site worker unless a short Arabic-book benchmark proves it improves the actual pages and the worker has enough RAM/GPU. KATIB 0.8B and Arabic-Qwen3.5-OCR-v4 are the smaller Arabic-trained OCR upgrades to try when QARI is too heavy. If the worker is too small for QARI, set `QARI_OCR_MODEL=NAMAA-Space/Qari-OCR-v0.3-VL-2B-Instruct` to test the lighter older QARI path. PaddleOCR-VL-1.6 remains the main general document-parser upgrade to watch. In the website, start with `1. Tesseract Arabic - Best readable`; use `Arabic OCR comparison - slower` or `Maximum Arabic OCR - slower` only for short tests or difficult pages.
97
 
98
  The repo includes optional KATIB, Arabic-Qwen, QARI-OCR, and PaddleOCR-VL sidecar scripts for this evaluation path:
99
 
scripts/benchmark_ocr.py CHANGED
@@ -132,6 +132,16 @@ def recommendation_for_extraction(extraction: str | None) -> dict[str, Any] | No
132
  env["OCR_RENDER_ZOOM"] = zoom
133
  if psm and engine == "tesseract":
134
  env["TESSERACT_PSM"] = psm
 
 
 
 
 
 
 
 
 
 
135
  if engine == "tesseract":
136
  notes.append("Confirm Tesseract Arabic data is installed before the full run.")
137
  elif engine == "easyocr":
@@ -169,7 +179,11 @@ def benchmark_engine(pdf_path: Path, engine: str) -> dict[str, Any]:
169
  job = main.Job(id="dry-run", filename=pdf_path.name, ocr_engine=engine)
170
  started = time.perf_counter()
171
  try:
172
- text = main.extract_pdf_text(pdf_path, job)
 
 
 
 
173
  elapsed = round(time.perf_counter() - started, 2)
174
  result = {
175
  "engine": engine,
@@ -179,7 +193,18 @@ def benchmark_engine(pdf_path: Path, engine: str) -> dict[str, Any]:
179
  "extraction": job.extraction,
180
  **text_metrics(text),
181
  }
182
- result["recommendation"] = recommendation_for_extraction(job.extraction)
 
 
 
 
 
 
 
 
 
 
 
183
  return result
184
  except Exception as exc:
185
  elapsed = round(time.perf_counter() - started, 2)
@@ -244,6 +269,7 @@ def main_cli() -> None:
244
  "paddleocr-vl",
245
  "surya",
246
  "tesseract",
 
247
  "auto",
248
  "best",
249
  ],
 
132
  env["OCR_RENDER_ZOOM"] = zoom
133
  if psm and engine == "tesseract":
134
  env["TESSERACT_PSM"] = psm
135
+ if engine == "tesseract-fast":
136
+ env["OCR_ENGINE"] = engine
137
+ env["OCR_RENDER_ZOOM"] = zoom or "1.5"
138
+ env["TESSERACT_PSM"] = psm or "6"
139
+ notes.append("Use this runner-up setting when speed matters and its sample text still sounds correct.")
140
+ return {
141
+ "summary": "For the full book, use OCR_ENGINE=tesseract-fast OCR_RENDER_ZOOM=1.5 TESSERACT_PSM=6.",
142
+ "env": env,
143
+ "notes": notes,
144
+ }
145
  if engine == "tesseract":
146
  notes.append("Confirm Tesseract Arabic data is installed before the full run.")
147
  elif engine == "easyocr":
 
179
  job = main.Job(id="dry-run", filename=pdf_path.name, ocr_engine=engine)
180
  started = time.perf_counter()
181
  try:
182
+ if engine == "tesseract-fast":
183
+ text = main.ocr_pdf_text_with_tesseract(pdf_path, job, render_zoom=1.5, psm=6)
184
+ job.ocr_engine = engine
185
+ else:
186
+ text = main.extract_pdf_text(pdf_path, job)
187
  elapsed = round(time.perf_counter() - started, 2)
188
  result = {
189
  "engine": engine,
 
193
  "extraction": job.extraction,
194
  **text_metrics(text),
195
  }
196
+ if engine == "tesseract-fast":
197
+ result["recommendation"] = {
198
+ "summary": "For the full book, use OCR_ENGINE=tesseract-fast.",
199
+ "env": {
200
+ "OCR_ENGINE": "tesseract-fast",
201
+ "OCR_RENDER_ZOOM": "1.5",
202
+ "TESSERACT_PSM": "6",
203
+ },
204
+ "notes": ["Use this runner-up setting when speed matters and its sample text still sounds correct."],
205
+ }
206
+ else:
207
+ result["recommendation"] = recommendation_for_extraction(job.extraction)
208
  return result
209
  except Exception as exc:
210
  elapsed = round(time.perf_counter() - started, 2)
 
269
  "paddleocr-vl",
270
  "surya",
271
  "tesseract",
272
+ "tesseract-fast",
273
  "auto",
274
  "best",
275
  ],
static/app.js CHANGED
@@ -64,9 +64,9 @@ const defaultVoiceCatalog = {
64
  { id: "silma-tts", label: "SILMA Arabic" },
65
  ],
66
  local: [
67
- { id: "silma-local", label: "SILMA Arabic - Recommended voice" },
68
- { id: "espeak-ar-clear", label: "Local Arabic Clear" },
69
- { id: "espeak-ar", label: "Local Arabic" },
70
  { id: "espeak-ar-male", label: "Local Arabic Low" },
71
  ],
72
  };
@@ -83,6 +83,9 @@ let browserSpeechText = "";
83
  let browserSpeechSourceName = "";
84
 
85
  const ocrModeLabels = {
 
 
 
86
  "arabic-max": "Maximum Arabic OCR - slower",
87
  arabic: "Arabic OCR comparison - slower",
88
  "qari-ocr": "QARI Arabic books (best)",
@@ -91,12 +94,10 @@ const ocrModeLabels = {
91
  "arabic-qwen-ocr": "Arabic-Qwen OCR",
92
  "arabic-glm-ocr": "Arabic-GLM OCR v2",
93
  "baseer-ocr": "Baseer Arabic OCR",
94
- paddleocr: "PaddleOCR Arabic - faster, less readable",
95
  "paddleocr-vl": "PaddleOCR-VL heavy",
96
  best: "Best scan test",
97
  surya: "Surya heavy OCR",
98
  easyocr: "General Arabic OCR",
99
- tesseract: "Tesseract Arabic - Recommended readable",
100
  auto: "Auto fallback",
101
  };
102
 
@@ -336,7 +337,9 @@ async function loadHealth() {
336
  : engines.ocr?.preferred === "surya"
337
  ? "Surya heavy OCR is ready"
338
  : engines.ocr?.preferred === "tesseract"
339
- ? "Recommended readable Tesseract Arabic OCR is ready"
 
 
340
  : engines.ocr?.preferred === "best"
341
  ? "Best Arabic OCR test mode is ready"
342
  : engines.ocr?.preferred
@@ -1415,10 +1418,13 @@ function describeOcrMode() {
1415
  engineNotice.textContent = "Best scan test selected. Use this on a short sample, then run the winning engine for the full book.";
1416
  engineNotice.classList.remove("warning");
1417
  } else if (ocrModeSelect.value === "paddleocr") {
1418
- engineNotice.textContent = "PaddleOCR Arabic selected. It works, but the 5-page benchmark produced more fragmented text than Tesseract.";
1419
  engineNotice.classList.remove("warning");
1420
  } else if (ocrModeSelect.value === "tesseract") {
1421
- engineNotice.textContent = "Tesseract Arabic selected. This is the recommended readable option from the 5-page OCR benchmark.";
 
 
 
1422
  engineNotice.classList.remove("warning");
1423
  } else if (ocrModeSelect.value === "paddleocr-vl") {
1424
  engineNotice.textContent = "PaddleOCR-VL selected. Use this only on a short sample or strong worker; it is much heavier than normal Arabic OCR.";
@@ -1649,8 +1655,8 @@ function showQualityHint(quality) {
1649
  }
1650
  const reasons = quality.reasons?.length ? ` ${quality.reasons.join("; ")}.` : "";
1651
  const action = quality.quality === "poor"
1652
- ? "Try Tesseract Arabic - Recommended readable, Best scan test, or another OCR mode before creating audio."
1653
- : "Listen to a short sample before running the full book. If it sounds wrong, try Tesseract Arabic - Recommended readable, Best scan test, or another OCR mode.";
1654
  qualityHint.textContent = `Text needs checking.${reasons} ${action}`;
1655
  qualityHint.classList.remove("hidden");
1656
  qualityHint.classList.toggle("poor", quality.quality === "poor");
 
64
  { id: "silma-tts", label: "SILMA Arabic" },
65
  ],
66
  local: [
67
+ { id: "silma-local", label: "1. SILMA Arabic - Most natural" },
68
+ { id: "espeak-ar-clear", label: "2. Local Arabic Clear - Fast fallback" },
69
+ { id: "espeak-ar", label: "3. Local Arabic - Standard fallback" },
70
  { id: "espeak-ar-male", label: "Local Arabic Low" },
71
  ],
72
  };
 
83
  let browserSpeechSourceName = "";
84
 
85
  const ocrModeLabels = {
86
+ tesseract: "1. Tesseract Arabic - Best readable",
87
+ "tesseract-fast": "2. Tesseract Arabic - Faster readable",
88
+ paddleocr: "3. PaddleOCR Arabic - Faster fallback",
89
  "arabic-max": "Maximum Arabic OCR - slower",
90
  arabic: "Arabic OCR comparison - slower",
91
  "qari-ocr": "QARI Arabic books (best)",
 
94
  "arabic-qwen-ocr": "Arabic-Qwen OCR",
95
  "arabic-glm-ocr": "Arabic-GLM OCR v2",
96
  "baseer-ocr": "Baseer Arabic OCR",
 
97
  "paddleocr-vl": "PaddleOCR-VL heavy",
98
  best: "Best scan test",
99
  surya: "Surya heavy OCR",
100
  easyocr: "General Arabic OCR",
 
101
  auto: "Auto fallback",
102
  };
103
 
 
337
  : engines.ocr?.preferred === "surya"
338
  ? "Surya heavy OCR is ready"
339
  : engines.ocr?.preferred === "tesseract"
340
+ ? "Rank 1 readable Tesseract Arabic OCR is ready"
341
+ : engines.ocr?.preferred === "tesseract-fast"
342
+ ? "Rank 2 faster Tesseract Arabic OCR is ready"
343
  : engines.ocr?.preferred === "best"
344
  ? "Best Arabic OCR test mode is ready"
345
  : engines.ocr?.preferred
 
1418
  engineNotice.textContent = "Best scan test selected. Use this on a short sample, then run the winning engine for the full book.";
1419
  engineNotice.classList.remove("warning");
1420
  } else if (ocrModeSelect.value === "paddleocr") {
1421
+ engineNotice.textContent = "Rank 3 PaddleOCR Arabic selected. It works, but the 5-page benchmark produced more fragmented text than Tesseract.";
1422
  engineNotice.classList.remove("warning");
1423
  } else if (ocrModeSelect.value === "tesseract") {
1424
+ engineNotice.textContent = "Rank 1 Tesseract Arabic selected. This produced the best readable text in the 5-page OCR benchmark.";
1425
+ engineNotice.classList.remove("warning");
1426
+ } else if (ocrModeSelect.value === "tesseract-fast") {
1427
+ engineNotice.textContent = "Rank 2 Tesseract Arabic selected. This was the faster readable runner-up in the 5-page OCR benchmark.";
1428
  engineNotice.classList.remove("warning");
1429
  } else if (ocrModeSelect.value === "paddleocr-vl") {
1430
  engineNotice.textContent = "PaddleOCR-VL selected. Use this only on a short sample or strong worker; it is much heavier than normal Arabic OCR.";
 
1655
  }
1656
  const reasons = quality.reasons?.length ? ` ${quality.reasons.join("; ")}.` : "";
1657
  const action = quality.quality === "poor"
1658
+ ? "Try 1. Tesseract Arabic - Best readable, Best scan test, or another OCR mode before creating audio."
1659
+ : "Listen to a short sample before running the full book. If it sounds wrong, try 1. Tesseract Arabic - Best readable, Best scan test, or another OCR mode.";
1660
  qualityHint.textContent = `Text needs checking.${reasons} ${action}`;
1661
  qualityHint.classList.remove("hidden");
1662
  qualityHint.classList.toggle("poor", quality.quality === "poor");
static/index.html CHANGED
@@ -67,7 +67,9 @@
67
  <div class="field-group">
68
  <label for="ocrModeSelect">Text quality</label>
69
  <select id="ocrModeSelect" name="ocrMode">
70
- <option value="tesseract">Tesseract Arabic - Recommended readable</option>
 
 
71
  <option value="arabic">Arabic OCR comparison - slower</option>
72
  <option value="arabic-max">Maximum Arabic OCR - slower</option>
73
  <option value="qari-ocr">QARI Arabic books (best)</option>
@@ -77,7 +79,6 @@
77
  <option value="arabic-glm-ocr">Arabic-GLM OCR v2</option>
78
  <option value="baseer-ocr">Baseer Arabic OCR</option>
79
  <option value="best">Best scan test</option>
80
- <option value="paddleocr">PaddleOCR Arabic - faster, less readable</option>
81
  <option value="paddleocr-vl">PaddleOCR-VL heavy</option>
82
  <option value="surya">Surya heavy OCR</option>
83
  <option value="easyocr">General Arabic OCR</option>
 
67
  <div class="field-group">
68
  <label for="ocrModeSelect">Text quality</label>
69
  <select id="ocrModeSelect" name="ocrMode">
70
+ <option value="tesseract">1. Tesseract Arabic - Best readable</option>
71
+ <option value="tesseract-fast">2. Tesseract Arabic - Faster readable</option>
72
+ <option value="paddleocr">3. PaddleOCR Arabic - Faster fallback</option>
73
  <option value="arabic">Arabic OCR comparison - slower</option>
74
  <option value="arabic-max">Maximum Arabic OCR - slower</option>
75
  <option value="qari-ocr">QARI Arabic books (best)</option>
 
79
  <option value="arabic-glm-ocr">Arabic-GLM OCR v2</option>
80
  <option value="baseer-ocr">Baseer Arabic OCR</option>
81
  <option value="best">Best scan test</option>
 
82
  <option value="paddleocr-vl">PaddleOCR-VL heavy</option>
83
  <option value="surya">Surya heavy OCR</option>
84
  <option value="easyocr">General Arabic OCR</option>