ABDALLALSWAITI commited on
Commit
e61da5a
·
verified ·
1 Parent(s): 430bb94

Update api.py

Browse files
Files changed (1) hide show
  1. api.py +157 -18
api.py CHANGED
@@ -1,17 +1,19 @@
1
  from fastapi import FastAPI, File, UploadFile, Form, HTTPException
2
  from fastapi.responses import Response, JSONResponse
3
  from fastapi.middleware.cors import CORSMiddleware
 
4
  import tempfile
5
  import shutil
6
  import os
7
  import subprocess
8
  import base64
9
  from pathlib import Path
 
10
 
11
  app = FastAPI(
12
- title="HTML to PDF API",
13
- description="Convert HTML to PDF using Puppeteer",
14
- version="1.0.0"
15
  )
16
 
17
  # Enable CORS
@@ -23,12 +25,49 @@ app.add_middleware(
23
  allow_headers=["*"],
24
  )
25
 
26
- def convert_html_to_pdf(html_content: str, aspect_ratio: str):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
27
  """Convert HTML content to PDF"""
28
- temp_dir = None
29
  try:
30
- temp_dir = tempfile.mkdtemp()
31
-
32
  # Style injection for better PDF rendering
33
  style_injection = """
34
  <style>
@@ -81,23 +120,21 @@ def convert_html_to_pdf(html_content: str, aspect_ratio: str):
81
  with open(pdf_file, 'rb') as f:
82
  pdf_bytes = f.read()
83
 
84
- shutil.rmtree(temp_dir, ignore_errors=True)
85
  return pdf_bytes
86
 
87
  except Exception as e:
88
- if temp_dir:
89
- shutil.rmtree(temp_dir, ignore_errors=True)
90
  raise e
91
 
92
  @app.get("/")
93
  async def root():
94
  """API root endpoint"""
95
  return {
96
- "message": "HTML to PDF Conversion API",
97
- "version": "1.0.0",
98
  "endpoints": {
99
- "POST /convert": "Convert HTML to PDF (file upload)",
100
- "POST /convert-text": "Convert HTML text to PDF",
 
101
  "GET /health": "Health check",
102
  "GET /docs": "API documentation (Swagger UI)"
103
  }
@@ -111,12 +148,14 @@ async def health_check():
111
  @app.post("/convert")
112
  async def convert_file(
113
  file: UploadFile = File(...),
 
114
  aspect_ratio: str = Form(default="9:16")
115
  ):
116
  """
117
- Convert uploaded HTML file to PDF
118
 
119
  - **file**: HTML file to convert
 
120
  - **aspect_ratio**: Page orientation (16:9, 1:1, or 9:16)
121
  """
122
  if not file.filename.lower().endswith(('.html', '.htm')):
@@ -125,7 +164,11 @@ async def convert_file(
125
  if aspect_ratio not in ["16:9", "1:1", "9:16"]:
126
  raise HTTPException(status_code=400, detail="Invalid aspect ratio. Use: 16:9, 1:1, or 9:16")
127
 
 
128
  try:
 
 
 
129
  # Read HTML content
130
  content = await file.read()
131
  try:
@@ -133,8 +176,16 @@ async def convert_file(
133
  except UnicodeDecodeError:
134
  html_content = content.decode('latin-1')
135
 
 
 
 
 
 
136
  # Convert to PDF
137
- pdf_bytes = convert_html_to_pdf(html_content, aspect_ratio)
 
 
 
138
 
139
  # Return PDF file
140
  filename = file.filename.replace('.html', '.pdf').replace('.htm', '.pdf')
@@ -150,27 +201,43 @@ async def convert_file(
150
  )
151
 
152
  except Exception as e:
 
 
153
  raise HTTPException(status_code=500, detail=f"Conversion failed: {str(e)}")
154
 
155
  @app.post("/convert-text")
156
  async def convert_text(
157
  html: str = Form(...),
 
158
  aspect_ratio: str = Form(default="9:16"),
159
  return_base64: bool = Form(default=False)
160
  ):
161
  """
162
- Convert HTML text to PDF
163
 
164
  - **html**: HTML content as string
 
165
  - **aspect_ratio**: Page orientation (16:9, 1:1, or 9:16)
166
  - **return_base64**: If true, returns base64 encoded PDF in JSON
167
  """
168
  if aspect_ratio not in ["16:9", "1:1", "9:16"]:
169
  raise HTTPException(status_code=400, detail="Invalid aspect ratio. Use: 16:9, 1:1, or 9:16")
170
 
 
171
  try:
 
 
 
 
 
 
 
 
172
  # Convert to PDF
173
- pdf_bytes = convert_html_to_pdf(html, aspect_ratio)
 
 
 
174
 
175
  if return_base64:
176
  # Return as JSON with base64 encoded PDF
@@ -191,6 +258,78 @@ async def convert_text(
191
  )
192
 
193
  except Exception as e:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
194
  raise HTTPException(status_code=500, detail=f"Conversion failed: {str(e)}")
195
 
196
  if __name__ == "__main__":
 
1
  from fastapi import FastAPI, File, UploadFile, Form, HTTPException
2
  from fastapi.responses import Response, JSONResponse
3
  from fastapi.middleware.cors import CORSMiddleware
4
+ from typing import List, Optional
5
  import tempfile
6
  import shutil
7
  import os
8
  import subprocess
9
  import base64
10
  from pathlib import Path
11
+ import mimetypes
12
 
13
  app = FastAPI(
14
+ title="HTML to PDF API with Image Support",
15
+ description="Convert HTML to PDF using Puppeteer with image upload support",
16
+ version="2.0.0"
17
  )
18
 
19
  # Enable CORS
 
25
  allow_headers=["*"],
26
  )
27
 
28
+ def save_uploaded_images(images: List[UploadFile], temp_dir: str):
29
+ """Save uploaded images to temp directory and return mapping"""
30
+ image_mapping = {}
31
+ images_dir = os.path.join(temp_dir, "images")
32
+ os.makedirs(images_dir, exist_ok=True)
33
+
34
+ for image in images:
35
+ if image.filename:
36
+ # Save image to temp directory
37
+ image_path = os.path.join(images_dir, image.filename)
38
+ with open(image_path, 'wb') as f:
39
+ f.write(image.file.read())
40
+
41
+ # Create mapping for HTML replacement
42
+ image_mapping[image.filename] = f"images/{image.filename}"
43
+
44
+ return image_mapping
45
+
46
+ def process_html_with_images(html_content: str, temp_dir: str, image_mapping: dict):
47
+ """Process HTML to handle image references"""
48
+ # Replace image references in HTML
49
+ for original_name, new_path in image_mapping.items():
50
+ # Handle various image reference patterns
51
+ patterns = [
52
+ f'src="{original_name}"',
53
+ f"src='{original_name}'",
54
+ f'src={original_name}',
55
+ f'href="{original_name}"',
56
+ f"href='{original_name}'"
57
+ ]
58
+
59
+ for pattern in patterns:
60
+ if pattern in html_content:
61
+ html_content = html_content.replace(
62
+ pattern,
63
+ pattern.replace(original_name, new_path)
64
+ )
65
+
66
+ return html_content
67
+
68
+ def convert_html_to_pdf(html_content: str, aspect_ratio: str, temp_dir: str):
69
  """Convert HTML content to PDF"""
 
70
  try:
 
 
71
  # Style injection for better PDF rendering
72
  style_injection = """
73
  <style>
 
120
  with open(pdf_file, 'rb') as f:
121
  pdf_bytes = f.read()
122
 
 
123
  return pdf_bytes
124
 
125
  except Exception as e:
 
 
126
  raise e
127
 
128
  @app.get("/")
129
  async def root():
130
  """API root endpoint"""
131
  return {
132
+ "message": "HTML to PDF Conversion API with Image Support",
133
+ "version": "2.0.0",
134
  "endpoints": {
135
+ "POST /convert": "Convert HTML to PDF (file upload with optional images)",
136
+ "POST /convert-text": "Convert HTML text to PDF (with optional image files)",
137
+ "POST /convert-with-images": "Convert HTML with multiple images",
138
  "GET /health": "Health check",
139
  "GET /docs": "API documentation (Swagger UI)"
140
  }
 
148
  @app.post("/convert")
149
  async def convert_file(
150
  file: UploadFile = File(...),
151
+ images: Optional[List[UploadFile]] = File(None),
152
  aspect_ratio: str = Form(default="9:16")
153
  ):
154
  """
155
+ Convert uploaded HTML file to PDF with optional images
156
 
157
  - **file**: HTML file to convert
158
+ - **images**: Optional list of image files (jpg, png, gif, svg, webp)
159
  - **aspect_ratio**: Page orientation (16:9, 1:1, or 9:16)
160
  """
161
  if not file.filename.lower().endswith(('.html', '.htm')):
 
164
  if aspect_ratio not in ["16:9", "1:1", "9:16"]:
165
  raise HTTPException(status_code=400, detail="Invalid aspect ratio. Use: 16:9, 1:1, or 9:16")
166
 
167
+ temp_dir = None
168
  try:
169
+ # Create temporary directory
170
+ temp_dir = tempfile.mkdtemp()
171
+
172
  # Read HTML content
173
  content = await file.read()
174
  try:
 
176
  except UnicodeDecodeError:
177
  html_content = content.decode('latin-1')
178
 
179
+ # Process images if provided
180
+ if images:
181
+ image_mapping = save_uploaded_images(images, temp_dir)
182
+ html_content = process_html_with_images(html_content, temp_dir, image_mapping)
183
+
184
  # Convert to PDF
185
+ pdf_bytes = convert_html_to_pdf(html_content, aspect_ratio, temp_dir)
186
+
187
+ # Clean up
188
+ shutil.rmtree(temp_dir, ignore_errors=True)
189
 
190
  # Return PDF file
191
  filename = file.filename.replace('.html', '.pdf').replace('.htm', '.pdf')
 
201
  )
202
 
203
  except Exception as e:
204
+ if temp_dir:
205
+ shutil.rmtree(temp_dir, ignore_errors=True)
206
  raise HTTPException(status_code=500, detail=f"Conversion failed: {str(e)}")
207
 
208
  @app.post("/convert-text")
209
  async def convert_text(
210
  html: str = Form(...),
211
+ images: Optional[List[UploadFile]] = File(None),
212
  aspect_ratio: str = Form(default="9:16"),
213
  return_base64: bool = Form(default=False)
214
  ):
215
  """
216
+ Convert HTML text to PDF with optional images
217
 
218
  - **html**: HTML content as string
219
+ - **images**: Optional list of image files
220
  - **aspect_ratio**: Page orientation (16:9, 1:1, or 9:16)
221
  - **return_base64**: If true, returns base64 encoded PDF in JSON
222
  """
223
  if aspect_ratio not in ["16:9", "1:1", "9:16"]:
224
  raise HTTPException(status_code=400, detail="Invalid aspect ratio. Use: 16:9, 1:1, or 9:16")
225
 
226
+ temp_dir = None
227
  try:
228
+ # Create temporary directory
229
+ temp_dir = tempfile.mkdtemp()
230
+
231
+ # Process images if provided
232
+ if images:
233
+ image_mapping = save_uploaded_images(images, temp_dir)
234
+ html = process_html_with_images(html, temp_dir, image_mapping)
235
+
236
  # Convert to PDF
237
+ pdf_bytes = convert_html_to_pdf(html, aspect_ratio, temp_dir)
238
+
239
+ # Clean up
240
+ shutil.rmtree(temp_dir, ignore_errors=True)
241
 
242
  if return_base64:
243
  # Return as JSON with base64 encoded PDF
 
258
  )
259
 
260
  except Exception as e:
261
+ if temp_dir:
262
+ shutil.rmtree(temp_dir, ignore_errors=True)
263
+ raise HTTPException(status_code=500, detail=f"Conversion failed: {str(e)}")
264
+
265
+ @app.post("/convert-with-images")
266
+ async def convert_with_images(
267
+ html_file: UploadFile = File(...),
268
+ images: List[UploadFile] = File(...),
269
+ aspect_ratio: str = Form(default="9:16")
270
+ ):
271
+ """
272
+ Convert HTML with multiple images - dedicated endpoint
273
+
274
+ - **html_file**: HTML file to convert
275
+ - **images**: List of image files (required)
276
+ - **aspect_ratio**: Page orientation (16:9, 1:1, or 9:16)
277
+ """
278
+ if not html_file.filename.lower().endswith(('.html', '.htm')):
279
+ raise HTTPException(status_code=400, detail="HTML file must be .html or .htm")
280
+
281
+ if aspect_ratio not in ["16:9", "1:1", "9:16"]:
282
+ raise HTTPException(status_code=400, detail="Invalid aspect ratio. Use: 16:9, 1:1, or 9:16")
283
+
284
+ # Validate image files
285
+ allowed_extensions = {'.jpg', '.jpeg', '.png', '.gif', '.svg', '.webp', '.bmp'}
286
+ for img in images:
287
+ ext = Path(img.filename).suffix.lower()
288
+ if ext not in allowed_extensions:
289
+ raise HTTPException(
290
+ status_code=400,
291
+ detail=f"Invalid image format: {img.filename}. Allowed: {', '.join(allowed_extensions)}"
292
+ )
293
+
294
+ temp_dir = None
295
+ try:
296
+ # Create temporary directory
297
+ temp_dir = tempfile.mkdtemp()
298
+
299
+ # Read HTML content
300
+ content = await html_file.read()
301
+ try:
302
+ html_content = content.decode('utf-8')
303
+ except UnicodeDecodeError:
304
+ html_content = content.decode('latin-1')
305
+
306
+ # Save and process images
307
+ image_mapping = save_uploaded_images(images, temp_dir)
308
+ html_content = process_html_with_images(html_content, temp_dir, image_mapping)
309
+
310
+ # Convert to PDF
311
+ pdf_bytes = convert_html_to_pdf(html_content, aspect_ratio, temp_dir)
312
+
313
+ # Clean up
314
+ shutil.rmtree(temp_dir, ignore_errors=True)
315
+
316
+ # Return PDF
317
+ filename = html_file.filename.replace('.html', '.pdf').replace('.htm', '.pdf')
318
+ if not filename.endswith('.pdf'):
319
+ filename += '.pdf'
320
+
321
+ return Response(
322
+ content=pdf_bytes,
323
+ media_type="application/pdf",
324
+ headers={
325
+ "Content-Disposition": f"attachment; filename={filename}",
326
+ "X-Image-Count": str(len(images))
327
+ }
328
+ )
329
+
330
+ except Exception as e:
331
+ if temp_dir:
332
+ shutil.rmtree(temp_dir, ignore_errors=True)
333
  raise HTTPException(status_code=500, detail=f"Conversion failed: {str(e)}")
334
 
335
  if __name__ == "__main__":