File size: 5,398 Bytes
f4395fd
e1882fa
dd990f8
 
e1882fa
dd990f8
 
f4395fd
dd990f8
 
 
 
f4395fd
dd990f8
 
 
f4395fd
 
 
 
dd990f8
 
f4395fd
dd990f8
 
 
 
e1882fa
dd990f8
e1882fa
dd990f8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e1882fa
dd990f8
 
 
 
 
 
 
 
 
 
e1882fa
dd990f8
 
 
 
 
e1882fa
dd990f8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e1882fa
 
dd990f8
 
 
 
 
 
 
 
 
 
 
 
 
f4395fd
dd990f8
 
 
 
 
f4395fd
dd990f8
 
 
 
 
 
 
 
 
 
 
c363a81
dd990f8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e1882fa
dd990f8
 
e1882fa
f4395fd
dd990f8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e1882fa
 
 
 
 
f4395fd
dd990f8
 
 
 
f4395fd
dd990f8
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
import os
import uuid
import io
import shutil
from PIL import Image
import pytesseract
import fitz  # PyMuPDF
import gradio as gr
from pptx import Presentation
from pptx.util import Inches, Pt
from docx import Document
from docx.shared import Cm

# -------------------------
# Configuration
# -------------------------
UPLOAD_DIR = "uploads"
os.makedirs(UPLOAD_DIR, exist_ok=True)
MAX_FILE_SIZE_MB = 500

# Explicit path to tesseract (HF Spaces Linux default)
pytesseract.pytesseract.tesseract_cmd = "/usr/bin/tesseract"

# -------------------------
# OCR Extraction
# -------------------------
def ocr_page_image(image: Image.Image):
    """
    Perform OCR on a PIL image and return list of text blocks.
    """
    data = pytesseract.image_to_data(image, output_type=pytesseract.Output.DICT)
    text_blocks = []
    n_boxes = len(data['level'])
    for i in range(n_boxes):
        text = data['text'][i].strip()
        if text:
            # Capture bounding box
            block = {
                'text': text,
                'left': data['left'][i],
                'top': data['top'][i],
                'width': data['width'][i],
                'height': data['height'][i]
            }
            text_blocks.append(block)
    return text_blocks

# -------------------------
# PDF โ†’ Word
# -------------------------
def pdf_to_word_ocr(pdf_doc, output_path):
    word_doc = Document()
    for page in pdf_doc:
        pix = page.get_pixmap()
        img = Image.frombytes("RGB", [pix.width, pix.height], pix.samples)
        blocks = ocr_page_image(img)
        page_text = "\n".join([b['text'] for b in blocks])
        word_doc.add_paragraph(page_text)
        word_doc.add_page_break()
    word_doc.save(output_path)

# -------------------------
# PDF โ†’ PowerPoint
# -------------------------
def pdf_to_ppt_ocr(pdf_doc, output_path):
    prs = Presentation()
    prs.slide_width = Inches(13.333)
    prs.slide_height = Inches(7.5)

    for page in pdf_doc:
        pix = page.get_pixmap()
        img = Image.frombytes("RGB", [pix.width, pix.height], pix.samples)
        blocks = ocr_page_image(img)

        slide = prs.slides.add_slide(prs.slide_layouts[6])
        # Place text boxes for each OCR block
        for block in blocks:
            left = Inches(block['left'] / 100)  # scale roughly
            top = Inches(block['top'] / 100)
            width = Inches(block['width'] / 100)
            height = Inches(block['height'] / 100)
            textbox = slide.shapes.add_textbox(left, top, width, height)
            tf = textbox.text_frame
            p = tf.add_paragraph()
            p.text = block['text']
            p.font.size = Pt(14)
        # Optional: insert full page image as reference below text
        img_stream = io.BytesIO()
        img.save(img_stream, format="PNG")
        img_stream.seek(0)
        slide.shapes.add_picture(img_stream, 0, 0, width=prs.slide_width, height=prs.slide_height)
        img_stream.close()

    prs.save(output_path)

# -------------------------
# Main Conversion
# -------------------------
def convert_pdf(file_path, target_format):
    if file_path is None:
        raise gr.Error("Please upload a PDF file.")

    if not file_path.lower().endswith(".pdf"):
        raise gr.Error("Only PDF files are supported.")

    unique_id = str(uuid.uuid4())
    pdf_path = os.path.join(UPLOAD_DIR, f"{unique_id}.pdf")
    shutil.copy(file_path, pdf_path)

    pdf_doc = fitz.open(pdf_path)
    if len(pdf_doc) == 0:
        pdf_doc.close()
        os.remove(pdf_path)
        raise gr.Error("The uploaded PDF is empty.")

    output_file = None
    try:
        if target_format == "Word Document (.docx)":
            output_file = os.path.join(UPLOAD_DIR, f"{unique_id}.docx")
            pdf_to_word_ocr(pdf_doc, output_file)
        else:
            output_file = os.path.join(UPLOAD_DIR, f"{unique_id}.pptx")
            pdf_to_ppt_ocr(pdf_doc, output_file)
    finally:
        pdf_doc.close()
        os.remove(pdf_path)

    return output_file

# -------------------------
# Clear Uploads
# -------------------------
def clear_files():
    for f in os.listdir(UPLOAD_DIR):
        try:
            os.remove(os.path.join(UPLOAD_DIR, f))
        except:
            pass
    return None

# -------------------------
# Gradio UI
# -------------------------
with gr.Blocks(title="PDF Converter | OCR Editable") as demo:
    gr.Markdown("""
    # ๐Ÿ“„ PDF Converter (OCR for Image-Based PDFs)
    Convert scanned or image-based PDFs into **editable Word or PowerPoint** files.
    """)

    with gr.Row():
        pdf_input = gr.File(
            label="Upload PDF File",
            file_types=[".pdf"]
        )
        format_choice = gr.Radio(
            choices=["Word Document (.docx)", "PowerPoint (.pptx)"],
            value="Word Document (.docx)",
            label="Select Output Format"
        )

    with gr.Row():
        convert_btn = gr.Button("Convert")
        clear_btn = gr.Button("Clear Uploaded Files")

    output_file = gr.File(label="Download Converted File")

    convert_btn.click(
        fn=convert_pdf,
        inputs=[pdf_input, format_choice],
        outputs=output_file
    )
    clear_btn.click(
        fn=clear_files,
        outputs=output_file
    )

# -------------------------
# Launch
# -------------------------
demo.launch(server_name="0.0.0.0", server_port=7860)