F-allahmoradi commited on
Commit
a79e351
·
verified ·
1 Parent(s): 6041121

Upload app.py

Browse files
Files changed (1) hide show
  1. app.py +197 -0
app.py ADDED
@@ -0,0 +1,197 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import re
3
+ import tempfile
4
+ import streamlit as st
5
+ from PIL import Image
6
+ from pdf2image import convert_from_path, pdfinfo_from_path
7
+ import pytesseract
8
+
9
+ # ----------------------------------------------------------------------
10
+ # تنظیمات اولیه
11
+ # ----------------------------------------------------------------------
12
+ ZWNJ = '\u200c'
13
+
14
+ # ----------------------------------------------------------------------
15
+ # تابع نرمال‌سازی متن فارسی
16
+ # ----------------------------------------------------------------------
17
+ def normalize_text(text: str) -> str:
18
+ if not text:
19
+ return ""
20
+
21
+ arabic_to_persian = {
22
+ 'ك': 'ک', 'ي': 'ی', 'ئ': 'ی', 'أ': 'ا', 'ة': 'ه',
23
+ 'ؤ': 'و', 'إ': 'ا', 'ٰ': '', 'ٔ': '', 'ّ': ''
24
+ }
25
+ for ar, pr in arabic_to_persian.items():
26
+ text = text.replace(ar, pr)
27
+
28
+ corrections = {
29
+ 'پسرده': 'پرده', 'اینن': 'این', 'خلسوت': 'خلوت',
30
+ 'نضورد': 'نخورد', 'سبصد': 'سیصد', 'صایون': 'صابون',
31
+ 'وشد': 'شد', 'میکنند': 'می‌کنند', 'میشود': 'می‌شود',
32
+ 'میکرد': 'می‌کرد', 'میکنم': 'می‌کنم', 'میکنی': 'می‌کنی',
33
+ 'میکسرد': 'می‌کرد', 'میکسند': 'می‌کنند', 'اِقتصاد': 'اقتصاد',
34
+ 'اِجتماع': 'اجتماع', 'اِنسان': 'انسان', 'اِمکان': 'امکان'
35
+ }
36
+ for wrong, correct in corrections.items():
37
+ text = text.replace(wrong, correct)
38
+
39
+ text = re.sub(r'\b[0-9a-zA-Z\-]+\b', '', text)
40
+ text = re.sub(r'[^\w\s\u200c\u200d\u200e\u200f\u0600-\u06FF]', ' ', text)
41
+ text = re.sub(r'\s+', ' ', text).strip()
42
+ text = re.sub(r'(می|نمی)\s+(باشد|کند|شود|روم|کنم|کنی|کنید|کنیم|رسد|گردد|دهد)',
43
+ lambda m: m.group(1) + ZWNJ + m.group(2), text)
44
+ text = re.sub(r'(ها)\s+', r'\1' + ZWNJ + ' ', text)
45
+ text = re.sub(r'\s+([؟؟،،.:!;])', r'\1', text)
46
+ return text
47
+
48
+ # ----------------------------------------------------------------------
49
+ # حذف نویز و اعداد وسط متن
50
+ # ----------------------------------------------------------------------
51
+ def remove_noise(text: str) -> str:
52
+ text = re.sub(r'\b[0-9۰-۹]+\b', '', text)
53
+ text = re.sub(r'\s+', ' ', text).strip()
54
+ return text
55
+
56
+ # ----------------------------------------------------------------------
57
+ # فرمت شبیه کتاب
58
+ # ----------------------------------------------------------------------
59
+ def format_as_book(text: str) -> str:
60
+ text = re.sub(r'\n+', '\n', text)
61
+ text = re.sub(r'(?<![.؟!])\n\s*', ' ', text)
62
+ text = re.sub(r'\s+', ' ', text).strip()
63
+ text = re.sub(r'([.؟!])\s+', r'\1\n', text)
64
+ return text
65
+
66
+ # ----------------------------------------------------------------------
67
+ # OCR از تصویر
68
+ # ----------------------------------------------------------------------
69
+ def ocr_from_image(image: Image.Image) -> str:
70
+ image = image.convert('L')
71
+ config = r'--oem 3 --psm 6 -c preserve_interword_spaces=1'
72
+ raw = pytesseract.image_to_string(image, lang='fas+eng', config=config)
73
+ text = normalize_text(raw)
74
+ text = remove_noise(text)
75
+ text = format_as_book(text)
76
+ return text
77
+
78
+ # ----------------------------------------------------------------------
79
+ # OCR از PDF (با نمایش پیشرفت)
80
+ # ----------------------------------------------------------------------
81
+ def ocr_from_pdf(pdf_path: str, start_page: int, end_page: int) -> str:
82
+ try:
83
+ info = pdfinfo_from_path(pdf_path)
84
+ total_pages = int(info["Pages"])
85
+ end_page = min(end_page, total_pages)
86
+ start_page = max(1, start_page)
87
+
88
+ if start_page > total_pages:
89
+ return "❌ شماره صفحه شروع از تعداد کل صفحات بیشتر است."
90
+
91
+ images = convert_from_path(pdf_path, dpi=300, first_page=start_page, last_page=end_page)
92
+ all_text = f"📊 استخراج صفحات {start_page} تا {end_page} از {total_pages} صفحه:\n\n"
93
+
94
+ # 🟢 نوار پیشرفت و متن وضعیت
95
+ progress_bar = st.progress(0)
96
+ status_text = st.empty()
97
+
98
+ total_to_process = len(images)
99
+ for i, img in enumerate(images):
100
+ page_num = start_page + i
101
+ status_text.text(f"در حال پردازش صفحه {page_num} از {end_page} ...")
102
+
103
+ # OCR صفحه
104
+ text = ocr_from_image(img)
105
+ all_text += f"--- صفحه {page_num} ---\n{text}\n\n"
106
+
107
+ # بروزرسانی درصد
108
+ percent = int(((i + 1) / total_to_process) * 100)
109
+ progress_bar.progress(percent)
110
+
111
+ status_text.text("✅ پردازش کامل شد.")
112
+ return all_text.strip()
113
+
114
+ except Exception as e:
115
+ return f"❌ خطا در پردازش PDF: {str(e)}"
116
+
117
+ # ----------------------------------------------------------------------
118
+ # رابط کاربری Streamlit
119
+ # ----------------------------------------------------------------------
120
+ def main():
121
+ st.set_page_config(page_title="OCR فارسی - کتابی", layout="wide")
122
+ st.markdown(
123
+ """
124
+ <style>
125
+ body {direction: rtl; text-align: right; font-family: "Vazir", "Tahoma", sans-serif;}
126
+ textarea {
127
+ direction: rtl !important;
128
+ text-align: justify !important;
129
+ font-family: "Vazir", "Tahoma", sans-serif !important;
130
+ line-height: 1.8 !important;
131
+ white-space: pre-wrap !important;
132
+ }
133
+ </style>
134
+ """, unsafe_allow_html=True
135
+ )
136
+
137
+ st.title(" OCR فارسی - خروجی شبیه کتاب")
138
+ st.markdown("استخراج متن از **PDF** یا **تصویر** بدون اعداد و نویز وسط متن")
139
+
140
+ with st.sidebar:
141
+ st.header("⚙️ تنظیمات استخراج")
142
+ uploaded_file = st.file_uploader("📁 فایل خود را آپلود کنید (PDF، JPG، PNG)", type=["pdf","jpg","jpeg","png"])
143
+ start_page, end_page = 1, 1
144
+ tmp_path = None
145
+
146
+ if uploaded_file is not None:
147
+ file_ext = uploaded_file.name.lower().split('.')[-1]
148
+ with tempfile.NamedTemporaryFile(delete=False, suffix=f".{file_ext}") as tmp_file:
149
+ tmp_file.write(uploaded_file.getvalue())
150
+ tmp_path = tmp_file.name
151
+
152
+ if file_ext == "pdf":
153
+ try:
154
+ info = pdfinfo_from_path(tmp_path)
155
+ total_pages = int(info["Pages"])
156
+ st.success(f"📄 فایل PDF با **{total_pages} صفحه** شناسایی شد.")
157
+ extract_all = st.checkbox("✅ استخراج همه صفحات", value=True)
158
+ if not extract_all:
159
+ start_page = st.number_input("صفحه شروع", 1, total_pages, 1)
160
+ end_page = st.number_input("صفحه پایان", 1, total_pages, total_pages)
161
+ else:
162
+ start_page, end_page = 1, total_pages
163
+ except Exception as e:
164
+ st.error(f"❌ خطا در خواندن PDF: {e}")
165
+ else:
166
+ st.info("📷 فایل تصویری — تمام محتوا پردازش خواهد شد.")
167
+
168
+ # Main
169
+ if uploaded_file is not None and tmp_path:
170
+ file_ext = uploaded_file.name.lower().split('.')[-1]
171
+
172
+ if file_ext == "pdf":
173
+ if st.button("🚀 استخراج متن از PDF", use_container_width=True):
174
+ with st.spinner("در حال پردازش..."):
175
+ result = ocr_from_pdf(tmp_path, start_page, end_page)
176
+ st.markdown("### 📝 متن استخراج‌شده ")
177
+ st.text_area("📘 خروجی OCR", result, height=600)
178
+ st.download_button("📥 دانلود متن", result, file_name="extracted_text.txt")
179
+
180
+ else:
181
+ st.image(uploaded_file, caption="تصویر آپلود شده", use_column_width=True)
182
+ if st.button("🚀 استخراج متن از تصویر", use_container_width=True):
183
+ with st.spinner("در حال پردازش..."):
184
+ image = Image.open(tmp_path)
185
+ result = ocr_from_image(image)
186
+ st.markdown("### 📝 متن استخراج‌شده")
187
+ st.text_area("📘 خروجی OCR", result, height=600)
188
+ st.download_button("📥 دانلود متن", result, file_name="extracted_text.txt")
189
+
190
+ if os.path.exists(tmp_path):
191
+ os.unlink(tmp_path)
192
+
193
+ else:
194
+ st.info("📁 لطفاً یک فایل آپلود کنید.")
195
+
196
+ if __name__ == "__main__":
197
+ main()