Spaces:
Sleeping
Sleeping
| import re | |
| import fitz | |
| from typing import Dict, Tuple, Union | |
| class PDFQualityChecker: | |
| """ | |
| Bộ lọc chất lượng PDF cơ bản trước khi xử lý. | |
| Đánh giá lỗi font, lỗi encode, ký tự hỏng, OCR kém, v.v. | |
| """ | |
| def __init__(self, | |
| max_invalid_ratio: float = 0.2, | |
| max_whitespace_ratio: float = 0.2, | |
| max_short_line_ratio: float = 0.3, | |
| min_total_chars: int = 300): | |
| self.max_invalid_ratio = max_invalid_ratio | |
| self.max_whitespace_ratio = max_whitespace_ratio | |
| self.max_short_line_ratio = max_short_line_ratio | |
| self.min_total_chars = min_total_chars | |
| # Regex nhận diện ký tự hợp lệ (chữ, số, dấu tiếng Việt, ký hiệu cơ bản) | |
| self.valid_char_pattern = re.compile(r"[A-Za-zÀ-ỹĐđ0-9.,:;!?()\"'’”“–\-_\s]") | |
| # ============================================================ | |
| # 1️⃣ HÀM CHÍNH | |
| # ============================================================ | |
| def evaluate(self, pdf: Union[str, fitz.Document]) -> Tuple[bool, Dict]: | |
| """ | |
| Đánh giá chất lượng PDF. | |
| - pdf: đường dẫn (str) hoặc fitz.Document đã mở | |
| - trả (is_good, metrics) | |
| """ | |
| # ---- Chuẩn hóa input ---- | |
| if isinstance(pdf, str): | |
| try: | |
| doc = fitz.open(pdf) | |
| except Exception as e: | |
| return False, {"check_mess": f"❌ Không mở được file: {e}"} | |
| elif isinstance(pdf, fitz.Document): | |
| doc = pdf | |
| else: | |
| raise TypeError("pdf phải là str hoặc fitz.Document") | |
| # ---- Bắt đầu thống kê ---- | |
| text_all = "" | |
| short_lines = 0 | |
| all_lines = 0 | |
| for page in doc: | |
| text = page.get_text("text") or "" | |
| if not text.strip(): | |
| continue | |
| lines = text.splitlines() | |
| for line in lines: | |
| if not line.strip(): | |
| continue | |
| all_lines += 1 | |
| if len(line.strip()) < 10: | |
| short_lines += 1 | |
| text_all += text + "\n" | |
| total_chars = len(text_all) | |
| if total_chars < self.min_total_chars: | |
| return False, { | |
| "check_mess": "❌ File quá ngắn hoặc không có text layer", | |
| "total_chars": total_chars, | |
| } | |
| # ---- Tính tỷ lệ lỗi ---- | |
| valid_chars = sum(1 for ch in text_all if self.valid_char_pattern.match(ch)) | |
| invalid_chars = total_chars - valid_chars | |
| invalid_ratio = invalid_chars / total_chars | |
| whitespace_excess = len(re.findall(r" {3,}", text_all)) | |
| whitespace_ratio = whitespace_excess / total_chars | |
| short_line_ratio = short_lines / max(all_lines, 1) | |
| # ---- Đưa ra kết luận ---- | |
| is_good = ( | |
| invalid_ratio <= self.max_invalid_ratio | |
| and whitespace_ratio <= self.max_whitespace_ratio | |
| and short_line_ratio < 1 | |
| ) | |
| if not is_good: | |
| if invalid_ratio > self.max_invalid_ratio: | |
| check_mess = "❌ Nhiều ký tự lỗi / encode sai" | |
| elif whitespace_ratio > self.max_whitespace_ratio: | |
| check_mess = "❌ Nhiều khoảng trắng thừa" | |
| elif short_line_ratio >= 1: | |
| check_mess = "⚠️ OCR hoặc mất ký tự" | |
| else: | |
| check_mess = "❌ Văn bản lỗi nặng" | |
| else: | |
| check_mess = "✅ Đạt yêu cầu" | |
| metrics = { | |
| "check_mess": check_mess, | |
| "total_chars": total_chars, | |
| "invalid_ratio": round(invalid_ratio, 3), | |
| "whitespace_ratio": round(whitespace_ratio, 3), | |
| "short_line_ratio": round(short_line_ratio, 3), | |
| } | |
| return is_good, metrics | |