from __future__ import annotations import base64 import binascii import json import mimetypes import re import shutil from pathlib import Path from typing import Any from .documents import DocumentPage _IMAGE_EXTENSION = { "image/png": ".png", "image/jpeg": ".jpg", "image/webp": ".webp", } def _decode_image(payload: dict[str, Any], output_stem: Path) -> Path | None: encoded = payload.get("annotated_image_base64") if not encoded: return None mime = str(payload.get("annotated_image_mime") or "image/png") extension = _IMAGE_EXTENSION.get(mime) or mimetypes.guess_extension(mime) or ".png" output = output_stem.with_suffix(extension) try: output.write_bytes(base64.b64decode(encoded, validate=True)) except (binascii.Error, ValueError) as exc: raise ValueError(f"Worker returned an invalid base64 image: {exc}") from exc return output def _plain_from_markdown(markdown: str) -> str: text = re.sub(r"```[^\n]*\n(.*?)```", r"\1", markdown, flags=re.DOTALL) text = re.sub(r"!\[[^\]]*\]\([^)]*\)", "", text) text = re.sub(r"\[([^\]]+)\]\([^)]*\)", r"\1", text) text = re.sub(r"<[^>]+>", "", text) text = re.sub(r"^[#>*+-]+\s*", "", text, flags=re.MULTILINE) text = re.sub(r"[`*_~]", "", text) return text.strip() def build_exports( *, run_dir: Path, model_id: str, model_label: str, pages: list[DocumentPage], responses: list[dict[str, Any]], ) -> tuple[list[tuple[str, str]], str, str, str, str]: output_dir = run_dir / "results" annotated_dir = output_dir / "annotated" annotated_dir.mkdir(parents=True, exist_ok=True) annotated_gallery: list[tuple[str, str]] = [] markdown_parts = [f"# OCR result — {model_label}"] text_parts: list[str] = [] raw_pages: list[dict[str, Any]] = [] for page, response in zip(pages, responses, strict=True): image = _decode_image( response, annotated_dir / f"page_{page.page_number:04d}", ) if image is None: image = annotated_dir / f"page_{page.page_number:04d}.png" shutil.copy2(page.image_path, image) annotated_gallery.append((str(image), f"Page {page.page_number}")) markdown = str(response.get("markdown") or "").strip() text = str(response.get("text") or "").strip() if not text and markdown: text = _plain_from_markdown(markdown) warnings = [str(item) for item in (response.get("warnings", []) or [])] warning_markdown = "" warning_text = "" if warnings: warning_markdown = "\n\n> ⚠ " + "\n> ⚠ ".join(warnings) warning_text = "\n\n[Warnings]\n- " + "\n- ".join(warnings) markdown_parts.append( f"\n## Page {page.page_number}\n\n" f"{markdown or text or '(empty result)'}{warning_markdown}" ) text_parts.append( f"===== Page {page.page_number} =====\n" f"{text or '(empty result)'}{warning_text}" ) raw_page = dict(response) raw_page.pop("annotated_image_base64", None) raw_page["page_number"] = page.page_number raw_pages.append(raw_page) markdown_document = "\n".join(markdown_parts).strip() + "\n" text_document = "\n\n".join(text_parts).strip() + "\n" raw_payload = { "schema_version": "1.0", "model": model_id, "model_label": model_label, "pages": raw_pages, } json_document = json.dumps(raw_payload, ensure_ascii=False, indent=2) output_dir.mkdir(parents=True, exist_ok=True) (output_dir / "result.md").write_text(markdown_document, encoding="utf-8") (output_dir / "result.txt").write_text(text_document, encoding="utf-8") (output_dir / "result.json").write_text(json_document, encoding="utf-8") archive_base = run_dir.parent / f"{run_dir.name}_ocr_result" archive = shutil.make_archive(str(archive_base), "zip", run_dir) return annotated_gallery, markdown_document, text_document, json_document, archive