feat: extract_worker에 LibreOffice 텍스트 추출 추가 (오피스 포맷)

- xlsx, docx, pptx, odt, ods, odp, odoc, osheet 지원
- LibreOffice --convert-to txt로 텍스트 추출 (60s timeout)
- 추가 의존성 없음 (Docker에 이미 설치된 LibreOffice 사용)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Hyungi Ahn
2026-04-03 11:12:19 +09:00
parent 9fd44ab268
commit 45448b4036

View File

@@ -1,5 +1,6 @@
"""텍스트 추출 워커 — kordoc 호출 또는 직접 파일 읽기"""
"""텍스트 추출 워커 — kordoc / LibreOffice / 직접 읽기"""
import subprocess
from datetime import datetime, timezone
from pathlib import Path
@@ -16,6 +17,8 @@ logger = setup_logger("extract_worker")
KORDOC_FORMATS = {"hwp", "hwpx", "pdf"}
# 직접 읽기 가능한 텍스트 포맷
TEXT_FORMATS = {"md", "txt", "csv", "json", "xml", "html"}
# LibreOffice로 텍스트 추출 가능한 포맷
OFFICE_FORMATS = {"xlsx", "xls", "docx", "doc", "pptx", "ppt", "odt", "ods", "odp", "odoc", "osheet"}
# OCR 필요 이미지 포맷 (Phase 2)
IMAGE_FORMATS = {"jpg", "jpeg", "png", "tiff", "tif", "bmp", "gif"}
@@ -73,6 +76,33 @@ async def process(document_id: int, session: AsyncSession) -> None:
logger.info(f"[kordoc] {doc.file_path} ({len(doc.extracted_text)}자)")
return
# 오피스 포맷 — LibreOffice 텍스트 변환
if fmt in OFFICE_FORMATS:
if not full_path.exists():
raise FileNotFoundError(f"파일 없음: {full_path}")
tmp_dir = Path("/tmp/extract_work")
tmp_dir.mkdir(exist_ok=True)
try:
result = subprocess.run(
["libreoffice", "--headless", "--convert-to", "txt:Text", "--outdir", str(tmp_dir), str(full_path)],
capture_output=True, text=True, timeout=60,
)
txt_file = tmp_dir / f"{full_path.stem}.txt"
if txt_file.exists():
text = txt_file.read_text(encoding="utf-8", errors="replace")
doc.extracted_text = text[:15000]
doc.extracted_at = datetime.now(timezone.utc)
doc.extractor_version = "libreoffice"
txt_file.unlink()
logger.info(f"[LibreOffice] {doc.file_path} ({len(text)}자)")
return
else:
raise RuntimeError(f"LibreOffice 변환 결과물 없음: {result.stderr[:200]}")
except subprocess.TimeoutExpired:
raise RuntimeError(f"LibreOffice 텍스트 추출 timeout (60s)")
# 미지원 포맷
doc.extracted_text = ""
doc.extracted_at = datetime.now(timezone.utc)