Spaces:
Build error
Build error
| # modules/file_processor.py | |
| import os | |
| import logging | |
| from typing import Optional | |
| from docx import Document | |
| import PyPDF2 | |
| from PIL import Image | |
| import pytesseract | |
| from pdf2image import convert_from_path | |
| import cv2 | |
| import numpy as np | |
| logger = logging.getLogger(__name__) | |
| class FileProcessor: | |
| def __init__(self): | |
| logger.info("FileProcessor initialized with OCR support") | |
| def extract_text(self, file_path: str) -> str: | |
| if not os.path.exists(file_path): | |
| raise FileNotFoundError(f"File not found: {file_path}") | |
| _, ext = os.path.splitext(file_path) | |
| ext = ext.lower() | |
| logger.info(f"Extracting text from {ext} file") | |
| try: | |
| if ext == '.txt': | |
| return self._extract_from_txt(file_path) | |
| elif ext == '.docx': | |
| return self._extract_from_docx(file_path) | |
| elif ext == '.pdf': | |
| return self._extract_from_pdf(file_path) | |
| else: | |
| raise ValueError(f"Unsupported file type: {ext}") | |
| except Exception as e: | |
| logger.error(f"Error extracting text: {e}") | |
| raise | |
| def _extract_from_txt(self, path: str) -> str: | |
| try: | |
| with open(path, 'r', encoding='utf-8') as f: | |
| text = f.read() | |
| logger.info(f"Extracted {len(text)} chars from TXT") | |
| return text | |
| except UnicodeDecodeError: | |
| with open(path, 'r', encoding='latin-1') as f: | |
| text = f.read() | |
| logger.warning("Used latin-1 encoding for TXT") | |
| return text | |
| def _extract_from_docx(self, path: str) -> str: | |
| doc = Document(path) | |
| paragraphs = [] | |
| for para in doc.paragraphs: | |
| text = para.text.strip() | |
| if text: | |
| paragraphs.append(text) | |
| text = '\n\n'.join(paragraphs) | |
| logger.info(f"Extracted {len(text)} chars from DOCX") | |
| return text | |
| def _extract_from_pdf(self, path: str) -> str: | |
| """Extract text from PDF with OCR fallback""" | |
| # پہلے PyPDF2 سے کوشش کریں | |
| text = self._extract_with_pypdf2(path) | |
| # اگر کم از کم 30 الفاظ نہیں ملے تو OCR استعمال کریں | |
| word_count = len(text.split()) | |
| if word_count < 30: | |
| logger.info(f"PyPDF2 extracted only {word_count} words. Trying OCR...") | |
| ocr_text = self._extract_with_ocr(path) | |
| # جو زیادہ text دے وہ استعمال کریں | |
| if len(ocr_text) > len(text): | |
| text = ocr_text | |
| logger.info(f"OCR extraction successful: {len(text)} chars") | |
| else: | |
| logger.warning("OCR did not improve extraction") | |
| logger.info(f"Final extracted text: {len(text)} chars from PDF") | |
| return text | |
| def _extract_with_pypdf2(self, path: str) -> str: | |
| """Regular PDF text extraction""" | |
| text_parts = [] | |
| try: | |
| with open(path, 'rb') as f: | |
| reader = PyPDF2.PdfReader(f) | |
| num_pages = len(reader.pages) | |
| logger.info(f"Reading {num_pages} pages with PyPDF2") | |
| for page_num in range(num_pages): | |
| page = reader.pages[page_num] | |
| text = page.extract_text() | |
| if text.strip(): | |
| text_parts.append(text) | |
| return '\n\n'.join(text_parts) | |
| except Exception as e: | |
| logger.error(f"PyPDF2 extraction failed: {e}") | |
| return "" | |
| def _extract_with_ocr(self, path: str) -> str: | |
| """OCR-based extraction for scanned PDFs""" | |
| text_parts = [] | |
| try: | |
| # PDF کو images میں convert کریں | |
| logger.info("Converting PDF to images for OCR...") | |
| images = convert_from_path(path, dpi=300) | |
| logger.info(f"Processing {len(images)} pages with OCR...") | |
| for i, image in enumerate(images): | |
| logger.info(f"OCR on page {i+1}/{len(images)}...") | |
| # Image کو pre-process کریں (better OCR) | |
| processed_image = self._preprocess_image(image) | |
| # OCR - Urdu اور English دونوں | |
| try: | |
| # پہلے English try کریں | |
| text_eng = pytesseract.image_to_string( | |
| processed_image, | |
| lang='eng', | |
| config='--psm 6' | |
| ) | |
| # پھر Urdu try کریں | |
| text_urd = pytesseract.image_to_string( | |
| processed_image, | |
| lang='urd', | |
| config='--psm 6' | |
| ) | |
| # جو زیادہ text دے وہ استعمال کریں | |
| if len(text_eng) > len(text_urd): | |
| text = text_eng | |
| logger.info(f"Page {i+1}: English OCR - {len(text)} chars") | |
| else: | |
| text = text_urd | |
| logger.info(f"Page {i+1}: Urdu OCR - {len(text)} chars") | |
| if text.strip(): | |
| text_parts.append(text.strip()) | |
| except Exception as e: | |
| logger.error(f"OCR failed on page {i+1}: {e}") | |
| # Fallback to English only | |
| try: | |
| text = pytesseract.image_to_string(processed_image, lang='eng') | |
| if text.strip(): | |
| text_parts.append(text.strip()) | |
| except: | |
| logger.error(f"English OCR also failed on page {i+1}") | |
| final_text = '\n\n'.join(text_parts) | |
| logger.info(f"OCR complete: {len(final_text)} chars extracted") | |
| return final_text | |
| except Exception as e: | |
| logger.error(f"OCR extraction failed: {e}") | |
| return "" | |
| def _preprocess_image(self, pil_image): | |
| """Image کو OCR کے لیے بہتر بنائیں""" | |
| try: | |
| # PIL to numpy array | |
| img = np.array(pil_image) | |
| # Convert to grayscale | |
| if len(img.shape) == 3: | |
| gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY) | |
| else: | |
| gray = img | |
| # Increase contrast | |
| alpha = 1.5 # Contrast | |
| beta = 0 # Brightness | |
| adjusted = cv2.convertScaleAbs(gray, alpha=alpha, beta=beta) | |
| # Denoise | |
| denoised = cv2.fastNlMeansDenoising(adjusted, None, 10, 7, 21) | |
| # Threshold | |
| _, thresh = cv2.threshold( | |
| denoised, 0, 255, | |
| cv2.THRESH_BINARY + cv2.THRESH_OTSU | |
| ) | |
| # Convert back to PIL | |
| processed = Image.fromarray(thresh) | |
| return processed | |
| except Exception as e: | |
| logger.error(f"Image preprocessing failed: {e}") | |
| return pil_image | |
| def detect_language(self, text: str) -> str: | |
| """زبان کی شناخت""" | |
| urdu_chars = sum(1 for c in text if '\u0600' <= c <= '\u06FF') | |
| english_chars = sum(1 for c in text if c.isalpha() and c.isascii()) | |
| total_chars = urdu_chars + english_chars | |
| if total_chars == 0: | |
| return 'unknown' | |
| urdu_percent = (urdu_chars / total_chars) * 100 | |
| if urdu_percent > 70: | |
| return 'urdu' | |
| elif urdu_percent < 30: | |
| return 'english' | |
| else: | |
| return 'mixed' |