Spaces:
Configuration error
Configuration error
| # app.py | |
| # ============================================================================ | |
| # 📦 ایمپورت کتابخانهها | |
| # ============================================================================ | |
| import os | |
| import requests | |
| import json | |
| import re | |
| import time | |
| import threading | |
| from PIL import Image, ImageEnhance | |
| import pytesseract | |
| from pdf2image import convert_from_path | |
| import gradio as gr | |
| from groq import Groq | |
| import numpy as np | |
| import cv2 | |
| from collections import Counter | |
| import easyocr | |
| from persian_tools import digits | |
| from rapidfuzz import fuzz | |
| from datetime import datetime | |
| import concurrent.futures | |
| # تنظیم مسیر Tesseract | |
| pytesseract.pytesseract.tesseract_cmd = '/usr/bin/tesseract' | |
| print("✅ کتابخانهها بارگذاری شدند") | |
| # ============================================================================ | |
| # 🔍 موتور OCR حرفهای با DPI 200 | |
| # ============================================================================ | |
| class ProfessionalOCREngine: | |
| """موتور OCR حرفهای با کیفیت بالا""" | |
| def __init__(self): | |
| self.setup_professional_ocr() | |
| self.setup_easyocr() | |
| def setup_professional_ocr(self): | |
| """تنظیمات حرفهای OCR""" | |
| self.tesseract_configs = [ | |
| '--oem 3 --psm 6 -c tessedit_char_whitelist=آابپتثجچحخدذرزژسشصضطظعغفقکگلمنوهیءةيك012345678۹۰۱۲۳۴۵۶۷۸۹ :.,-()', | |
| '--oem 3 --psm 4 -c preserve_interword_spaces=1', | |
| '--oem 3 --psm 8 -c tessedit_char_blacklist=|\\/><[]{}', | |
| ] | |
| def setup_easyocr(self): | |
| """راهاندازی EasyOCR حرفهای""" | |
| try: | |
| self.easy_reader = easyocr.Reader(['fa', 'en'], gpu=False) | |
| self.easyocr_available = True | |
| print("✅ EasyOCR حرفهای راهاندازی شد") | |
| except Exception as e: | |
| print(f"⚠️ EasyOCR راهاندازی نشد: {e}") | |
| self.easyocr_available = False | |
| def extract_text_professional(self, input_file, num_pages=5): | |
| """استخراج متن با روش حرفهای - ۵ صفحه با DPI 200""" | |
| try: | |
| print(f"🔍 شروع استخراج متن حرفهای از {num_pages} صفحه با DPI 200...") | |
| if isinstance(input_file, str) and input_file.lower().endswith('.pdf'): | |
| # استفاده از DPI 200 برای تعادل سرعت و کیفیت | |
| images = convert_from_path(input_file, first_page=1, last_page=num_pages, dpi=200) | |
| all_texts = [] | |
| for i, image in enumerate(images): | |
| print(f"📄 پردازش صفحه {i+1} از {num_pages} با DPI 200...") | |
| # پردازش حرفهای با کیفیت بالا | |
| tesseract_text = self._extract_with_pro_tesseract(image) | |
| easyocr_text = self._extract_with_pro_easyocr(image) if self.easyocr_available else "" | |
| # ترکیب پیشرفته | |
| combined_text = self._advanced_combination([tesseract_text, easyocr_text]) | |
| if combined_text.strip(): | |
| page_result = f""" | |
| {'='*40} | |
| 📄 صفحه {i+1}: | |
| {'='*40} | |
| {combined_text}""" | |
| all_texts.append(page_result) | |
| print(f"✅ صفحه {i+1} پردازش شد: {len(combined_text)} کاراکتر") | |
| result = '\n'.join(all_texts) | |
| print(f"✅ پردازش {len(images)} صفحه با DPI 200 کامل شد: {len(result)} کاراکتر") | |
| return result | |
| else: | |
| # فایل تصویری با کیفیت بالا | |
| image = Image.open(input_file) | |
| tesseract_text = self._extract_with_pro_tesseract(image) | |
| easyocr_text = self._extract_with_pro_easyocr(image) if self.easyocr_available else "" | |
| combined_text = self._advanced_combination([tesseract_text, easyocr_text]) | |
| result = f""" | |
| {'='*40} | |
| 📄 صفحه 1: | |
| {'='*40} | |
| {combined_text}""" | |
| print(f"✅ پردازش تصویر با کیفیت بالا کامل شد: {len(combined_text)} کاراکتر") | |
| return result | |
| except Exception as e: | |
| return f"❌ خطا در پردازش صفحات: {str(e)}" | |
| def _extract_with_pro_tesseract(self, image): | |
| """استخراج حرفهای با Tesseract""" | |
| try: | |
| # پیشپردازش حرفهای برای DPI 200 | |
| processed_images = [ | |
| self._preprocess_high_quality(image), | |
| self._preprocess_enhanced_contrast(image), | |
| self._preprocess_denoise_advanced(image) | |
| ] | |
| all_texts = [] | |
| for processed_img in processed_images: | |
| for config in self.tesseract_configs: | |
| try: | |
| text = pytesseract.image_to_string(processed_img, lang='fas+eng', config=config) | |
| if text.strip(): | |
| all_texts.append(text) | |
| except: | |
| continue | |
| return self._select_best_quality_text(all_texts) if all_texts else "" | |
| except Exception as e: | |
| print(f"⚠️ خطا در Tesseract حرفهای: {e}") | |
| return "" | |
| def _extract_with_pro_easyocr(self, image): | |
| """استخراج حرفهای با EasyOCR""" | |
| try: | |
| image_np = np.array(image) | |
| # تنظیمات پیشرفته برای کیفیت بالا | |
| results = self.easy_reader.readtext(image_np, paragraph=True, text_threshold=0.3, batch_size=1) | |
| extracted_texts = [] | |
| for result in results: | |
| if len(result) >= 2: | |
| text = result[1] | |
| confidence = result[2] if len(result) > 2 else 0.5 | |
| if confidence > 0.2: # آستانه پایینتر برای دریافت متن بیشتر | |
| extracted_texts.append(text) | |
| return " ".join(extracted_texts) | |
| except Exception as e: | |
| print(f"⚠️ خطا در EasyOCR حرفهای: {e}") | |
| return "" | |
| def _preprocess_high_quality(self, image): | |
| """پیشپردازش برای کیفیت بالا""" | |
| try: | |
| if image.mode != 'L': | |
| image = image.convert('L') | |
| # افزایش کنتراست برای DPI 200 | |
| enhancer = ImageEnhance.Contrast(image) | |
| image = enhancer.enhance(2.5) | |
| # افزایش وضوح | |
| enhancer = ImageEnhance.Sharpness(image) | |
| image = enhancer.enhance(2.0) | |
| return image | |
| except: | |
| return image | |
| def _preprocess_enhanced_contrast(self, image): | |
| """پیشپردازش با کنتراست پیشرفته""" | |
| try: | |
| if image.mode != 'L': | |
| image = image.convert('L') | |
| # استفاده از CLAHE برای کنتراست پیشرفته | |
| img_np = np.array(image) | |
| clahe = cv2.createCLAHE(clipLimit=4.0, tileGridSize=(8,8)) | |
| img_contrast = clahe.apply(img_np) | |
| return Image.fromarray(img_contrast) | |
| except: | |
| return image | |
| def _preprocess_denoise_advanced(self, image): | |
| """پیشپردازش حذف نویز پیشرفته""" | |
| try: | |
| if image.mode != 'L': | |
| image = image.convert('L') | |
| img_np = np.array(image) | |
| # حذف نویز با فیلترهای پیشرفته | |
| img_denoised = cv2.medianBlur(img_np, 3) | |
| img_denoised = cv2.GaussianBlur(img_denoised, (1, 1), 0) | |
| return Image.fromarray(img_denoised) | |
| except: | |
| return image | |
| def _select_best_quality_text(self, text_list): | |
| """انتخاب متن با بالاترین کیفیت""" | |
| if not text_list: | |
| return "" | |
| scored_texts = [] | |
| for text in text_list: | |
| score = self._calculate_advanced_quality(text) | |
| scored_texts.append((text, score)) | |
| return max(scored_texts, key=lambda x: x[1])[0] | |
| def _calculate_advanced_quality(self, text): | |
| """محاسبه کیفیت پیشرفته متن""" | |
| if not text.strip(): | |
| return 0 | |
| score = 0 | |
| # امتیاز بر اساس طول متن | |
| if 50 <= len(text) <= 5000: | |
| score += 3 | |
| # امتیاز بر اساس کاراکترهای فارسی | |
| persian_chars = len(re.findall(r'[آ-ی]', text)) | |
| persian_ratio = persian_chars / len(text) if len(text) > 0 else 0 | |
| if persian_ratio > 0.3: | |
| score += persian_ratio * 4 | |
| # امتیاز بر اساس ساختار متن | |
| lines = [line.strip() for line in text.split('\n') if line.strip()] | |
| if len(lines) > 2: | |
| valid_lines = sum(1 for line in lines if 5 <= len(line) <= 200) | |
| score += (valid_lines / len(lines)) * 3 | |
| # امتیاز بر اساس کلمات کلیدی کتاب | |
| book_keywords = ['عنوان', 'نویسنده', 'مؤلف', 'ناشر', 'چاپ', 'شابک', 'قیمت', 'تیراژ', 'کتاب', 'انتشارات', 'مترجم', 'فهرست', 'مقدمه'] | |
| keyword_count = sum(1 for keyword in book_keywords if keyword in text) | |
| score += keyword_count * 0.5 | |
| return score | |
| def _advanced_combination(self, texts): | |
| """ترکیب پیشرفته نتایج""" | |
| valid_texts = [t for t in texts if t and t.strip()] | |
| if not valid_texts: | |
| return "" | |
| if len(valid_texts) == 1: | |
| return valid_texts[0] | |
| # ترکیب هوشمند بر اساس کیفیت | |
| best_text = max(valid_texts, key=lambda x: self._calculate_advanced_quality(x)) | |
| return best_text | |
| # ============================================================================ | |
| # 🧠 استخراجکننده متادیتای حرفهای | |
| # ============================================================================ | |
| class ProfessionalMetadataExtractor: | |
| """استخراجکننده متادیتای حرفهای""" | |
| def __init__(self): | |
| self.setup_professional_patterns() | |
| def setup_professional_patterns(self): | |
| """الگوهای حرفهای برای استخراج اطلاعات""" | |
| self.patterns = { | |
| 'title': [ | |
| r'عنوان\s*[:\-]\s*(.+?)(?=\n|$)', | |
| r'نام\s*کتاب\s*[:\-]\s*(.+?)(?=\n|$)', | |
| r'کتاب\s*[:\-]\s*(.+?)(?=\n|$)', | |
| r'^(?!.*(نویسنده|مؤلف|ناشر|چاپ|شابک))(.{10,120}?)(?=\n|$)', | |
| ], | |
| 'author': [ | |
| r'نویسنده\s*[:\-]\s*(.+?)(?=\n|$)', | |
| r'مؤلف\s*[:\-]\s*(.+?)(?=\n|$)', | |
| r'پدیدآور\s*[:\-]\s*(.+?)(?=\n|$)', | |
| r'تألیف\s*[:\-]\s*(.+?)(?=\n|$)', | |
| ], | |
| 'translator': [ | |
| r'مترجم\s*[:\-]\s*(.+?)(?=\n|$)', | |
| r'ترجمه\s*[:\-]\s*(.+?)(?=\n|$)', | |
| r'برگردان\s*[:\-]\s*(.+?)(?=\n|$)', | |
| ], | |
| 'publisher': [ | |
| r'ناشر\s*[:\-]\s*(.+?)(?=\n|$)', | |
| r'انتشارات\s*[:\-]\s*(.+?)(?=\n|$)', | |
| r'چاپ\s*[:\-]\s*(.+?)(?=\n|$)', | |
| ], | |
| 'publication_year': [ | |
| r'سال\s*انتشار\s*[:\-]\s*(\d{4})', | |
| r'تاریخ\s*چاپ\s*[:\-]\s*(\d{4})', | |
| r'چاپ\s*[:\-].*?(\d{4})', | |
| r'(\d{4})\s*,\s*تیراژ', | |
| r'۱۳[۴-۹]\d', | |
| ], | |
| 'edition': [ | |
| r'نوبت\s*چاپ\s*[:\-]\s*(\S+)', | |
| r'چاپ\s*[:\-]\s*(\S+)', | |
| r'چاپ\s*(اول|دوم|سوم|چهارم|پنجم|ششم|هفتم|هشتم|نهم|دهم)', | |
| ], | |
| 'isbn': [ | |
| r'شابک\s*[:\-]\s*([\d\-]+)', | |
| r'ISBN\s*[:\-]\s*([\d\-]+)', | |
| r'[\d\-]{10,17}', | |
| ] | |
| } | |
| def extract_metadata_professional(self, extracted_text): | |
| """استخراج متادیتا با روش حرفهای""" | |
| try: | |
| if not extracted_text or len(extracted_text.strip()) < 100: | |
| return self._get_empty_result("متن کافی برای تحلیل یافت نشد") | |
| # استخراج پیشرفته | |
| pattern_results = self._extract_with_advanced_patterns(extracted_text) | |
| intelligent_results = self._intelligent_analysis(extracted_text) | |
| combined_results = self._professional_combination(pattern_results, intelligent_results) | |
| enhanced_results = self._enhance_with_context(combined_results, extracted_text) | |
| validated_results = self._professional_validation(enhanced_results) | |
| confidence = self._calculate_professional_confidence(validated_results) | |
| return { | |
| 'success': True, | |
| 'metadata': validated_results, | |
| 'confidence': confidence, | |
| 'extraction_method': 'professional' | |
| } | |
| except Exception as e: | |
| return self._get_empty_result(str(e)) | |
| def _extract_with_advanced_patterns(self, text): | |
| """استخراج با الگوهای پیشرفته""" | |
| results = {} | |
| for field, patterns in self.patterns.items(): | |
| best_match = None | |
| for pattern in patterns: | |
| try: | |
| matches = re.findall(pattern, text, re.IGNORECASE | re.MULTILINE) | |
| for match in matches: | |
| if isinstance(match, tuple): | |
| match = match[0] if match[0] else (match[1] if len(match) > 1 else "") | |
| if match: | |
| clean_value = self._professional_clean(field, str(match)) | |
| if self._is_professionally_valid(field, clean_value): | |
| if not best_match or len(clean_value) > len(best_match): | |
| best_match = clean_value | |
| except: | |
| continue | |
| if best_match: | |
| results[field] = best_match | |
| return results | |
| def _intelligent_analysis(self, text): | |
| """تحلیل هوشمند""" | |
| lines = [line.strip() for line in text.split('\n') if line.strip()] | |
| results = {} | |
| # تحلیل عمیق خطوط | |
| for i, line in enumerate(lines): | |
| if i < 10: # فقط 10 خط اول برای کارایی | |
| if not results.get('title') and self._is_professional_title(line, i): | |
| results['title'] = self._professional_title_clean(line) | |
| if not results.get('author') and self._contains_author_indicator(line): | |
| author = self._extract_professional_author(line) | |
| if author: | |
| results['author'] = author | |
| if not results.get('publisher') and self._contains_publisher_indicator(line): | |
| publisher = self._extract_professional_publisher(line) | |
| if publisher: | |
| results['publisher'] = publisher | |
| return results | |
| def _is_professional_title(self, line, line_index): | |
| """بررسی حرفهای عنوان""" | |
| if len(line) < 8 or len(line) > 150: | |
| return False | |
| # خطوط اول احتمال بیشتری برای عنوان دارند | |
| title_probability = max(0.8 - (line_index * 0.1), 0.3) | |
| exclude_patterns = [ | |
| r'نویسنده', r'مؤلف', r'ناشر', r'چاپ', r'شابک', | |
| r'قیمت', r'تیراژ', r'صفحه', r'فهرست', r'مقدمه' | |
| ] | |
| if any(re.search(pattern, line) for pattern in exclude_patterns): | |
| return False | |
| persian_ratio = len(re.findall(r'[آ-ی]', line)) / len(line) if len(line) > 0 else 0 | |
| if persian_ratio < 0.4: | |
| return False | |
| return True | |
| def _professional_combination(self, pattern_results, intelligent_results): | |
| """ترکیب حرفهای نتایج""" | |
| combined = pattern_results.copy() | |
| # اولویت با نتایج الگوها، سپس نتایج هوشمند | |
| for field, value in intelligent_results.items(): | |
| if value and not combined.get(field): | |
| combined[field] = value | |
| return combined | |
| def _professional_clean(self, field, value): | |
| """پاکسازی حرفهای""" | |
| if not value: | |
| return value | |
| value = re.sub(r'[ـ\r\x200c\x200d]', '', value) | |
| value = re.sub(r'\s+', ' ', value).strip() | |
| # پاکسازی ویژه هر فیلد | |
| cleaners = { | |
| 'title': lambda x: re.sub(r'^[:\-\s]*', '', x), | |
| 'author': lambda x: re.sub(r'^(نویسنده|مؤلف|پدیدآور)[:\-\s]*', '', x), | |
| 'publisher': lambda x: re.sub(r'^(ناشر|انتشارات)[:\-\s]*', '', x), | |
| 'publication_year': lambda x: re.sub(r'[^\d]', '', x), | |
| } | |
| if field in cleaners: | |
| value = cleaners[field](value) | |
| return value.strip() | |
| def _is_professionally_valid(self, field, value): | |
| """اعتبارسنجی حرفهای""" | |
| if not value: | |
| return False | |
| validators = { | |
| 'title': lambda x: 5 <= len(x) <= 200, | |
| 'author': lambda x: 3 <= len(x) <= 100, | |
| 'publisher': lambda x: 3 <= len(x) <= 100, | |
| 'publication_year': lambda x: x.isdigit() and 1300 <= int(x) <= 1500, | |
| 'edition': lambda x: 1 <= len(x) <= 50, | |
| 'isbn': lambda x: 10 <= len(x.replace('-', '')) <= 17 | |
| } | |
| return field in validators and validators[field](value) | |
| def _calculate_professional_confidence(self, results): | |
| """محاسبه اطمینان حرفهای""" | |
| if not results: | |
| return 0.0 | |
| weights = { | |
| 'title': 0.25, | |
| 'author': 0.20, | |
| 'publisher': 0.15, | |
| 'publication_year': 0.15, | |
| 'edition': 0.10, | |
| 'isbn': 0.10, | |
| 'translator': 0.05 | |
| } | |
| total_score = 0.0 | |
| for field, weight in weights.items(): | |
| if field in results and results[field]: | |
| total_score += weight | |
| return total_score | |
| # ============================================================================ | |
| # 🤖 سیستم پردازش ترکیبی حرفهای | |
| # ============================================================================ | |
| class ProfessionalPersianBookProcessor: | |
| def __init__(self): | |
| # خواندن کلیدها از متغیرهای محیطی (Secrets) | |
| key1 = os.getenv("GROQ_API_KEY_1") | |
| key2 = os.getenv("GROQ_API_KEY_2") | |
| key3 = os.getenv("GROQ_API_KEY_3") | |
| self.groq_keys = [k for k in [key1, key2, key3] if k] | |
| if not self.groq_keys: | |
| print("⚠️ هیچ کلید Groq از Secrets یافت نشد. ممکن است عملکرد محدود شود.") | |
| self.groq_keys = [] | |
| self.current_key_index = 0 | |
| self.ocr_engine = ProfessionalOCREngine() | |
| self.metadata_extractor = ProfessionalMetadataExtractor() | |
| print(f"✅ سیستم حرفهای با {len(self.groq_keys)} کلید Groq راهاندازی شد") | |
| def get_next_groq_client(self): | |
| """دریافت کلاینت Groq بعدی""" | |
| if not self.groq_keys: | |
| return None | |
| self.current_key_index = (self.current_key_index + 1) % len(self.groq_keys) | |
| try: | |
| return Groq(api_key=self.groq_keys[self.current_key_index]) | |
| except: | |
| return None | |
| def extract_text_professional(self, file_path, num_pages=5): | |
| """استخراج متن حرفهای - ۵ صفحه با DPI 200""" | |
| return self.ocr_engine.extract_text_professional(file_path, num_pages) | |
| def analyze_with_groq_professional(self, full_text): | |
| """تحلیل حرفهای با Groq - پرامپت پیشرفته""" | |
| for key_index in range(len(self.groq_keys)): | |
| client = self.get_next_groq_client() | |
| if not client: | |
| continue | |
| try: | |
| print(f" 🤖 تحلیل حرفهای با Groq (کلید {key_index + 1})...") | |
| # پرامپت حرفهای و جامع | |
| prompt = f""" | |
| شما یک متخصص حرفهای در تحلیل و استخراج اطلاعات از کتابهای فارسی هستید. لطفاً با دقت بالا اطلاعات زیر را از متن ۵ صفحه اول کتاب استخراج کنید. | |
| **متن کامل ۵ صفحه اول کتاب:** | |
| {full_text[:4000]} | |
| **اطلاعات مورد نیاز برای استخراج:** | |
| ۱. **عنوان اصلی کتاب** (title): | |
| - دقیقترین و کاملترین عنوان را پیدا کنید | |
| - عناوین فرعی را نیز در صورت وجود شامل شود | |
| ۲. **نام نویسنده/مؤلف** (author): | |
| - نام کامل نویسنده یا مؤلف | |
| - در صورت وجود چند نویسنده، همه را ذکر کنید | |
| ۳. **نام مترجم** (translator): | |
| - اگر کتاب ترجمه است، نام کامل مترجم | |
| - در صورت عدم ترجمه، "یافت نشد" | |
| ۴. **نام ناشر** (publisher): | |
| - نام کامل انتشارات یا ناشر | |
| - شامل شهر در صورت ذکر شدن | |
| ۵. **سال انتشار** (publication_year): | |
| - سال چاپ به صورت عدد (مثال: 1402) | |
| - از تاریخهای هجری شمسی استفاده شود | |
| ۶. **شماره شابک** (isbn): | |
| - شماره ۱۰ یا ۱۳ رقمی شابک | |
| - با فرمت استاندارد | |
| ۷. **نوبت چاپ** (edition): | |
| - شماره یا عنوان نوبت چاپ | |
| - مثال: اول، دوم، سوم... | |
| ۸. **موضوع کتاب** (subject): | |
| - حوزه موضوعی اصلی کتاب | |
| - ژانر و زمینه محتوایی | |
| ۹. **خلاصه محتوا** (summary): | |
| - خلاصهای جامع از محتوای ۵ صفحه اول | |
| - حدود ۱۰۰-۱۵۰ کلمه | |
| -突出重点 و مفاهیم اصلی | |
| **دستورات مهم:** | |
| - پاسخ را **فقط و فقط** به صورت JSON برگردانید | |
| - از هیچ متن اضافی قبل یا بعد از JSON استفاده نکنید | |
| - برای فیلدهایی که اطلاعاتی پیدا نکردید از "یافت نشد" استفاده کنید | |
| - از قالببندی استاندارد JSON استفاده کنید | |
| - دقت و صحت اطلاعات اولویت دارد | |
| **قالب خروجی JSON:** | |
| {{ | |
| "title": "عنوان کامل کتاب", | |
| "author": "نام کامل نویسنده", | |
| "translator": "نام کامل مترجم", | |
| "publisher": "نام کامل ناشر", | |
| "publication_year": "سال انتشار", | |
| "isbn": "شماره شابک", | |
| "edition": "نوبت چاپ", | |
| }} | |
| **تأکید: فقط JSON خالص برگردانید، بدون هیچ توضیح اضافی!** | |
| """ | |
| response = client.chat.completions.create( | |
| messages=[{"role": "user", "content": prompt}], | |
| model="llama-3.1-8b-instant", | |
| temperature=0.1, | |
| max_tokens=2000, # افزایش به 2000 توکن | |
| timeout=30 | |
| ) | |
| result_text = response.choices[0].message.content | |
| print(f" ✅ پاسخ Groq دریافت شد ({len(result_text)} کاراکتر)") | |
| # استخراج پیشرفته JSON | |
| json_match = re.search(r'\{[^{}]*(?:\{[^{}]*\}[^{}]*)*\}', result_text, re.DOTALL) | |
| if json_match: | |
| json_str = json_match.group() | |
| try: | |
| result_json = json.loads(json_str) | |
| filled_fields = sum(1 for v in result_json.values() if v and v != "یافت نشد") | |
| print(f" 📊 فیلدهای پر: {filled_fields} از ۹") | |
| if filled_fields > 0: | |
| return { | |
| "success": True, | |
| "source": "groq_professional", | |
| "results": result_json, | |
| "filled_fields": filled_fields, | |
| "response_length": len(result_text) | |
| } | |
| except json.JSONDecodeError as e: | |
| print(f" ❌ خطای JSON: {e}") | |
| else: | |
| print(" ⚠️ JSON در پاسخ یافت نشد") | |
| except Exception as e: | |
| print(f" ⚠️ خطا در Groq حرفهای: {e}") | |
| continue | |
| # استفاده از روش محلی حرفهای | |
| print(" 🔄 استفاده از روش محلی حرفهای...") | |
| return self._analyze_with_local_professional(full_text) | |
| def _analyze_with_local_professional(self, full_text): | |
| """تحلیل با روش محلی حرفهای""" | |
| try: | |
| result = self.metadata_extractor.extract_metadata_professional(full_text) | |
| if result['success']: | |
| metadata = result['metadata'] | |
| formatted_results = { | |
| "title": metadata.get('title', 'یافت نشد'), | |
| "author": metadata.get('author', 'یافت نشد'), | |
| "translator": metadata.get('translator', 'یافت نشد'), | |
| "publisher": metadata.get('publisher', 'یافت نشد'), | |
| "publication_year": metadata.get('publication_year', 'یافت نشد'), | |
| "isbn": metadata.get('isbn', 'یافت نشد'), | |
| "edition": metadata.get('edition', 'یافت نشد'), | |
| "subject": 'یافت نشد', | |
| "summary": 'یافت نشد' | |
| } | |
| filled_fields = sum(1 for v in formatted_results.values() if v and v != "یافت نشد") | |
| return { | |
| "success": True, | |
| "source": "local_professional", | |
| "results": formatted_results, | |
| "filled_fields": filled_fields, | |
| "confidence": result['confidence'] | |
| } | |
| else: | |
| return { | |
| "success": False, | |
| "source": "local_professional", | |
| "error": result.get('error', 'خطای ناشناخته'), | |
| "results": self._get_fallback_results() | |
| } | |
| except Exception as e: | |
| return { | |
| "success": False, | |
| "source": "local_professional", | |
| "error": str(e), | |
| "results": self._get_fallback_results() | |
| } | |
| def _get_fallback_results(self): | |
| """نتایج پیشفرض""" | |
| return { | |
| "title": "یافت نشد", | |
| "author": "یافت نشد", | |
| "translator": "یافت نشد", | |
| "publisher": "یافت نشد", | |
| "publication_year": "یافت نشد", | |
| "isbn": "یافت نشد", | |
| "edition": "یافت نشد", | |
| "subject": "یافت نشد", | |
| "summary": "یافت نشد" | |
| } | |
| # ============================================================================ | |
| # 🎯 رابط کاربری حرفهای | |
| # ============================================================================ | |
| def process_book_professional(file): | |
| """پردازش کتاب با سیستم حرفهای - ۵ صفحه با DPI 200""" | |
| if file is None: | |
| return create_empty_display(), None, "📊 منتظر پردازش..." | |
| try: | |
| processor = ProfessionalPersianBookProcessor() | |
| print("=" * 60) | |
| print("🔄 شروع پردازش حرفهای (۵ صفحه اول با DPI 200)...") | |
| start_time = time.time() | |
| # استخراج متن حرفهای - ۵ صفحه با DPI 200 | |
| print("🔍 در حال استخراج متن از ۵ صفحه اول با DPI 200...") | |
| extracted_text = processor.extract_text_professional(file.name, num_pages=5) | |
| extraction_time = time.time() - start_time | |
| if "❌" in extracted_text: | |
| return create_error_display(extracted_text), None, f"❌ خطا در استخراج ({extraction_time:.1f}ثانیه)" | |
| if not extracted_text.strip(): | |
| return create_error_display("متن قابل استخراج یافت نشد"), None, f"⚠️ متن خالی ({extraction_time:.1f}ثانیه)" | |
| print(f"✅ استخراج متن کامل: {len(extracted_text)} کاراکتر") | |
| print(f"⏱️ زمان استخراج: {extraction_time:.2f} ثانیه") | |
| # تحلیل حرفهای با پرامپت پیشرفته | |
| print("🔍 در حال تحلیل حرفهای با AI...") | |
| analysis_start_time = time.time() | |
| analysis_result = processor.analyze_with_groq_professional(extracted_text) | |
| analysis_time = time.time() - analysis_start_time | |
| total_time = extraction_time + analysis_time | |
| print(f"⏱️ زمان تحلیل: {analysis_time:.2f} ثانیه") | |
| print(f"⏱️ زمان کل: {total_time:.2f} ثانیه") | |
| # ایجاد گزارش حرفهای | |
| if analysis_result["success"]: | |
| print(f"✅ تحلیل موفق با {analysis_result['source']}") | |
| report = create_professional_report(analysis_result, extracted_text, total_time) | |
| stats = f"✅ {analysis_result['source']} - {analysis_result['filled_fields']}/۹ فیلد - {total_time:.1f}ثانیه" | |
| if analysis_result.get('response_length'): | |
| stats += f" - پاسخ: {analysis_result['response_length']}کاراکتر" | |
| if analysis_result["source"] == "local_professional": | |
| stats += f" - اطمینان: {analysis_result.get('confidence', 0)*100:.1f}%" | |
| else: | |
| print(f"❌ خطا در تحلیل") | |
| report = create_error_display(f"خطا در تحلیل: {analysis_result.get('error', 'نامشخص')}") | |
| stats = f"❌ خطا در تحلیل - {total_time:.1f}ثانیه" | |
| # ذخیره فایل | |
| output_file = "/tmp/book_analysis_professional.txt" # در Hugging Face از /tmp استفاده کنید | |
| with open(output_file, "w", encoding="utf-8") as f: | |
| f.write(report) | |
| print("✅ پردازش حرفهای ۵ صفحه کامل شد") | |
| print("=" * 60) | |
| return create_professional_display(analysis_result, total_time), output_file, stats | |
| except Exception as e: | |
| error_msg = f"❌ خطای سیستمی: {str(e)}" | |
| print(error_msg) | |
| return create_error_display(error_msg), None, "❌ خطا" | |
| def create_professional_report(analysis_result, full_text, processing_time): | |
| """ایجاد گزارش حرفهای""" | |
| results = analysis_result["results"] | |
| source = analysis_result["source"] | |
| report = f""" | |
| 📚 گزارش تحلیل حرفهای کتاب - سیستم پیشرفته | |
| {'='*50} | |
| ⚙️ اطلاعات پردازش: | |
| • روش تحلیل: {source.upper()} | |
| • زمان پردازش: {processing_time:.2f} ثانیه | |
| • کیفیت تصویر: DPI 200 | |
| • طول متن: {len(full_text)} کاراکتر | |
| • فیلدهای پر: {analysis_result['filled_fields']} از ۹ | |
| • صفحات پردازش شده: ۵ صفحه اول | |
| 📖 اطلاعات استخراج شده: | |
| {'‐'*30} | |
| """ | |
| fields = [ | |
| ('📖 عنوان کتاب', 'title'), | |
| ('✍️ نویسنده/مؤلف', 'author'), | |
| ('🌐 مترجم', 'translator'), | |
| ('🏢 ناشر', 'publisher'), | |
| ('📅 سال انتشار', 'publication_year'), | |
| ('🔖 شابک (ISBN)', 'isbn'), | |
| ('🔄 نوبت چاپ', 'edition'), | |
| ('📚 موضوع کتاب', 'subject'), | |
| ('📝 خلاصه محتوا', 'summary') | |
| ] | |
| for persian_name, english_key in fields: | |
| value = results.get(english_key, 'یافت نشد') | |
| report += f"{persian_name}: {value}\n" | |
| # آمار پیشرفته | |
| page_count = len([p for p in full_text.split('📄 صفحه') if p.strip()]) | |
| report += f""" | |
| 📊 آمار حرفهای: | |
| • صفحات پردازش شده: {page_count} از ۵ صفحه | |
| • کیفیت استخراج: DPI 200 | |
| • دقت تحلیل: {analysis_result['filled_fields'] * 11.1:.1f}% | |
| """ | |
| return report | |
| def create_professional_display(analysis_result, processing_time): | |
| """ایجاد نمایش حرفهای""" | |
| results = analysis_result["results"] | |
| source = analysis_result["source"] | |
| filled_fields = analysis_result["filled_fields"] | |
| basic_html = "" | |
| primary_fields = [ | |
| ('title', '📚 عنوان کتاب', 'عنوانی یافت نشد'), | |
| ('author', '✍️ نویسنده/مؤلف', 'نویسندهای یافت نشد'), | |
| ('publisher', '🏢 ناشر', 'ناشری یافت نشد'), | |
| ('publication_year', '📅 سال انتشار', 'سال انتشار یافت نشد'), | |
| ] | |
| for field, display, not_found in primary_fields: | |
| value = results.get(field, not_found) | |
| if value != not_found: | |
| basic_html += f""" | |
| <div style="background: linear-gradient(135deg, #2a2a2a 0%, #1a3a1a 100%); color: #00ff00; padding: 15px; margin: 8px 0; border-radius: 8px; border-left: 4px solid #00ff00; border-right: 1px solid #00ff00;"> | |
| <strong style="color: #00ff00; font-size: 16px;">{display}:</strong> | |
| <div style="color: #ffffff; font-size: 15px; margin-top: 5px;">{value}</div> | |
| </div> | |
| """ | |
| else: | |
| basic_html += f""" | |
| <div style="background: #1a1a1a; color: #666; padding: 15px; margin: 8px 0; border-radius: 8px; border-left: 4px solid #666;"> | |
| <strong style="color: #666;">{display}:</strong> {not_found} | |
| </div> | |
| """ | |
| # فیلدهای تکمیلی | |
| secondary_html = "" | |
| secondary_fields = [ | |
| ('translator', '🌐 مترجم', 'مترجمی یافت نشد'), | |
| ('isbn', '🔖 شابک (ISBN)', 'شابکی یافت نشد'), | |
| ('edition', '🔄 نوبت چاپ', 'نوبت چاپی یافت نشد'), | |
| ('subject', '📚 موضوع کتاب', 'موضوعی یافت نشد'), | |
| ] | |
| for field, display, not_found in secondary_fields: | |
| value = results.get(field, not_found) | |
| secondary_html += f""" | |
| <div style="background: #2a2a2a; color: #ccc; padding: 12px; margin: 6px 0; border-radius: 6px; border: 1px solid #444;"> | |
| <strong>{display}:</strong> {value} | |
| </div> | |
| """ | |
| # خلاصه | |
| summary_html = "" | |
| summary = results.get('summary', 'یافت نشد') | |
| if summary != 'یافت نشد': | |
| summary_html = f""" | |
| <div style="background: #1a2a1a; color: #aaffaa; padding: 15px; margin: 10px 0; border-radius: 8px; border: 1px solid #00aa00;"> | |
| <strong style="color: #00ff00;">📝 خلاصه محتوا:</strong> | |
| <div style="color: #e0e0e0; margin-top: 8px; line-height: 1.6;">{summary}</div> | |
| </div> | |
| """ | |
| confidence_html = "" | |
| if analysis_result.get('confidence'): | |
| confidence_color = "#00ff00" if analysis_result['confidence'] > 0.7 else "#ffff00" if analysis_result['confidence'] > 0.4 else "#ff4444" | |
| confidence_html = f""" | |
| <div style="color: {confidence_color}; font-weight: bold; margin-top: 10px;"> | |
| 🎯 میزان اطمینان تحلیل: {analysis_result['confidence']*100:.1f}% | |
| </div> | |
| """ | |
| return f""" | |
| <div style="font-family: 'Tahoma', 'Arial', sans-serif; background: #000000; color: #00ff00;"> | |
| <div style="background: linear-gradient(135deg, #001a00 0%, #004400 100%); color: #00ff00; padding: 20px; border-radius: 10px 10px 0 0; border: 2px solid #00ff00;"> | |
| <h2 style="margin: 0; text-align: center; color: #00ff00;">🎯 نتایج تحلیل حرفهای (۵ صفحه اول)</h2> | |
| <p style="text-align: center; margin: 5px 0 0 0; color: #aaffaa;">کیفیت DPI 200 - پرامپت پیشرفته</p> | |
| </div> | |
| <div style="padding: 20px; background: #000000; border-radius: 0 0 10px 10px; border: 2px solid #00ff00; border-top: none;"> | |
| <h3 style="color: #00ff00; border-bottom: 2px solid #00ff00; padding-bottom: 10px;">📖 اطلاعات اصلی کتاب</h3> | |
| {basic_html} | |
| <div style="margin-top: 20px;"> | |
| <h4 style="color: #00ff00; margin-bottom: 10px;">📋 اطلاعات تکمیلی</h4> | |
| {secondary_html} | |
| </div> | |
| {summary_html} | |
| <div style="background: #1a1a1a; color: #00ff00; padding: 15px; border-radius: 8px; margin-top: 20px; border: 2px solid #00ff00;"> | |
| <h4 style="margin-top: 0; color: #00ff00;">🔧 اطلاعات فنی پردازش</h4> | |
| <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 10px;"> | |
| <div>⏱️ زمان پردازش: <strong style="color: #ffffff;">{processing_time:.2f} ثانیه</strong></div> | |
| <div>📊 فیلدهای پر: <strong style="color: #ffffff;">{filled_fields} از ۹</strong></div> | |
| <div>🤖 روش استخراج: <strong style="color: #ffffff;">{source.upper()}</strong></div> | |
| <div>📄 کیفیت تصویر: <strong style="color: #ffffff;">DPI 200</strong></div> | |
| <div>🔍 صفحات پردازش: <strong style="color: #ffffff;">۵ صفحه اول</strong></div> | |
| <div>💾 max_tokens: <strong style="color: #ffffff;">۲۰۰۰</strong></div> | |
| </div> | |
| {confidence_html} | |
| </div> | |
| </div> | |
| </div> | |
| """ | |
| def create_error_display(error_message): | |
| """ایجاد نمایش خطا""" | |
| return f""" | |
| <div style="background: #1a0000; color: #ff0000; padding: 15px; border-radius: 8px; border: 2px solid #ff0000;"> | |
| <h4 style="margin: 0;">❌ خطا در پردازش</h4> | |
| <p style="margin: 10px 0 0 0;">{error_message}</p> | |
| </div> | |
| """ | |
| def create_empty_display(): | |
| """ایجاد نمایش خالی""" | |
| return """ | |
| <div style="background: #000000; color: #00ff00; padding: 30px; text-align: center; border-radius: 8px; border: 2px dashed #00ff00;"> | |
| <h4 style="margin: 0 0 10px 0;">📚 سیستم حرفهای استخراج اطلاعات کتاب</h4> | |
| <p style="margin: 0;">لطفاً یک فایل PDF یا تصویر آپلود کنید</p> | |
| <p style="margin: 10px 0 0 0; font-size: 12px; color: #00cc00;">📄 ۵ صفحه اول با DPI 200 پردازش میشود</p> | |
| </div> | |
| """ | |
| # ============================================================================ | |
| # 🚀 اجرای سیستم حرفهای | |
| # ============================================================================ | |
| print("🎯 ایجاد رابط کاربری حرفهای...") | |
| professional_css = """ | |
| .gradio-container { | |
| background: #000000 !important; | |
| color: #00ff00 !important; | |
| } | |
| .gradio-container .panel { | |
| background: #000000 !important; | |
| border: 2px solid #00ff00 !important; | |
| } | |
| .gradio-container .button { | |
| background: linear-gradient(135deg, #001a00 0%, #004400 100%) !important; | |
| color: #00ff00 !important; | |
| border: 2px solid #00ff00 !important; | |
| font-weight: bold !important; | |
| } | |
| .gradio-container .button:hover { | |
| background: linear-gradient(135deg, #003300 0%, #006600 100%) !important; | |
| } | |
| """ | |
| with gr.Blocks(title="سیستم حرفهای استخراج کتاب", theme=gr.themes.Default(primary_hue="green"), css=professional_css) as demo: | |
| gr.Markdown(""" | |
| <div style="background: linear-gradient(135deg, #001a00 0%, #004400 100%); color: #00ff00; padding: 25px; border-radius: 12px; border: 3px solid #00ff00; font-family: 'Tahoma', sans-serif;"> | |
| <h1 style="text-align: center; margin: 0; color: #00ff00;">📚 سیستم حرفهای استخراج اطلاعات کتاب</h1> | |
| <p style="text-align: center; color: #aaffaa; margin: 10px 0; font-size: 16px;">پردازش ۵ صفحه اول با کیفیت DPI 200 - پرامپت پیشرفته</p> | |
| <div style="text-align: center; color: #88ff88; font-size: 14px;"> | |
| • کیفیت تصویر: DPI 200 • max_tokens: 2000 • پردازش ۵ صفحه • | |
| </div> | |
| </div> | |
| """) | |
| with gr.Row(): | |
| file_input = gr.File( | |
| label="📁 آپلود فایل کتاب (PDF یا تصویر)", | |
| file_types=[".pdf", ".jpg", ".jpeg", ".png"], | |
| height=100 | |
| ) | |
| with gr.Row(): | |
| process_btn = gr.Button( | |
| "🚀 شروع پردازش حرفهای (۵ صفحه با DPI 200)", | |
| variant="primary", | |
| size="lg", | |
| scale=2 | |
| ) | |
| with gr.Row(): | |
| output_display = gr.HTML( | |
| label="🎯 نتایج تحلیل حرفهای", | |
| value=create_empty_display() | |
| ) | |
| with gr.Row(): | |
| download_output = gr.File( | |
| label="📥 دانلود گزارش کامل", | |
| interactive=False | |
| ) | |
| with gr.Row(): | |
| stats_display = gr.Textbox( | |
| label="📊 وضعیت پردازش حرفهای", | |
| lines=2, | |
| interactive=False | |
| ) | |
| process_btn.click( | |
| fn=process_book_professional, | |
| inputs=[file_input], | |
| outputs=[output_display, download_output, stats_display] | |
| ) | |
| print("✅ سیستم حرفهای با DPI 200 و max_tokens 2000 آماده است!") | |
| print("🌐 در حال راهاندازی سرور Gradio...") | |
| # تغییرات برای Hugging Face Spaces | |
| if __name__ == "__main__": | |
| demo.launch( | |
| server_name="0.0.0.0", | |
| server_port=int(os.environ.get("PORT", 7860)), | |
| share=False | |
| ) | |