ShamsKhan404 commited on
Commit
cdc53f1
·
verified ·
1 Parent(s): 6c11ca4

Upload gradio_app.py

Browse files
Files changed (1) hide show
  1. gradio_app.py +342 -0
gradio_app.py ADDED
@@ -0,0 +1,342 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import shutil
5
+ import uuid
6
+ from pathlib import Path
7
+
8
+ import spaces
9
+ import fitz
10
+ import gradio as gr
11
+
12
+ from app import pdf_engine as editor
13
+
14
+
15
+ def upload_pdf_gradio(pdf_file):
16
+ if not pdf_file:
17
+ raise gr.Error("Please upload a PDF.")
18
+
19
+ source = Path(pdf_file)
20
+ if source.suffix.lower() != ".pdf":
21
+ raise gr.Error("Please upload a PDF file.")
22
+
23
+ try:
24
+ raw = source.read_bytes()
25
+ except Exception as exc:
26
+ raise gr.Error(f"Could not read PDF: {exc}")
27
+
28
+ if len(raw) > editor.MAX_UPLOAD_BYTES:
29
+ raise gr.Error(
30
+ f"PDF must be smaller than "
31
+ f"{editor.MAX_UPLOAD_BYTES // (1024 * 1024)} MB."
32
+ )
33
+
34
+ if not raw.startswith(b"%PDF-"):
35
+ raise gr.Error("The uploaded file is not a valid PDF.")
36
+
37
+ try:
38
+ pdf = fitz.open(stream=raw, filetype="pdf")
39
+ except Exception as exc:
40
+ raise gr.Error("Could not read this PDF.") from exc
41
+
42
+ if pdf.needs_pass:
43
+ pdf.close()
44
+ raise gr.Error("Password-protected PDFs are not supported.")
45
+
46
+ if len(pdf) == 0 or len(pdf) > editor.MAX_PAGES:
47
+ count = len(pdf)
48
+ pdf.close()
49
+ raise gr.Error(
50
+ f"PDF must have between 1 and {editor.MAX_PAGES} pages. "
51
+ f"This file has {count} pages."
52
+ )
53
+
54
+ document_id = uuid.uuid4().hex
55
+ target = editor.DATA_DIR / document_id
56
+
57
+ try:
58
+ target.mkdir(parents=True, exist_ok=False)
59
+ (target / "original.pdf").write_bytes(raw)
60
+
61
+ objects = editor.extract_native_objects(pdf, target)
62
+
63
+ pages = [
64
+ {
65
+ "number": i,
66
+ "width": float(page.rect.width),
67
+ "height": float(page.rect.height),
68
+ "hasText": any(
69
+ obj["page"] == i
70
+ and obj.get("type", "text") == "text"
71
+ for obj in objects
72
+ ),
73
+ "rotation": int(page.rotation),
74
+ "analyzed": False,
75
+ "regions": [],
76
+ }
77
+ for i, page in enumerate(pdf)
78
+ ]
79
+
80
+ model = {
81
+ "id": document_id,
82
+ "filename": source.name,
83
+ "pages": pages,
84
+ "objects": objects,
85
+ }
86
+
87
+ (target / "document.json").write_text(
88
+ json.dumps(model, ensure_ascii=False, indent=2),
89
+ encoding="utf-8",
90
+ )
91
+
92
+ except Exception:
93
+ shutil.rmtree(target, ignore_errors=True)
94
+ raise
95
+ finally:
96
+ pdf.close()
97
+
98
+ with editor._document_storage_lock:
99
+ editor.purge_document_storage(keep_id=document_id)
100
+
101
+ return (
102
+ document_id,
103
+ 0,
104
+ json.dumps(
105
+ [obj for obj in objects if obj["page"] == 0],
106
+ ensure_ascii=False,
107
+ indent=2,
108
+ ),
109
+ "[]",
110
+ render_page(document_id, 0),
111
+ f"### PDF loaded\n\n**File:** `{source.name}`\n\n"
112
+ f"**Pages:** `{len(pages)}`",
113
+ )
114
+
115
+
116
+ def render_page(document_id: str, page_number: int):
117
+ if not document_id:
118
+ return None
119
+
120
+ folder = editor.document_dir(document_id)
121
+ pdf_path = folder / "original.pdf"
122
+
123
+ if not pdf_path.exists():
124
+ return None
125
+
126
+ try:
127
+ page_number = int(page_number)
128
+ pdf = fitz.open(pdf_path)
129
+
130
+ if page_number < 0 or page_number >= len(pdf):
131
+ pdf.close()
132
+ return None
133
+
134
+ page = pdf[page_number]
135
+ pixmap = page.get_pixmap(
136
+ matrix=fitz.Matrix(1.5, 1.5),
137
+ alpha=False,
138
+ )
139
+
140
+ image_path = folder / f"gradio-page-{page_number}.png"
141
+ pixmap.save(image_path)
142
+ pdf.close()
143
+
144
+ return str(image_path)
145
+
146
+ except Exception as exc:
147
+ raise gr.Error(f"Could not render page: {exc}")
148
+
149
+
150
+ def change_page(document_id: str, page_number: int):
151
+ if not document_id:
152
+ return None
153
+
154
+ try:
155
+ return render_page(document_id, int(page_number))
156
+ except Exception as exc:
157
+ raise gr.Error(f"Could not change page: {exc}")
158
+
159
+
160
+ @spaces.GPU(duration=120)
161
+ def analyze_page_gradio(document_id: str, page_number: int):
162
+ if not document_id:
163
+ raise gr.Error("Upload a PDF first.")
164
+
165
+ try:
166
+ page_number = int(page_number)
167
+
168
+ result = editor.analyze_page(
169
+ document_id=document_id,
170
+ page_number=page_number,
171
+ refresh=False,
172
+ )
173
+
174
+ preview = render_page(document_id, page_number)
175
+
176
+ objects_json = json.dumps(
177
+ result["objects"],
178
+ indent=2,
179
+ ensure_ascii=False,
180
+ )
181
+
182
+ regions_json = json.dumps(
183
+ result["regions"],
184
+ indent=2,
185
+ ensure_ascii=False,
186
+ )
187
+
188
+ status = (
189
+ "### Analysis complete\n\n"
190
+ f"- OCR model: `{result['ocrModel']}`\n"
191
+ f"- Layout model: `{result['layoutModel']}`\n"
192
+ f"- OCR detections: `{result['ocrDetections']}`\n"
193
+ f"- Font model: `{result['fontModel']}`"
194
+ )
195
+
196
+ if result.get("fontModelError"):
197
+ status += f"\n\n> FontID: `{result['fontModelError']}`"
198
+
199
+ return preview, objects_json, regions_json, status
200
+
201
+ except editor.HTTPException as exc:
202
+ raise gr.Error(exc.detail)
203
+ except Exception as exc:
204
+ raise gr.Error(f"Page analysis failed: {exc}")
205
+
206
+
207
+ def export_pdf_gradio(
208
+ document_id: str,
209
+ objects_json: str,
210
+ regions_json: str,
211
+ ):
212
+ if not document_id:
213
+ raise gr.Error("Upload a PDF first.")
214
+
215
+ try:
216
+ objects_data = json.loads(objects_json or "[]")
217
+ regions_data = json.loads(regions_json or "[]")
218
+
219
+ request = editor.ExportRequest(
220
+ objects=objects_data,
221
+ tableGroups=regions_data,
222
+ )
223
+
224
+ output_path = editor.export_pdf(
225
+ document_id=document_id,
226
+ request=request,
227
+ )
228
+
229
+ return str(output_path)
230
+
231
+ except json.JSONDecodeError as exc:
232
+ raise gr.Error(f"Invalid editor JSON: {exc}")
233
+ except editor.HTTPException as exc:
234
+ raise gr.Error(exc.detail)
235
+ except Exception as exc:
236
+ raise gr.Error(f"PDF export failed: {exc}")
237
+
238
+
239
+ with gr.Blocks(title="Paperplane PDF Editor") as demo:
240
+ gr.Markdown(
241
+ """
242
+ # 📄 Paperplane PDF Editor
243
+
244
+ Upload a PDF, analyze its pages with OCR and layout detection,
245
+ inspect the detected objects, and export an edited PDF.
246
+ """
247
+ )
248
+
249
+ document_id = gr.State("")
250
+
251
+ with gr.Row():
252
+ pdf_upload = gr.File(
253
+ label="PDF",
254
+ file_types=[".pdf"],
255
+ type="filepath",
256
+ )
257
+ upload_button = gr.Button(
258
+ "Open PDF",
259
+ variant="primary",
260
+ )
261
+
262
+ status = gr.Markdown("Upload a PDF to begin.")
263
+
264
+ with gr.Row():
265
+ page_number = gr.Number(
266
+ label="Page",
267
+ value=0,
268
+ precision=0,
269
+ minimum=0,
270
+ )
271
+ analyze_button = gr.Button(
272
+ "Analyze Page",
273
+ variant="primary",
274
+ )
275
+
276
+ page_preview = gr.Image(
277
+ label="Page Preview",
278
+ type="filepath",
279
+ )
280
+
281
+ with gr.Row():
282
+ objects_editor = gr.Code(
283
+ label="Detected / Editable Objects",
284
+ language="json",
285
+ lines=20,
286
+ )
287
+ regions_editor = gr.Code(
288
+ label="Layout Regions",
289
+ language="json",
290
+ lines=20,
291
+ )
292
+
293
+ export_button = gr.Button(
294
+ "Export PDF",
295
+ variant="primary",
296
+ )
297
+
298
+ exported_pdf = gr.File(label="Edited PDF")
299
+
300
+ upload_button.click(
301
+ fn=upload_pdf_gradio,
302
+ inputs=pdf_upload,
303
+ outputs=[
304
+ document_id,
305
+ page_number,
306
+ objects_editor,
307
+ regions_editor,
308
+ page_preview,
309
+ status,
310
+ ],
311
+ )
312
+
313
+ page_number.change(
314
+ fn=change_page,
315
+ inputs=[document_id, page_number],
316
+ outputs=page_preview,
317
+ )
318
+
319
+ analyze_button.click(
320
+ fn=analyze_page_gradio,
321
+ inputs=[document_id, page_number],
322
+ outputs=[
323
+ page_preview,
324
+ objects_editor,
325
+ regions_editor,
326
+ status,
327
+ ],
328
+ )
329
+
330
+ export_button.click(
331
+ fn=export_pdf_gradio,
332
+ inputs=[
333
+ document_id,
334
+ objects_editor,
335
+ regions_editor,
336
+ ],
337
+ outputs=exported_pdf,
338
+ )
339
+
340
+
341
+ if __name__ == "__main__":
342
+ demo.launch(server_name="0.0.0.0", server_port=7860)