Spaces:
Runtime error
Runtime error
Update app.py
#1
by Asem75 - opened
app.py
CHANGED
|
@@ -1,100 +1,218 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
import os
|
| 2 |
-
import json
|
| 3 |
import re
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
os.
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 34 |
return text
|
| 35 |
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
def
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
# -*- coding: utf-8 -*-
|
| 3 |
+
"""
|
| 4 |
+
Arabic Golden Engine – Pro Lite Edition (Stable)
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
import os
|
|
|
|
| 8 |
import re
|
| 9 |
+
import queue
|
| 10 |
+
import threading
|
| 11 |
+
import time
|
| 12 |
+
import logging
|
| 13 |
+
import tempfile
|
| 14 |
+
import shutil
|
| 15 |
+
import json
|
| 16 |
+
import pandas as pd
|
| 17 |
+
import gradio as gr
|
| 18 |
+
from huggingface_hub import HfApi
|
| 19 |
+
from fastapi import FastAPI, Request
|
| 20 |
+
from pyarabic.araby import strip_tashkeel, normalize_ligature
|
| 21 |
+
|
| 22 |
+
os.environ["TOKENIZERS_PARALLELISM"] = "false"
|
| 23 |
+
|
| 24 |
+
# ========== إعدادات المستودع ==========
|
| 25 |
+
REPO_ID = "Asem75/aiocr_asistant"
|
| 26 |
+
FINAL_DIR = "Final_Arabic_Files"
|
| 27 |
+
EXCEL_DIR = "editor_sync"
|
| 28 |
+
|
| 29 |
+
LOCAL_FREE_TXT_DIR = "./free_processed_txt_queue"
|
| 30 |
+
LOCAL_FREE_XLS_DIR = "./free_processed_xls_queue"
|
| 31 |
+
os.makedirs(LOCAL_FREE_TXT_DIR, exist_ok=True)
|
| 32 |
+
os.makedirs(LOCAL_FREE_XLS_DIR, exist_ok=True)
|
| 33 |
+
|
| 34 |
+
HF_TOKEN = os.environ.get("AIOCR_KEY")
|
| 35 |
+
api = HfApi(token=HF_TOKEN.strip().split()[0]) if HF_TOKEN else None
|
| 36 |
+
|
| 37 |
+
logging.basicConfig(level=logging.INFO)
|
| 38 |
+
logger = logging.getLogger("Golden_Engine")
|
| 39 |
+
|
| 40 |
+
# ========== طابور المعالجة ==========
|
| 41 |
+
ARABIC_ENGINE_QUEUE = queue.Queue()
|
| 42 |
+
latest_processed_text = ""
|
| 43 |
+
|
| 44 |
+
# ========== نسخة Pro Lite — دوال التصحيح ==========
|
| 45 |
+
COMMON_AR = set("""
|
| 46 |
+
الله هذا ذلك لكن لأن فإن كان تكون الذين الذي التي عندما حيث بينما أيضا جدا فقط بين حتى عليه عليها لديهم لديه منها منه فيها فيه منهم نحن أنت هو هي هم هن ثم بعد قبل خلال بسبب بدون حول تحت فوق ضد عبر نحو عند الى إلى على عن مع من ما ماذا لماذا كيف متى أين هنا هناك نعم لا ربما قد لقد إن أن إذا إذ
|
| 47 |
+
""".split())
|
| 48 |
+
|
| 49 |
+
PHONETIC_MAP = {
|
| 50 |
+
"هاذا": "هذا",
|
| 51 |
+
"هده": "هذه",
|
| 52 |
+
"دالك": "ذلك",
|
| 53 |
+
"لأنو": "لأنه",
|
| 54 |
+
"علشان": "لأن",
|
| 55 |
+
}
|
| 56 |
+
|
| 57 |
+
VISUAL_MAP = {
|
| 58 |
+
"اﻟ": "ال",
|
| 59 |
+
"ى": "ي",
|
| 60 |
+
"ئ": "ي",
|
| 61 |
+
"ؤ": "و",
|
| 62 |
+
"ة": "ه",
|
| 63 |
+
"اللا": "لا",
|
| 64 |
+
}
|
| 65 |
+
|
| 66 |
+
def pro_fix_word(word):
|
| 67 |
+
base = strip_tashkeel(normalize_ligature(word))
|
| 68 |
+
|
| 69 |
+
if base in COMMON_AR:
|
| 70 |
+
return base
|
| 71 |
+
|
| 72 |
+
if base in PHONETIC_MAP:
|
| 73 |
+
return PHONETIC_MAP[base]
|
| 74 |
+
|
| 75 |
+
for k, v in VISUAL_MAP.items():
|
| 76 |
+
if k in base:
|
| 77 |
+
return base.replace(k, v)
|
| 78 |
+
|
| 79 |
+
base2 = re.sub(r"(.)\1{2,}", r"\1\1", base)
|
| 80 |
+
return base2
|
| 81 |
+
|
| 82 |
+
def pro_lite_disambiguate(text):
|
| 83 |
+
words = text.split()
|
| 84 |
+
fixed = [pro_fix_word(w) for w in words]
|
| 85 |
+
return " ".join(fixed)
|
| 86 |
+
|
| 87 |
+
# ========== تنظيف + ترقيم + تنسيق ==========
|
| 88 |
+
def mechanical_clean(text: str) -> str:
|
| 89 |
+
text = re.sub(r"--- صفحة \d+ \(OCR\) ---", "", text)
|
| 90 |
+
text = re.sub(r"Page \d+", "", text)
|
| 91 |
+
text = re.sub(r"\s+", " ", text)
|
| 92 |
+
return text.strip()
|
| 93 |
+
|
| 94 |
+
def insert_punctuation(text: str) -> str:
|
| 95 |
+
text = re.sub(r'\b(أولاً|ثانياً|ثالثاً|ملاحظة|تنبيه|مثال)\b\s*', r'\1: ', text)
|
| 96 |
+
return text
|
| 97 |
+
|
| 98 |
+
def final_format(text: str) -> str:
|
| 99 |
+
lines = []
|
| 100 |
+
for chunk in text.split("\n"):
|
| 101 |
+
chunk = chunk.strip()
|
| 102 |
+
if not chunk:
|
| 103 |
+
continue
|
| 104 |
+
if not chunk.endswith((".", "،", "؟", "!", "؛", ":")):
|
| 105 |
+
chunk += "."
|
| 106 |
+
lines.append(chunk)
|
| 107 |
+
return "\n\n".join(lines)
|
| 108 |
+
|
| 109 |
+
def process_ocr_text(raw_text: str) -> str:
|
| 110 |
+
if not raw_text.strip():
|
| 111 |
+
return ""
|
| 112 |
+
text = mechanical_clean(raw_text)
|
| 113 |
+
text = pro_lite_disambiguate(text)
|
| 114 |
+
text = insert_punctuation(text)
|
| 115 |
+
text = final_format(text)
|
| 116 |
return text
|
| 117 |
|
| 118 |
+
# ========== Excel ==========
|
| 119 |
+
def generate_excel_grid(text: str) -> str:
|
| 120 |
+
words = [w.strip() for w in text.split() if w.strip()]
|
| 121 |
+
df = pd.DataFrame(words, columns=["الكلمات المصححة"])
|
| 122 |
+
tmp = tempfile.NamedTemporaryFile(delete=False, suffix=".xlsx")
|
| 123 |
+
df.to_excel(tmp.name, index=False, engine='openpyxl')
|
| 124 |
+
return tmp.name
|
| 125 |
+
|
| 126 |
+
# ========== حلقة المعالجة ==========
|
| 127 |
+
def in_memory_receiver_loop():
|
| 128 |
+
global latest_processed_text
|
| 129 |
+
logger.info("🚀 Pro Lite Engine Ready...")
|
| 130 |
+
|
| 131 |
+
while True:
|
| 132 |
+
try:
|
| 133 |
+
try:
|
| 134 |
+
raw_text, metadata = ARABIC_ENGINE_QUEUE.get(timeout=1)
|
| 135 |
+
except queue.Empty:
|
| 136 |
+
continue
|
| 137 |
+
|
| 138 |
+
source = metadata.get("source", "unknown")
|
| 139 |
+
rank = metadata.get("rank", "free")
|
| 140 |
+
is_vip = (rank == "vip")
|
| 141 |
+
|
| 142 |
+
clean_name = os.path.splitext(os.path.basename(source))[0]
|
| 143 |
+
txt_filename = f"{clean_name}.txt"
|
| 144 |
+
xls_filename = f"{clean_name}.xlsx"
|
| 145 |
+
|
| 146 |
+
logger.info("📥 معالجة: %s (%s)", txt_filename, rank)
|
| 147 |
+
|
| 148 |
+
final_output = process_ocr_text(raw_text)
|
| 149 |
+
latest_processed_text = final_output
|
| 150 |
+
excel_path = generate_excel_grid(final_output)
|
| 151 |
+
|
| 152 |
+
if api:
|
| 153 |
+
if is_vip:
|
| 154 |
+
with tempfile.NamedTemporaryFile(delete=False, suffix=".txt") as tmp_txt:
|
| 155 |
+
tmp_txt.write(final_output.encode("utf-8"))
|
| 156 |
+
tmp_txt_path = tmp_txt.name
|
| 157 |
+
|
| 158 |
+
api.upload_file(
|
| 159 |
+
path_or_fileobj=tmp_txt_path,
|
| 160 |
+
path_in_repo=f"{FINAL_DIR}/{txt_filename}",
|
| 161 |
+
repo_id=REPO_ID, repo_type="dataset", token=HF_TOKEN
|
| 162 |
+
)
|
| 163 |
+
os.unlink(tmp_txt_path)
|
| 164 |
+
|
| 165 |
+
api.upload_file(
|
| 166 |
+
path_or_fileobj=excel_path,
|
| 167 |
+
path_in_repo=f"{EXCEL_DIR}/{xls_filename}",
|
| 168 |
+
repo_id=REPO_ID, repo_type="dataset", token=HF_TOKEN
|
| 169 |
+
)
|
| 170 |
+
|
| 171 |
+
logger.info("⚡ VIP رفع مباشر: %s", txt_filename)
|
| 172 |
+
|
| 173 |
+
else:
|
| 174 |
+
shutil.copy2(excel_path, os.path.join(LOCAL_FREE_XLS_DIR, xls_filename))
|
| 175 |
+
with open(os.path.join(LOCAL_FREE_TXT_DIR, txt_filename), "w", encoding="utf-8") as f:
|
| 176 |
+
f.write(final_output)
|
| 177 |
+
logger.info("🐢 FREE تخزين مؤقت: %s", txt_filename)
|
| 178 |
+
|
| 179 |
+
if os.path.exists(excel_path):
|
| 180 |
+
os.unlink(excel_path)
|
| 181 |
+
|
| 182 |
+
ARABIC_ENGINE_QUEUE.task_done()
|
| 183 |
+
|
| 184 |
+
except Exception as e:
|
| 185 |
+
logger.error("خطأ: %s", e)
|
| 186 |
+
time.sleep(1)
|
| 187 |
+
|
| 188 |
+
# ========== استقبال الـ OCR ==========
|
| 189 |
+
def receive_from_ocr_space(raw_text: str, username: str, rank: str):
|
| 190 |
+
prefix = "VIP_" if rank == "vip" else ""
|
| 191 |
+
simulated_source = f"{prefix}{username}_document"
|
| 192 |
+
ARABIC_ENGINE_QUEUE.put((raw_text, {"source": simulated_source, "rank": rank}))
|
| 193 |
+
return f"تم استقبال النص من {username}."
|
| 194 |
+
|
| 195 |
+
# ========== FastAPI ==========
|
| 196 |
+
fastapi_app = FastAPI()
|
| 197 |
+
|
| 198 |
+
@fastapi_app.post("/receive_ocr")
|
| 199 |
+
async def receive_ocr(request: Request):
|
| 200 |
+
payload = await request.json()
|
| 201 |
+
raw_text = payload.get("text", "")
|
| 202 |
+
username = payload.get("username", "UnknownUser")
|
| 203 |
+
rank = payload.get("rank", "free")
|
| 204 |
+
return receive_from_ocr_space(raw_text, username, rank)
|
| 205 |
+
|
| 206 |
+
# ========== Gradio ==========
|
| 207 |
+
with gr.Blocks(title="Arabic Golden Engine – Pro Lite") as demo:
|
| 208 |
+
gr.Markdown("# ⚡ المعالج الذهبي – نسخة Pro Lite")
|
| 209 |
+
gr.Textbox(label="آخر نص", value=lambda: latest_processed_text, every=2)
|
| 210 |
+
|
| 211 |
+
# ========== تشغيل ==========
|
| 212 |
+
threading.Thread(target=in_memory_receiver_loop, daemon=True).start()
|
| 213 |
+
|
| 214 |
+
app = gr.mount_gradio_app(fastapi_app, demo, path="/")
|
| 215 |
+
|
| 216 |
+
if __name__ == "__main__":
|
| 217 |
+
import uvicorn
|
| 218 |
+
uvicorn.run(app, host="0.0.0.0", port=7860)
|