ABDALLALSWAITI commited on
Commit
18b284f
·
verified ·
1 Parent(s): 4b2d20a

Update api.py

Browse files
Files changed (1) hide show
  1. api.py +179 -209
api.py CHANGED
@@ -1,20 +1,15 @@
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
20
  app.add_middleware(
@@ -25,32 +20,63 @@ app.add_middleware(
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
- content = image.file.read()
40
- f.write(content)
41
-
42
- # Reset file pointer for potential reuse
43
- image.file.seek(0)
44
-
45
- # Create mapping with relative path
46
- image_mapping[image.filename] = f"images/{image.filename}"
47
- print(f"Saved image: {image.filename} -> {image_path}")
48
 
49
  return image_mapping
50
 
51
  def process_html_with_images(html_content: str, temp_dir: str, image_mapping: dict):
52
  """Process HTML to handle image references with absolute file paths"""
53
- import re
54
 
55
  for original_name, relative_path in image_mapping.items():
56
  # Get absolute path for the image
@@ -60,42 +86,72 @@ def process_html_with_images(html_content: str, temp_dir: str, image_mapping: di
60
  # Escape the filename for regex
61
  escaped_name = re.escape(original_name)
62
 
63
- # Replace various image reference patterns
64
- # Match filename with or without directory paths (images/, src/images/, ./images/, etc.)
65
-
66
  # Pattern 1: src with any path prefix
 
 
67
  html_content = re.sub(
68
- rf'src=(["\'])(?:[^"\']*/)?' + escaped_name + r'\1',
69
  f'src=\\1{file_url}\\1',
70
  html_content,
71
  flags=re.IGNORECASE
72
  )
 
 
73
 
74
  # Pattern 2: url() with any path prefix
 
 
75
  html_content = re.sub(
76
- rf'url\((["\']?)(?:[^)"\']*/)?{escaped_name}\1\)',
77
  f'url("{file_url}")',
78
  html_content,
79
  flags=re.IGNORECASE
80
  )
 
 
81
 
82
  # Pattern 3: href with any path prefix
 
 
83
  html_content = re.sub(
84
- rf'href=(["\'])(?:[^"\']*/)?' + escaped_name + r'\1',
85
  f'href=\\1{file_url}\\1',
86
  html_content,
87
  flags=re.IGNORECASE
88
  )
 
 
 
 
 
 
 
 
 
 
 
89
 
90
  return html_content
91
 
92
  def convert_html_to_pdf(html_content: str, aspect_ratio: str, temp_dir: str):
93
- """Convert HTML content to PDF"""
 
 
 
 
 
 
 
 
 
 
94
  try:
95
- # Style injection for better PDF rendering
96
  style_injection = """
97
  <style>
98
- @page { margin: 0; }
 
 
99
  * {
100
  -webkit-print-color-adjust: exact !important;
101
  print-color-adjust: exact !important;
@@ -108,6 +164,7 @@ def convert_html_to_pdf(html_content: str, aspect_ratio: str, temp_dir: str):
108
  </style>
109
  """
110
 
 
111
  if '</head>' in html_content:
112
  html_content = html_content.replace('</head>', style_injection + '</head>')
113
  elif '<body' in html_content:
@@ -115,246 +172,159 @@ def convert_html_to_pdf(html_content: str, aspect_ratio: str, temp_dir: str):
115
  else:
116
  html_content = style_injection + html_content
117
 
118
- # Save HTML to temp file
119
  html_file = os.path.join(temp_dir, "input.html")
120
  with open(html_file, 'w', encoding='utf-8') as f:
121
  f.write(html_content)
122
 
123
- # Get puppeteer script path
124
- script_dir = os.path.dirname(os.path.abspath(__file__))
125
- puppeteer_script = os.path.join(script_dir, 'puppeteer_pdf.js')
 
 
 
126
 
127
- # Run conversion
128
  result = subprocess.run(
129
  ['node', puppeteer_script, html_file, aspect_ratio],
130
  capture_output=True,
131
  text=True,
132
  timeout=60,
133
- cwd=script_dir
134
  )
135
 
136
  if result.returncode != 0:
137
- raise Exception(f"PDF conversion failed: {result.stderr}")
 
138
 
 
139
  pdf_file = html_file.replace('.html', '.pdf')
140
 
141
  if not os.path.exists(pdf_file):
142
- raise Exception("PDF file was not generated")
143
 
 
144
  with open(pdf_file, 'rb') as f:
145
  pdf_bytes = f.read()
146
 
147
- return pdf_bytes
 
148
 
 
 
149
  except Exception as e:
150
- raise e
 
151
 
152
  @app.get("/")
153
  async def root():
154
- """API root endpoint"""
155
  return {
156
- "message": "HTML to PDF Conversion API with Image Support",
157
- "version": "2.0.0",
158
  "endpoints": {
159
- "POST /convert": "Convert HTML to PDF (file upload with optional images)",
160
- "POST /convert-text": "Convert HTML text to PDF (with optional image files)",
161
- "POST /convert-with-images": "Convert HTML with multiple images",
162
- "GET /health": "Health check",
163
- "GET /docs": "API documentation (Swagger UI)"
164
  }
165
  }
166
 
167
  @app.get("/health")
168
- async def health_check():
169
- """Health check endpoint"""
170
- return {"status": "healthy", "service": "html-to-pdf-api"}
171
 
172
  @app.post("/convert")
173
- async def convert_file(
174
- file: UploadFile = File(...),
175
- images: Optional[List[UploadFile]] = File(None),
176
- aspect_ratio: str = Form(default="9:16")
 
 
177
  ):
178
  """
179
- Convert uploaded HTML file to PDF with optional images
180
-
181
- - **file**: HTML file to convert
182
- - **images**: Optional list of image files (jpg, png, gif, svg, webp)
183
- - **aspect_ratio**: Page orientation (16:9, 1:1, or 9:16)
184
- """
185
- if not file.filename.lower().endswith(('.html', '.htm')):
186
- raise HTTPException(status_code=400, detail="File must be HTML (.html or .htm)")
187
 
188
- if aspect_ratio not in ["16:9", "1:1", "9:16"]:
189
- raise HTTPException(status_code=400, detail="Invalid aspect ratio. Use: 16:9, 1:1, or 9:16")
190
-
191
- temp_dir = None
192
- try:
193
- # Create temporary directory
194
- temp_dir = tempfile.mkdtemp()
195
-
196
- # Read HTML content
197
- content = await file.read()
198
- try:
199
- html_content = content.decode('utf-8')
200
- except UnicodeDecodeError:
201
- html_content = content.decode('latin-1')
202
-
203
- # Process images if provided
204
- if images:
205
- image_mapping = save_uploaded_images(images, temp_dir)
206
- html_content = process_html_with_images(html_content, temp_dir, image_mapping)
207
-
208
- # Convert to PDF
209
- pdf_bytes = convert_html_to_pdf(html_content, aspect_ratio, temp_dir)
210
-
211
- # Clean up
212
- shutil.rmtree(temp_dir, ignore_errors=True)
213
-
214
- # Return PDF file
215
- filename = file.filename.replace('.html', '.pdf').replace('.htm', '.pdf')
216
- if not filename.endswith('.pdf'):
217
- filename += '.pdf'
218
-
219
- return Response(
220
- content=pdf_bytes,
221
- media_type="application/pdf",
222
- headers={
223
- "Content-Disposition": f"attachment; filename={filename}"
224
- }
225
- )
226
-
227
- except Exception as e:
228
- if temp_dir:
229
- shutil.rmtree(temp_dir, ignore_errors=True)
230
- raise HTTPException(status_code=500, detail=f"Conversion failed: {str(e)}")
231
-
232
- @app.post("/convert-text")
233
- async def convert_text(
234
- html: str = Form(...),
235
- images: Optional[List[UploadFile]] = File(None),
236
- aspect_ratio: str = Form(default="9:16"),
237
- return_base64: bool = Form(default=False)
238
- ):
239
- """
240
- Convert HTML text to PDF with optional images
241
 
242
- - **html**: HTML content as string
243
- - **images**: Optional list of image files
244
- - **aspect_ratio**: Page orientation (16:9, 1:1, or 9:16)
245
- - **return_base64**: If true, returns base64 encoded PDF in JSON
246
  """
247
- if aspect_ratio not in ["16:9", "1:1", "9:16"]:
248
- raise HTTPException(status_code=400, detail="Invalid aspect ratio. Use: 16:9, 1:1, or 9:16")
249
-
250
  temp_dir = None
 
251
  try:
252
- # Create temporary directory
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
253
  temp_dir = tempfile.mkdtemp()
 
254
 
255
  # Process images if provided
256
  if images:
 
257
  image_mapping = save_uploaded_images(images, temp_dir)
258
  html = process_html_with_images(html, temp_dir, image_mapping)
 
259
 
260
- # Convert to PDF
261
- pdf_bytes = convert_html_to_pdf(html, aspect_ratio, temp_dir)
262
-
263
- # Clean up
264
- shutil.rmtree(temp_dir, ignore_errors=True)
265
-
266
- if return_base64:
267
- # Return as JSON with base64 encoded PDF
268
- pdf_base64 = base64.b64encode(pdf_bytes).decode('utf-8')
269
- return JSONResponse(content={
270
- "success": True,
271
- "pdf_base64": pdf_base64,
272
- "size_bytes": len(pdf_bytes)
273
- })
274
  else:
275
- # Return PDF file directly
276
- return Response(
277
- content=pdf_bytes,
278
- media_type="application/pdf",
279
- headers={
280
- "Content-Disposition": "attachment; filename=converted.pdf"
281
- }
282
- )
283
 
284
- except Exception as e:
 
 
 
285
  if temp_dir:
286
  shutil.rmtree(temp_dir, ignore_errors=True)
287
- raise HTTPException(status_code=500, detail=f"Conversion failed: {str(e)}")
288
-
289
- @app.post("/convert-with-images")
290
- async def convert_with_images(
291
- html_file: UploadFile = File(...),
292
- images: List[UploadFile] = File(...),
293
- aspect_ratio: str = Form(default="9:16")
294
- ):
295
- """
296
- Convert HTML with multiple images - dedicated endpoint
297
-
298
- - **html_file**: HTML file to convert
299
- - **images**: List of image files (required)
300
- - **aspect_ratio**: Page orientation (16:9, 1:1, or 9:16)
301
- """
302
- if not html_file.filename.lower().endswith(('.html', '.htm')):
303
- raise HTTPException(status_code=400, detail="HTML file must be .html or .htm")
304
-
305
- if aspect_ratio not in ["16:9", "1:1", "9:16"]:
306
- raise HTTPException(status_code=400, detail="Invalid aspect ratio. Use: 16:9, 1:1, or 9:16")
307
-
308
- # Validate image files
309
- allowed_extensions = {'.jpg', '.jpeg', '.png', '.gif', '.svg', '.webp', '.bmp'}
310
- for img in images:
311
- ext = Path(img.filename).suffix.lower()
312
- if ext not in allowed_extensions:
313
- raise HTTPException(
314
- status_code=400,
315
- detail=f"Invalid image format: {img.filename}. Allowed: {', '.join(allowed_extensions)}"
316
- )
317
-
318
- temp_dir = None
319
- try:
320
- # Create temporary directory
321
- temp_dir = tempfile.mkdtemp()
322
-
323
- # Read HTML content
324
- content = await html_file.read()
325
- try:
326
- html_content = content.decode('utf-8')
327
- except UnicodeDecodeError:
328
- html_content = content.decode('latin-1')
329
 
330
- # Save and process images
331
- image_mapping = save_uploaded_images(images, temp_dir)
332
- html_content = process_html_with_images(html_content, temp_dir, image_mapping)
333
-
334
- # Convert to PDF
335
- pdf_bytes = convert_html_to_pdf(html_content, aspect_ratio, temp_dir)
336
 
337
- # Clean up
338
- shutil.rmtree(temp_dir, ignore_errors=True)
 
 
339
 
340
- # Return PDF
341
- filename = html_file.filename.replace('.html', '.pdf').replace('.htm', '.pdf')
342
- if not filename.endswith('.pdf'):
343
- filename += '.pdf'
344
-
345
  return Response(
346
  content=pdf_bytes,
347
  media_type="application/pdf",
348
  headers={
349
- "Content-Disposition": f"attachment; filename={filename}",
350
- "X-Image-Count": str(len(images))
351
  }
352
  )
353
 
 
 
354
  except Exception as e:
355
  if temp_dir:
356
  shutil.rmtree(temp_dir, ignore_errors=True)
357
- raise HTTPException(status_code=500, detail=f"Conversion failed: {str(e)}")
 
358
 
359
  if __name__ == "__main__":
360
  import uvicorn
 
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 subprocess
5
+ import os
6
  import tempfile
7
  import shutil
 
 
 
8
  from pathlib import Path
9
+ import re
10
+ from typing import List, Optional
11
 
12
+ app = FastAPI(title="HTML to PDF Converter API")
 
 
 
 
13
 
14
  # Enable CORS
15
  app.add_middleware(
 
20
  allow_headers=["*"],
21
  )
22
 
23
+ def detect_aspect_ratio(html_content):
24
+ """
25
+ Detect aspect ratio from HTML content
26
+ Returns: "16:9", "1:1", or "9:16"
27
+ """
28
+ # Check for viewport meta tag
29
+ viewport_match = re.search(r'<meta[^>]*viewport[^>]*content=["\']([^"\']*)["\']', html_content, re.IGNORECASE)
30
+ if viewport_match:
31
+ viewport = viewport_match.group(1).lower()
32
+ if 'width=device-width' in viewport or 'width=100%' in viewport:
33
+ if 'orientation=portrait' in viewport:
34
+ return "9:16"
35
+ elif 'orientation=landscape' in viewport:
36
+ return "16:9"
37
+
38
+ # Check for CSS aspect-ratio property
39
+ aspect_match = re.search(r'aspect-ratio\s*:\s*(\d+)\s*/\s*(\d+)', html_content, re.IGNORECASE)
40
+ if aspect_match:
41
+ width = int(aspect_match.group(1))
42
+ height = int(aspect_match.group(2))
43
+ ratio = width / height
44
+ if ratio > 1.5:
45
+ return "16:9"
46
+ elif ratio < 0.7:
47
+ return "9:16"
48
+ else:
49
+ return "1:1"
50
+
51
+ # Check for common presentation frameworks
52
+ if any(keyword in html_content.lower() for keyword in ['reveal.js', 'impress.js', 'slide', 'presentation']):
53
+ return "16:9"
54
+
55
+ # Default to A4 portrait
56
+ return "9:16"
57
+
58
  def save_uploaded_images(images: List[UploadFile], temp_dir: str):
59
+ """Save uploaded images and return mapping"""
60
  image_mapping = {}
61
  images_dir = os.path.join(temp_dir, "images")
62
  os.makedirs(images_dir, exist_ok=True)
63
 
64
  for image in images:
65
+ # Save image
66
+ image_path = os.path.join(images_dir, image.filename)
67
+ with open(image_path, 'wb') as f:
68
+ content = image.file.read()
69
+ f.write(content)
70
+
71
+ # Create mapping
72
+ image_mapping[image.filename] = f"images/{image.filename}"
73
+ print(f"API: Saved image: {image.filename} -> {image_path}")
 
 
 
 
74
 
75
  return image_mapping
76
 
77
  def process_html_with_images(html_content: str, temp_dir: str, image_mapping: dict):
78
  """Process HTML to handle image references with absolute file paths"""
79
+ replacements_made = []
80
 
81
  for original_name, relative_path in image_mapping.items():
82
  # Get absolute path for the image
 
86
  # Escape the filename for regex
87
  escaped_name = re.escape(original_name)
88
 
 
 
 
89
  # Pattern 1: src with any path prefix
90
+ pattern1 = rf'src=(["\'])(?:[^"\']*?/)?{escaped_name}\1'
91
+ matches1 = re.findall(pattern1, html_content, flags=re.IGNORECASE)
92
  html_content = re.sub(
93
+ pattern1,
94
  f'src=\\1{file_url}\\1',
95
  html_content,
96
  flags=re.IGNORECASE
97
  )
98
+ if matches1:
99
+ replacements_made.append(f"Pattern 1 (src): Found {len(matches1)} matches for {original_name}")
100
 
101
  # Pattern 2: url() with any path prefix
102
+ pattern2 = rf'url\((["\']?)(?:[^)"\']*/)?{escaped_name}\1\)'
103
+ matches2 = re.findall(pattern2, html_content, flags=re.IGNORECASE)
104
  html_content = re.sub(
105
+ pattern2,
106
  f'url("{file_url}")',
107
  html_content,
108
  flags=re.IGNORECASE
109
  )
110
+ if matches2:
111
+ replacements_made.append(f"Pattern 2 (url): Found {len(matches2)} matches for {original_name}")
112
 
113
  # Pattern 3: href with any path prefix
114
+ pattern3 = rf'href=(["\'])(?:[^"\']*?/)?{escaped_name}\1'
115
+ matches3 = re.findall(pattern3, html_content, flags=re.IGNORECASE)
116
  html_content = re.sub(
117
+ pattern3,
118
  f'href=\\1{file_url}\\1',
119
  html_content,
120
  flags=re.IGNORECASE
121
  )
122
+ if matches3:
123
+ replacements_made.append(f"Pattern 3 (href): Found {len(matches3)} matches for {original_name}")
124
+
125
+ # Print debug info
126
+ if replacements_made:
127
+ print("=== API Image Replacements Made ===")
128
+ for msg in replacements_made:
129
+ print(f" ✓ {msg}")
130
+ else:
131
+ print("=== API WARNING: No image replacements made ===")
132
+ print(f"Looking for images: {list(image_mapping.keys())}")
133
 
134
  return html_content
135
 
136
  def convert_html_to_pdf(html_content: str, aspect_ratio: str, temp_dir: str):
137
+ """
138
+ Convert HTML content to PDF using Puppeteer
139
+
140
+ Args:
141
+ html_content: String containing HTML content
142
+ aspect_ratio: One of "16:9", "1:1", or "9:16"
143
+ temp_dir: Temporary directory for processing
144
+
145
+ Returns:
146
+ Tuple of (pdf_bytes, error_message)
147
+ """
148
  try:
149
+ # Inject CSS to preserve styles better
150
  style_injection = """
151
  <style>
152
+ @page {
153
+ margin: 0;
154
+ }
155
  * {
156
  -webkit-print-color-adjust: exact !important;
157
  print-color-adjust: exact !important;
 
164
  </style>
165
  """
166
 
167
+ # Insert style injection
168
  if '</head>' in html_content:
169
  html_content = html_content.replace('</head>', style_injection + '</head>')
170
  elif '<body' in html_content:
 
172
  else:
173
  html_content = style_injection + html_content
174
 
175
+ # Save HTML content to temporary file
176
  html_file = os.path.join(temp_dir, "input.html")
177
  with open(html_file, 'w', encoding='utf-8') as f:
178
  f.write(html_content)
179
 
180
+ # Get the path to puppeteer_pdf.js
181
+ puppeteer_script = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'puppeteer_pdf.js')
182
+
183
+ print(f"API: Running Puppeteer conversion with aspect ratio: {aspect_ratio}")
184
+ print(f"API: HTML file: {html_file}")
185
+ print(f"API: Puppeteer script: {puppeteer_script}")
186
 
187
+ # Run Node.js script to convert HTML to PDF
188
  result = subprocess.run(
189
  ['node', puppeteer_script, html_file, aspect_ratio],
190
  capture_output=True,
191
  text=True,
192
  timeout=60,
193
+ cwd=os.path.dirname(os.path.abspath(__file__))
194
  )
195
 
196
  if result.returncode != 0:
197
+ print(f"API: Puppeteer error: {result.stderr}")
198
+ return None, f"PDF conversion failed: {result.stderr}"
199
 
200
+ # Get the generated PDF path
201
  pdf_file = html_file.replace('.html', '.pdf')
202
 
203
  if not os.path.exists(pdf_file):
204
+ return None, "PDF file was not generated"
205
 
206
+ # Read PDF file into memory
207
  with open(pdf_file, 'rb') as f:
208
  pdf_bytes = f.read()
209
 
210
+ print(f"API: PDF generated successfully, size: {len(pdf_bytes)} bytes")
211
+ return pdf_bytes, None
212
 
213
+ except subprocess.TimeoutExpired:
214
+ return None, "Error: PDF conversion timed out (60 seconds)"
215
  except Exception as e:
216
+ print(f"API: Conversion error: {str(e)}")
217
+ return None, f"Error: {str(e)}"
218
 
219
  @app.get("/")
220
  async def root():
 
221
  return {
222
+ "message": "HTML to PDF Converter API",
223
+ "version": "2.0",
224
  "endpoints": {
225
+ "/convert": "POST - Convert HTML to PDF (supports file upload or raw HTML)",
226
+ "/health": "GET - Health check"
 
 
 
227
  }
228
  }
229
 
230
  @app.get("/health")
231
+ async def health():
232
+ return {"status": "healthy"}
 
233
 
234
  @app.post("/convert")
235
+ async def convert_to_pdf(
236
+ html_file: Optional[UploadFile] = File(None),
237
+ html_content: Optional[str] = Form(None),
238
+ aspect_ratio: Optional[str] = Form(None),
239
+ auto_detect: bool = Form(True),
240
+ images: Optional[List[UploadFile]] = File(None)
241
  ):
242
  """
243
+ Convert HTML to PDF
 
 
 
 
 
 
 
244
 
245
+ Parameters:
246
+ - html_file: HTML file upload (optional)
247
+ - html_content: Raw HTML content (optional, used if html_file not provided)
248
+ - aspect_ratio: "16:9", "1:1", or "9:16" (optional if auto_detect is True)
249
+ - auto_detect: Auto-detect aspect ratio from HTML (default: True)
250
+ - images: List of image files referenced in the HTML (optional)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
251
 
252
+ Returns:
253
+ - PDF file as bytes
 
 
254
  """
 
 
 
255
  temp_dir = None
256
+
257
  try:
258
+ # Validate input
259
+ if not html_file and not html_content:
260
+ raise HTTPException(status_code=400, detail="Either html_file or html_content must be provided")
261
+
262
+ # Get HTML content
263
+ if html_file:
264
+ content = await html_file.read()
265
+ try:
266
+ html = content.decode('utf-8')
267
+ except UnicodeDecodeError:
268
+ html = content.decode('latin-1')
269
+ filename = html_file.filename
270
+ else:
271
+ html = html_content
272
+ filename = "converted.pdf"
273
+
274
+ # Create temp directory
275
  temp_dir = tempfile.mkdtemp()
276
+ print(f"API: Created temp directory: {temp_dir}")
277
 
278
  # Process images if provided
279
  if images:
280
+ print(f"API: Processing {len(images)} uploaded images")
281
  image_mapping = save_uploaded_images(images, temp_dir)
282
  html = process_html_with_images(html, temp_dir, image_mapping)
283
+ print(f"API: Image processing complete")
284
 
285
+ # Determine aspect ratio
286
+ if auto_detect or not aspect_ratio:
287
+ detected_ratio = detect_aspect_ratio(html)
288
+ aspect_ratio = detected_ratio
289
+ print(f"API: Auto-detected aspect ratio: {aspect_ratio}")
 
 
 
 
 
 
 
 
 
290
  else:
291
+ # Validate aspect ratio
292
+ if aspect_ratio not in ["16:9", "1:1", "9:16"]:
293
+ raise HTTPException(status_code=400, detail="Invalid aspect_ratio. Must be '16:9', '1:1', or '9:16'")
294
+ print(f"API: Using specified aspect ratio: {aspect_ratio}")
 
 
 
 
295
 
296
+ # Convert to PDF
297
+ pdf_bytes, error = convert_html_to_pdf(html, aspect_ratio, temp_dir)
298
+
299
+ # Cleanup
300
  if temp_dir:
301
  shutil.rmtree(temp_dir, ignore_errors=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
302
 
303
+ if error:
304
+ raise HTTPException(status_code=500, detail=error)
 
 
 
 
305
 
306
+ # Generate output filename
307
+ output_filename = filename.replace('.html', '.pdf').replace('.htm', '.pdf')
308
+ if not output_filename.endswith('.pdf'):
309
+ output_filename = 'converted.pdf'
310
 
311
+ # Return PDF as response
 
 
 
 
312
  return Response(
313
  content=pdf_bytes,
314
  media_type="application/pdf",
315
  headers={
316
+ "Content-Disposition": f"attachment; filename={output_filename}",
317
+ "X-Aspect-Ratio": aspect_ratio
318
  }
319
  )
320
 
321
+ except HTTPException:
322
+ raise
323
  except Exception as e:
324
  if temp_dir:
325
  shutil.rmtree(temp_dir, ignore_errors=True)
326
+ print(f"API: Error in convert endpoint: {str(e)}")
327
+ raise HTTPException(status_code=500, detail=str(e))
328
 
329
  if __name__ == "__main__":
330
  import uvicorn