pdf-to-markdown / app.py
Biifruu's picture
Update app.py
7ad1608 verified
import spaces
import gradio as gr
import fitz # PyMuPDF
from PIL import Image
import pytesseract
import os
import numpy as np
import cv2
def clean_ocr_text(text):
lines = text.splitlines()
cleaned_lines = []
for line in lines:
line = line.strip()
if line and not line.isspace():
cleaned_lines.append(line)
return "\n".join(cleaned_lines)
def extract_text_markdown(doc, image_paths, page_index, seen_xrefs):
markdown_output = f"\n## Página {page_index + 1}\n\n"
image_counter = 1
elements = []
page = doc[0]
blocks = page.get_text("dict")["blocks"]
for b in blocks:
y = b["bbox"][1]
if b["type"] == 0:
for line in b["lines"]:
line_y = line["bbox"][1]
line_text = " ".join([span["text"] for span in line["spans"]]).strip()
max_font_size = max([span.get("size", 10) for span in line["spans"]])
if line_text:
elements.append((line_y, line_text, max_font_size))
images_on_page = page.get_images(full=True)
for img_index, img in enumerate(images_on_page):
xref = img[0]
if xref in seen_xrefs:
continue
seen_xrefs.add(xref)
try:
base_image = page.parent.extract_image(xref)
image_bytes = base_image["image"]
ext = base_image["ext"]
image_path = f"/tmp/imagen_p{page_index + 1}_{img_index + 1}.{ext}"
with open(image_path, "wb") as f:
f.write(image_bytes)
image_paths.append(image_path)
elements.append((float("inf") - img_index, f"\n\n![imagen_{image_counter}]({image_path})\n", 10))
image_counter += 1
except Exception as e:
elements.append((float("inf"), f"[Error imagen: {e}]", 10))
elements.sort(key=lambda x: x[0])
previous_y = None
for y, text, font_size in elements:
is_header = font_size >= 14
if previous_y is not None and abs(y - previous_y) > 10:
markdown_output += "\n"
if is_header:
markdown_output += f"\n### {text.strip()}\n"
else:
markdown_output += text.strip() + "\n"
previous_y = y
markdown_output += "\n---\n\n"
return markdown_output.strip()
@spaces.GPU
def convert(pdf_file):
doc = fitz.open(pdf_file)
markdown_output = ""
image_paths = []
seen_xrefs = set()
for page_num in range(len(doc)):
page = doc[page_num]
text = page.get_text("text").strip()
if len(text) > 30:
markdown_output += extract_text_markdown([page], image_paths, page_num, seen_xrefs) + "\n"
else:
markdown_output += f"\n## Página {page_num + 1}\n\n"
pix = page.get_pixmap(dpi=300)
img = Image.frombytes("RGB", [pix.width, pix.height], pix.samples)
image_path = f"/tmp/ocr_page_{page_num + 1}.jpg"
img.save(image_path)
image_paths.append(image_path)
markdown_output += f"![imagen_pagina_{page_num + 1}]({image_path})\n"
try:
ocr_text = pytesseract.image_to_string(img)
except pytesseract.TesseractError:
ocr_text = ""
ocr_text = clean_ocr_text(ocr_text)
if ocr_text.strip():
markdown_output += ocr_text + "\n"
try:
img_cv = np.array(img)
gray = cv2.cvtColor(img_cv, cv2.COLOR_RGB2GRAY)
_, thresh = cv2.threshold(gray, 200, 255, cv2.THRESH_BINARY_INV)
contours, _ = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
contours = sorted(contours, key=cv2.contourArea, reverse=True)[:5]
for i, cnt in enumerate(contours):
x, y, w, h = cv2.boundingRect(cnt)
area = w * h
if area > 5000:
region = img_cv[y:y+h, x:x+w]
detected_path = f"/tmp/img_detectada_p{page_num + 1}_{i + 1}.jpg"
Image.fromarray(region).save(detected_path)
image_paths.append(detected_path)
markdown_output += f"\n\n![imagen_detectada]({detected_path})\n"
except Exception as e:
markdown_output += f"\n\n[Error al detectar imágenes embebidas: {e}]\n"
markdown_output += "\n---\n\n"
markdown_path = "/tmp/resultado.md"
with open(markdown_path, "w", encoding="utf-8") as f:
f.write(markdown_output)
return markdown_output.strip(), image_paths, markdown_path
# Interfaz Gradio compatible
with gr.Blocks() as demo:
with gr.Row():
pdf_input = gr.File(label="Sube tu PDF", type="filepath")
submit_btn = gr.Button("Procesar PDF")
# 🔄 Botón refrescar eliminado
markdown_output = gr.Textbox(label="Markdown estructurado", lines=25, interactive=True)
gallery_output = gr.Gallery(label="Imágenes extraídas", type="file")
download_md = gr.File(label="Descargar .md")
submit_btn.click(fn=convert, inputs=[pdf_input], outputs=[markdown_output, gallery_output, download_md])
demo.launch()