from fastapi import FastAPI from pydantic import BaseModel import fitz # PyMuPDF import os import psycopg2 from datetime import datetime import json DB_DSN = os.getenv("DB_DSN", "postgresql://doc_user:doc_password@db:5432/doc_translator") app = FastAPI() class ProcessRequest(BaseModel): document_id: int original_path: str def get_db_conn(): return psycopg2.connect(DB_DSN) @app.post("/process-document") def process_document(req: ProcessRequest): doc_id = req.document_id path = req.original_path if not os.path.exists(path): _set_status(doc_id, "failed", f"File not found: {path}") return {"ok": False, "error": "file_not_found"} try: _set_status(doc_id, "processing", None) # 1. Открываем PDF pdf = fitz.open(path) page_count = pdf.page_count # Обновим page_count в documents _update_document_page_count(doc_id, page_count) # 2. Проходим по страницам и блокам текста translations = [] for page_index in range(page_count): page = pdf[page_index] blocks = page.get_text("blocks") # список блоков: (x0, y0, x1, y1, text, block_no, block_type, ...) segment_index = 0 for b in blocks: x0, y0, x1, y1, text, *_rest = b if not text.strip(): continue segment_index += 1 # TODO: здесь вызвать DeepSeek через Ollama translated = _translate_text(text) bbox = {"x0": x0, "y0": y0, "x1": x1, "y1": y1} translations.append({ "document_id": doc_id, "page_number": page_index + 1, "segment_index": segment_index, "source_text": text, "translated_text": translated, "bbox": bbox, }) # 3. Сохраняем сегменты в таблицу translations _insert_translations(translations) # 4. Собираем новый PDF с русским текстом output_path = _build_translated_pdf(path, pdf, translations, doc_id) # 5. Обновляем запись в documents _set_status(doc_id, "completed", None, output_path=output_path) return {"ok": True, "document_id": doc_id, "output_path": output_path} except Exception as e: _set_status(doc_id, "failed", str(e)) return {"ok": False, "error": str(e)} def _set_status(doc_id: int, status: str, error: str | None, output_path: str | None = None): conn = get_db_conn() cur = conn.cursor() cur.execute( """ UPDATE documents SET status = %s, error_message = %s, output_path = COALESCE(%s, output_path), updated_at = %s WHERE id = %s """, (status, error, output_path, datetime.now(), doc_id), ) conn.commit() cur.close() conn.close() def _update_document_page_count(doc_id: int, page_count: int): conn = get_db_conn() cur = conn.cursor() cur.execute( "UPDATE documents SET page_count = %s WHERE id = %s", (page_count, doc_id), ) conn.commit() cur.close() conn.close() def _translate_text(text: str) -> str: """ Заглушка перевода. Здесь надо: - взять активный промпт из таблицы prompts; - вызвать Ollama (DeepSeek) HTTP-запросом; - вернуть перевод. Пока просто возвращаем исходный текст. """ return text # TODO: заменить на реальный вызов LLM def _insert_translations(rows: list[dict]): conn = get_db_conn() cur = conn.cursor() for r in rows: cur.execute( """ INSERT INTO translations (document_id, page_number, segment_index, source_text, translated_text, bbox, model_name, created_at) VALUES (%s, %s, %s, %s, %s, %s::jsonb, %s, %s) """, ( r["document_id"], r["page_number"], r["segment_index"], r["source_text"], r["translated_text"], json.dumps(r["bbox"]), "deepseek-r1:7b", datetime.now(), ), ) conn.commit() cur.close() conn.close() def _build_translated_pdf(original_path: str, pdf: fitz.Document, segments: list[dict], doc_id: int) -> str: """ Строим новый PDF: - берем исходную страницу как фон; - поверх в тех же bbox'ах рисуем русский текст. Это упрощенный вариант: не идеальная типографика, но структура и картинки сохраняются. """ import pathlib import json # Индексируем переводы по (page, segment_index) или лучше по bbox by_page: dict[int, list[dict]] = {} for s in segments: by_page.setdefault(s["page_number"], []).append(s) output_dir = os.getenv("OUTPUT_DIR", "/data/output") os.makedirs(output_dir, exist_ok=True) basename = os.path.basename(original_path) name, _ext = os.path.splitext(basename) output_path = os.path.join(output_dir, f"{name}_ru.pdf") # Создаем новый PDF out_doc = fitz.open() for page_index in range(pdf.page_count): src_page = pdf[page_index] rect = src_page.rect new_page = out_doc.new_page(width=rect.width, height=rect.height) # Рендерим исходную страницу как картинку (фон) pix = src_page.get_pixmap(dpi=150) img_rect = fitz.Rect(0, 0, rect.width, rect.height) new_page.insert_image(img_rect, pixmap=pix) # Наносим переведенный текст поверх page_number = page_index + 1 for seg in by_page.get(page_number, []): bbox = seg["bbox"] r = fitz.Rect(bbox["x0"], bbox["y0"], bbox["x1"], bbox["y1"]) new_page.insert_textbox( r, seg["translated_text"], fontsize=8, fontname="helv", align=fitz.TEXT_ALIGN_LEFT, ) out_doc.save(output_path) out_doc.close() pdf.close() return output_path