andevs commited on
Commit
7e937d5
·
verified ·
1 Parent(s): de1760e

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +399 -295
app.py CHANGED
@@ -7,11 +7,9 @@ import io
7
  import requests
8
  from PIL import Image
9
  import json
10
- import tempfile
11
- import subprocess
12
- from pathlib import Path
13
- import shutil
14
  import textwrap
 
 
15
 
16
  app = FastAPI(title="CONVERTLY API", version="2.0.0")
17
 
@@ -25,140 +23,226 @@ app.add_middleware(
25
 
26
  HF_TOKEN = os.getenv("HF_TOKEN", "")
27
 
28
- # ===== CLEAN PDF GENERATION (NO METADATA) =====
29
 
30
- def create_clean_pdf(text_content):
31
- """Create a clean PDF with just the content - NO extra metadata"""
32
  try:
33
- from reportlab.pdfgen import canvas
34
- from reportlab.lib.pagesizes import letter
35
- from reportlab.lib.units import inch
 
 
 
36
  from reportlab.pdfbase import pdfmetrics
37
  from reportlab.pdfbase.ttfonts import TTFont
38
  import textwrap
39
 
40
  buffer = io.BytesIO()
41
- c = canvas.Canvas(buffer, pagesize=letter)
42
- width, height = letter
43
-
44
- # Set up margins
45
- left_margin = 72 # 1 inch
46
- right_margin = 72
47
- top_margin = 72
48
- bottom_margin = 72
49
-
50
- # Set font
51
- c.setFont("Helvetica", 11)
52
-
53
- # Start position
54
- x = left_margin
55
- y = height - top_margin
56
-
57
- # Split content into lines and wrap
58
- lines = []
59
- for paragraph in text_content.split('\n\n'):
60
- if paragraph.strip():
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
61
  # Clean the paragraph
62
- clean_para = ' '.join(paragraph.split())
63
- # Wrap text
64
- wrapped = textwrap.wrap(clean_para, width=80)
65
- lines.extend(wrapped)
66
- lines.append('') # Add blank line between paragraphs
67
-
68
- # Draw each line
69
- for line in lines:
70
- if line == '':
71
- y -= 12 # Blank line
72
- else:
73
- # Check if we need a new page
74
- if y < bottom_margin:
75
- c.showPage()
76
- c.setFont("Helvetica", 11)
77
- y = height - top_margin
 
 
 
 
 
 
 
 
 
 
 
 
 
78
 
79
- c.drawString(x, y, line)
80
- y -= 15 # Line spacing
 
81
 
82
- c.save()
 
83
  buffer.seek(0)
84
  return buffer.getvalue()
85
 
86
  except Exception as e:
87
- print(f"PDF generation error: {e}")
88
- # Fallback: return plain text
89
- return text_content.encode('utf-8')
90
 
91
- def create_professional_pdf(text_content, title="Document"):
92
- """Create a professional PDF with proper formatting - minimal metadata"""
 
93
  try:
94
- from reportlab.pdfgen import canvas
95
- from reportlab.lib.pagesizes import letter
96
- from reportlab.lib.units import inch
97
- import textwrap
 
 
98
 
99
  buffer = io.BytesIO()
100
- c = canvas.Canvas(buffer, pagesize=letter)
101
- width, height = letter
102
 
103
- # Set up margins
104
- left_margin = 72
105
- right_margin = 72
106
- top_margin = 72
107
- bottom_margin = 72
 
 
 
108
 
109
- # Set font
110
- c.setFont("Helvetica", 11)
111
 
112
- # Start position
113
- x = left_margin
114
- y = height - top_margin
 
 
 
 
 
 
115
 
116
- # Split content into paragraphs
117
- paragraphs = text_content.split('\n\n') if '\n\n' in text_content else [text_content]
118
 
119
- for paragraph in paragraphs:
120
- if paragraph.strip():
121
- # Clean the paragraph
122
- clean_para = ' '.join(paragraph.split())
123
- # Wrap text
124
- wrapped = textwrap.wrap(clean_para, width=80)
125
-
126
- for line in wrapped:
127
- if y < bottom_margin:
128
- c.showPage()
129
- c.setFont("Helvetica", 11)
130
- y = height - top_margin
131
- c.drawString(x, y, line)
132
- y -= 15
133
-
134
- # Add space between paragraphs
135
- y -= 10
136
 
137
- c.save()
138
  buffer.seek(0)
139
  return buffer.getvalue()
140
 
141
  except Exception as e:
142
- print(f"PDF generation error: {e}")
143
- return create_clean_pdf(text_content)
144
 
145
- # ===== REAL CONVERSION FUNCTIONS =====
146
 
147
- def convert_docx_to_pdf(docx_bytes):
148
- """Convert DOCX to PDF with clean content"""
149
  try:
150
  from docx import Document
 
 
151
 
152
- # Read the DOCX
153
  doc = Document(io.BytesIO(docx_bytes))
154
 
155
- # Extract all text
156
  full_text = []
157
 
158
  # Extract paragraphs
159
  for para in doc.paragraphs:
160
  if para.text.strip():
161
- full_text.append(para.text)
 
 
 
 
 
162
 
163
  # Extract tables
164
  for table in doc.tables:
@@ -168,39 +252,63 @@ def convert_docx_to_pdf(docx_bytes):
168
  if cell.text.strip():
169
  row_text.append(cell.text.strip())
170
  if row_text:
171
- full_text.append(" | ".join(row_text))
172
-
173
- text_content = '\n\n'.join(full_text) if full_text else "No text content found in document."
174
 
175
- # Create clean PDF
176
- return create_clean_pdf(text_content)
177
 
 
 
 
 
 
 
 
 
 
 
 
 
178
  except Exception as e:
179
  print(f"DOCX to PDF error: {e}")
180
  return docx_bytes
181
 
 
182
  def convert_pdf_to_docx(pdf_bytes):
183
- """Convert PDF to DOCX - extract text"""
184
  try:
185
  from PyPDF2 import PdfReader
186
  from docx import Document
 
 
187
 
188
- # Read PDF
189
  pdf = PdfReader(io.BytesIO(pdf_bytes))
190
-
191
- # Create DOCX
192
  doc = Document()
193
 
 
 
 
 
 
 
 
 
 
 
194
  # Extract text from each page
195
- for page in pdf.pages:
196
  text = page.extract_text()
197
  if text and text.strip():
198
- # Split into paragraphs
199
  paragraphs = text.split('\n\n')
200
  for para in paragraphs:
201
  if para.strip():
202
  cleaned = ' '.join(para.split())
203
  doc.add_paragraph(cleaned)
 
 
 
 
 
204
 
205
  buffer = io.BytesIO()
206
  doc.save(buffer)
@@ -211,28 +319,56 @@ def convert_pdf_to_docx(pdf_bytes):
211
  print(f"PDF to DOCX error: {e}")
212
  return pdf_bytes
213
 
 
214
  def convert_pdf_to_excel(pdf_bytes):
215
- """Convert PDF to Excel - extract tables"""
216
  try:
217
  import openpyxl
 
218
  from PyPDF2 import PdfReader
219
 
220
  wb = openpyxl.Workbook()
221
  ws = wb.active
222
  ws.title = "Extracted Data"
223
 
 
 
 
 
224
  pdf = PdfReader(io.BytesIO(pdf_bytes))
225
  row = 1
226
 
227
- for page in pdf.pages:
 
 
 
 
 
 
 
 
 
228
  text = page.extract_text()
229
  if text:
230
  lines = text.split('\n')
231
  for line in lines:
232
  if line.strip():
233
- ws.cell(row=row, column=1, value=line.strip()[:500])
 
234
  row += 1
235
 
 
 
 
 
 
 
 
 
 
 
 
 
236
  buffer = io.BytesIO()
237
  wb.save(buffer)
238
  buffer.seek(0)
@@ -242,27 +378,38 @@ def convert_pdf_to_excel(pdf_bytes):
242
  print(f"PDF to Excel error: {e}")
243
  return pdf_bytes
244
 
 
245
  def convert_pdf_to_pptx(pdf_bytes):
246
  """Convert PDF to PPTX"""
247
  try:
248
  from pptx import Presentation
 
249
  from PyPDF2 import PdfReader
250
 
251
  prs = Presentation()
 
252
  content_slide_layout = prs.slide_layouts[1]
253
 
 
 
 
 
 
 
 
254
  pdf = PdfReader(io.BytesIO(pdf_bytes))
255
 
256
- for page in pdf.pages:
257
  slide = prs.slides.add_slide(content_slide_layout)
258
  title = slide.shapes.title
259
  content = slide.placeholders[1]
260
 
 
261
  text = page.extract_text()
262
  if text and text.strip():
263
  content.text = text[:1000] + "..." if len(text) > 1000 else text
264
  else:
265
- content.text = "No text extracted"
266
 
267
  buffer = io.BytesIO()
268
  prs.save(buffer)
@@ -273,51 +420,60 @@ def convert_pdf_to_pptx(pdf_bytes):
273
  print(f"PDF to PPTX error: {e}")
274
  return pdf_bytes
275
 
 
276
  def convert_excel_to_pdf(excel_bytes):
277
  """Convert Excel to PDF"""
278
  try:
279
  import openpyxl
280
- from reportlab.pdfgen import canvas
281
- from reportlab.lib.pagesizes import letter
282
- from reportlab.lib.units import inch
283
- import textwrap
 
 
284
 
285
  wb = openpyxl.load_workbook(io.BytesIO(excel_bytes))
286
  ws = wb.active
287
 
288
  buffer = io.BytesIO()
289
- c = canvas.Canvas(buffer, pagesize=letter)
290
- width, height = letter
 
 
 
 
 
 
291
 
292
- # Set up margins
293
- left_margin = 72
294
- top_margin = 72
295
- bottom_margin = 72
296
 
297
- c.setFont("Helvetica", 10)
298
- y = height - top_margin
299
- x = left_margin
300
 
301
- # Extract all cell values
302
- cell_values = []
303
  for row in ws.iter_rows(values=True):
304
- row_text = ' | '.join([str(cell) for cell in row if cell])
305
- if row_text:
306
- cell_values.append(row_text)
307
-
308
- # Write to PDF with word wrapping
309
- for row_text in cell_values:
310
- wrapped = textwrap.wrap(row_text, width=80)
311
- for line in wrapped:
312
- if y < bottom_margin:
313
- c.showPage()
314
- c.setFont("Helvetica", 10)
315
- y = height - top_margin
316
- c.drawString(x, y, line)
317
- y -= 12
318
- y -= 6 # Space between rows
319
-
320
- c.save()
 
 
321
  buffer.seek(0)
322
  return buffer.getvalue()
323
 
@@ -325,48 +481,52 @@ def convert_excel_to_pdf(excel_bytes):
325
  print(f"Excel to PDF error: {e}")
326
  return excel_bytes
327
 
 
328
  def convert_ppt_to_pdf(pptx_bytes):
329
  """Convert PPTX to PDF"""
330
  try:
331
  from pptx import Presentation
332
- from reportlab.pdfgen import canvas
333
- from reportlab.lib.pagesizes import letter
334
- from reportlab.lib.units import inch
335
- import textwrap
 
336
 
337
  prs = Presentation(io.BytesIO(pptx_bytes))
338
 
339
  buffer = io.BytesIO()
340
- c = canvas.Canvas(buffer, pagesize=letter)
341
- width, height = letter
 
 
 
 
 
 
342
 
343
- left_margin = 72
344
- top_margin = 72
345
- bottom_margin = 72
346
 
347
- c.setFont("Helvetica", 11)
348
- y = height - top_margin
349
- x = left_margin
350
 
351
- for slide in prs.slides:
352
- slide_text = []
 
 
353
  for shape in slide.shapes:
354
  if hasattr(shape, "text") and shape.text:
355
- slide_text.append(shape.text.strip())
 
 
 
356
 
357
- if slide_text:
358
- text = '\n\n'.join(slide_text)
359
- wrapped = textwrap.wrap(text, width=80)
360
- for line in wrapped:
361
- if y < bottom_margin:
362
- c.showPage()
363
- c.setFont("Helvetica", 11)
364
- y = height - top_margin
365
- c.drawString(x, y, line)
366
- y -= 15
367
- y -= 20 # Space between slides
368
-
369
- c.save()
370
  buffer.seek(0)
371
  return buffer.getvalue()
372
 
@@ -374,22 +534,54 @@ def convert_ppt_to_pdf(pptx_bytes):
374
  print(f"PPT to PDF error: {e}")
375
  return pptx_bytes
376
 
 
377
  def convert_image_to_other(img_bytes, target_format):
378
  """Convert image to different format"""
379
- img = Image.open(io.BytesIO(img_bytes))
380
- output = io.BytesIO()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
381
 
382
- fmt = target_format.upper()
383
- if fmt == "JPG":
384
- fmt = "JPEG"
385
- if img.mode in ('RGBA', 'P'):
386
- rgb_img = Image.new('RGB', img.size, (255, 255, 255))
387
- rgb_img.paste(img, mask=img.split()[-1] if img.mode == 'RGBA' else None)
388
- img = rgb_img
389
 
390
- img.save(output, format=fmt, quality=95)
391
- output.seek(0)
392
- return output.getvalue()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
393
 
394
  # ===== API ENDPOINTS =====
395
 
@@ -422,7 +614,6 @@ async def convert_file(
422
  ext = filename.split('.')[-1].lower() if '.' in filename else ''
423
  target = target_format.lower()
424
 
425
- # Map source and target formats
426
  format_map = {
427
  ('docx', 'pdf'): ('application/pdf', 'pdf', convert_docx_to_pdf),
428
  ('pdf', 'docx'): ('application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'docx', convert_pdf_to_docx),
@@ -448,48 +639,16 @@ async def convert_file(
448
  )
449
  except Exception as e:
450
  print(f"Conversion error: {e}")
451
- # Fall through to text conversion
452
 
453
- # If no specific converter, try text conversion
454
- try:
455
- text_content = content.decode('utf-8', errors='ignore')
456
-
457
- if target == 'txt':
458
- file_content = text_content.encode('utf-8')
459
- mime_type = 'text/plain'
460
- elif target == 'csv':
461
- lines = text_content.split('\n')
462
- csv_content = '\n'.join([','.join(line.split()) for line in lines if line.strip()])
463
- file_content = csv_content.encode('utf-8')
464
- mime_type = 'text/csv'
465
- elif target == 'json':
466
- try:
467
- data = json.loads(text_content)
468
- file_content = json.dumps(data, indent=2).encode('utf-8')
469
- except:
470
- file_content = json.dumps({"content": text_content[:500]}).encode('utf-8')
471
- mime_type = 'application/json'
472
- else:
473
- file_content = content
474
- mime_type = 'application/octet-stream'
475
-
476
- return Response(
477
- content=file_content,
478
- media_type=mime_type,
479
- headers={
480
- "Content-Disposition": f"attachment; filename=converted.{target}",
481
- "Content-Length": str(len(file_content))
482
- }
483
- )
484
- except:
485
- return Response(
486
- content=content,
487
- media_type='application/octet-stream',
488
- headers={
489
- "Content-Disposition": f"attachment; filename=converted.{target}",
490
- "Content-Length": str(len(content))
491
- }
492
- )
493
 
494
  except Exception as e:
495
  return JSONResponse({"error": str(e)}, status_code=500)
@@ -515,54 +674,6 @@ async def convert_image(
515
  except Exception as e:
516
  return JSONResponse({"error": str(e)}, status_code=500)
517
 
518
- @app.post("/api/remove-background")
519
- async def remove_background(file: UploadFile = File(...)):
520
- """Remove background from image"""
521
- if not HF_TOKEN:
522
- try:
523
- content = await file.read()
524
- img = Image.open(io.BytesIO(content))
525
- if img.mode != 'RGBA':
526
- img = img.convert('RGBA')
527
- output = io.BytesIO()
528
- img.save(output, format='PNG')
529
- output.seek(0)
530
- return Response(
531
- content=output.getvalue(),
532
- media_type="image/png",
533
- headers={"Content-Disposition": "attachment; filename=no_background.png"}
534
- )
535
- except:
536
- return JSONResponse({"error": "HF_TOKEN not configured"}, status_code=401)
537
-
538
- try:
539
- image_bytes = await file.read()
540
- headers = {"Authorization": f"Bearer {HF_TOKEN}"}
541
- url = "https://api-inference.huggingface.co/models/briaai/RMBG-1.4"
542
-
543
- response = requests.post(url, headers=headers, data=image_bytes, timeout=30)
544
-
545
- if response.status_code == 200:
546
- return Response(
547
- content=response.content,
548
- media_type="image/png",
549
- headers={"Content-Disposition": "attachment; filename=no_background.png"}
550
- )
551
- else:
552
- img = Image.open(io.BytesIO(image_bytes))
553
- if img.mode != 'RGBA':
554
- img = img.convert('RGBA')
555
- output = io.BytesIO()
556
- img.save(output, format='PNG')
557
- output.seek(0)
558
- return Response(
559
- content=output.getvalue(),
560
- media_type="image/png",
561
- headers={"Content-Disposition": "attachment; filename=no_background.png"}
562
- )
563
- except Exception as e:
564
- return JSONResponse({"error": str(e)}, status_code=500)
565
-
566
  @app.post("/api/extract-text")
567
  async def extract_text(file: UploadFile = File(...)):
568
  """Extract text from image using OCR"""
@@ -571,13 +682,11 @@ async def extract_text(file: UploadFile = File(...)):
571
 
572
  try:
573
  image_bytes = await file.read()
574
- headers = {"Authorization": f"Bearer {HF_TOKEN}"}
575
  url = "https://api-inference.huggingface.co/models/microsoft/trocr-large-printed"
 
576
 
577
- response = requests.post(url, headers=headers, data=image_bytes, timeout=30)
578
-
579
- if response.status_code == 200:
580
- text = response.content.decode('utf-8', errors='ignore')
581
  return JSONResponse({"text": text, "success": True})
582
  else:
583
  return JSONResponse({"text": "OCR failed", "error": "OCR failed"}, status_code=500)
@@ -592,13 +701,11 @@ async def caption_image(file: UploadFile = File(...)):
592
 
593
  try:
594
  image_bytes = await file.read()
595
- headers = {"Authorization": f"Bearer {HF_TOKEN}"}
596
  url = "https://api-inference.huggingface.co/models/Salesforce/blip-image-captioning-base"
 
597
 
598
- response = requests.post(url, headers=headers, data=image_bytes, timeout=30)
599
-
600
- if response.status_code == 200:
601
- caption = response.content.decode('utf-8', errors='ignore')
602
  return JSONResponse({"caption": caption, "success": True})
603
  else:
604
  return JSONResponse({"caption": "Caption generation failed", "error": "Failed"}, status_code=500)
@@ -621,13 +728,11 @@ async def summarize_text(file: UploadFile = File(...)):
621
  if len(text) > 2000:
622
  text = text[:2000]
623
 
624
- headers = {"Authorization": f"Bearer {HF_TOKEN}"}
625
  url = "https://api-inference.huggingface.co/models/facebook/bart-large-cnn"
 
626
 
627
- response = requests.post(url, headers=headers, data=text.encode(), timeout=30)
628
-
629
- if response.status_code == 200:
630
- summary = response.content.decode('utf-8', errors='ignore')
631
  return JSONResponse({"summary": summary, "success": True})
632
  else:
633
  return JSONResponse({"summary": text[:200] + "...", "error": "Failed"}, status_code=500)
@@ -645,11 +750,10 @@ async def vectorize_logo(file: UploadFile = File(...)):
645
  caption = "CONVERTLY Logo"
646
  if HF_TOKEN:
647
  try:
648
- headers = {"Authorization": f"Bearer {HF_TOKEN}"}
649
  url = "https://api-inference.huggingface.co/models/Salesforce/blip-image-captioning-base"
650
- response = requests.post(url, headers=headers, data=image_bytes, timeout=30)
651
- if response.status_code == 200:
652
- caption = response.content.decode('utf-8', errors='ignore')[:50]
653
  except:
654
  pass
655
 
 
7
  import requests
8
  from PIL import Image
9
  import json
 
 
 
 
10
  import textwrap
11
+ import time
12
+ from datetime import datetime
13
 
14
  app = FastAPI(title="CONVERTLY API", version="2.0.0")
15
 
 
23
 
24
  HF_TOKEN = os.getenv("HF_TOKEN", "")
25
 
26
+ # ===== PROFESSIONAL PDF GENERATION WITH PROPER FORMATTING =====
27
 
28
+ def create_professional_pdf(text_content, title="Document"):
29
+ """Create a properly formatted PDF with correct margins and text flow"""
30
  try:
31
+ from reportlab.lib.pagesizes import A4
32
+ from reportlab.lib.units import inch, cm
33
+ from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
34
+ from reportlab.lib.enums import TA_LEFT, TA_CENTER, TA_JUSTIFY
35
+ from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, PageBreak
36
+ from reportlab.lib import colors
37
  from reportlab.pdfbase import pdfmetrics
38
  from reportlab.pdfbase.ttfonts import TTFont
39
  import textwrap
40
 
41
  buffer = io.BytesIO()
42
+
43
+ # ===== DOCUMENT SETUP =====
44
+ # Use A4 with standard margins (1 inch / 2.54 cm)
45
+ doc = SimpleDocTemplate(
46
+ buffer,
47
+ pagesize=A4,
48
+ rightMargin=2.54 * cm,
49
+ leftMargin=2.54 * cm,
50
+ topMargin=2.54 * cm,
51
+ bottomMargin=2.54 * cm,
52
+ )
53
+
54
+ # ===== STYLES =====
55
+ styles = getSampleStyleSheet()
56
+
57
+ # Title style
58
+ title_style = ParagraphStyle(
59
+ 'CustomTitle',
60
+ parent=styles['Title'],
61
+ fontSize=20,
62
+ fontName='Helvetica-Bold',
63
+ alignment=TA_CENTER,
64
+ spaceAfter=20,
65
+ textColor=colors.HexColor('#1a1a2e')
66
+ )
67
+
68
+ # Heading style
69
+ heading_style = ParagraphStyle(
70
+ 'CustomHeading',
71
+ parent=styles['Heading2'],
72
+ fontSize=14,
73
+ fontName='Helvetica-Bold',
74
+ alignment=TA_LEFT,
75
+ spaceAfter=10,
76
+ spaceBefore=15,
77
+ textColor=colors.HexColor('#2d2d44')
78
+ )
79
+
80
+ # Body text style - JUSTIFIED for professional look
81
+ body_style = ParagraphStyle(
82
+ 'CustomBody',
83
+ parent=styles['Normal'],
84
+ fontSize=11,
85
+ fontName='Helvetica',
86
+ alignment=TA_JUSTIFY, # Justified text like real documents
87
+ spaceAfter=8,
88
+ leading=16, # Line spacing
89
+ textColor=colors.HexColor('#333333')
90
+ )
91
+
92
+ # ===== BUILD CONTENT =====
93
+ story = []
94
+
95
+ # Add title
96
+ story.append(Paragraph(f"<b>{title}</b>", title_style))
97
+ story.append(Spacer(1, 0.3 * inch))
98
+
99
+ # Add date (subtle)
100
+ date_style = ParagraphStyle(
101
+ 'Date',
102
+ parent=styles['Normal'],
103
+ fontSize=10,
104
+ fontName='Helvetica',
105
+ alignment=TA_LEFT,
106
+ textColor=colors.HexColor('#888888'),
107
+ spaceAfter=20
108
+ )
109
+ story.append(Paragraph(f"Generated: {datetime.now().strftime('%B %d, %Y at %I:%M %p')}", date_style))
110
+ story.append(Spacer(1, 0.2 * inch))
111
+
112
+ # ===== PROCESS TEXT CONTENT =====
113
+ if text_content:
114
+ # Split into paragraphs (handle different line endings)
115
+ paragraphs = []
116
+ raw_paragraphs = text_content.split('\n\n')
117
+
118
+ for para in raw_paragraphs:
119
  # Clean the paragraph
120
+ cleaned = para.strip()
121
+ if cleaned:
122
+ # Remove extra spaces
123
+ cleaned = ' '.join(cleaned.split())
124
+ paragraphs.append(cleaned)
125
+
126
+ # Add each paragraph with proper formatting
127
+ for i, para in enumerate(paragraphs):
128
+ # Check if it might be a heading (short, no period at end)
129
+ is_heading = len(para) < 60 and not para.endswith('.') and not para.endswith('?') and not para.endswith('!')
130
+ is_heading = is_heading or (len(para.split()) < 8 and not para.endswith('.'))
131
+
132
+ if is_heading and i < len(paragraphs) - 1:
133
+ # It's a heading - use heading style
134
+ story.append(Paragraph(para, heading_style))
135
+ else:
136
+ # Regular paragraph - use body style
137
+ # Wrap long paragraphs for better flow
138
+ if len(para) > 800:
139
+ # Split into smaller paragraphs
140
+ sentences = para.split('. ')
141
+ for j, sentence in enumerate(sentences):
142
+ if sentence.strip():
143
+ if j < len(sentences) - 1:
144
+ story.append(Paragraph(sentence + '.', body_style))
145
+ else:
146
+ story.append(Paragraph(sentence, body_style))
147
+ else:
148
+ story.append(Paragraph(para, body_style))
149
 
150
+ # Add spacing between paragraphs
151
+ if i < len(paragraphs) - 1:
152
+ story.append(Spacer(1, 0.05 * inch))
153
 
154
+ # ===== BUILD PDF =====
155
+ doc.build(story)
156
  buffer.seek(0)
157
  return buffer.getvalue()
158
 
159
  except Exception as e:
160
+ print(f"Professional PDF error: {e}")
161
+ return create_simple_pdf(text_content, title)
 
162
 
163
+
164
+ def create_simple_pdf(text_content, title="Document"):
165
+ """Fallback: Simple PDF with basic formatting"""
166
  try:
167
+ from reportlab.lib.pagesizes import A4
168
+ from reportlab.lib.units import inch, cm
169
+ from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
170
+ from reportlab.lib.enums import TA_LEFT, TA_CENTER, TA_JUSTIFY
171
+ from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer
172
+ from reportlab.lib import colors
173
 
174
  buffer = io.BytesIO()
 
 
175
 
176
+ doc = SimpleDocTemplate(
177
+ buffer,
178
+ pagesize=A4,
179
+ rightMargin=2.54 * cm,
180
+ leftMargin=2.54 * cm,
181
+ topMargin=2.54 * cm,
182
+ bottomMargin=2.54 * cm,
183
+ )
184
 
185
+ styles = getSampleStyleSheet()
 
186
 
187
+ body_style = ParagraphStyle(
188
+ 'Body',
189
+ parent=styles['Normal'],
190
+ fontSize=11,
191
+ fontName='Helvetica',
192
+ alignment=TA_JUSTIFY,
193
+ spaceAfter=8,
194
+ leading=16
195
+ )
196
 
197
+ story = []
 
198
 
199
+ # Title
200
+ title_style = ParagraphStyle(
201
+ 'Title',
202
+ parent=styles['Title'],
203
+ fontSize=18,
204
+ alignment=TA_CENTER,
205
+ spaceAfter=20
206
+ )
207
+ story.append(Paragraph(title, title_style))
208
+ story.append(Spacer(1, 0.2 * inch))
209
+
210
+ # Content
211
+ for para in text_content.split('\n\n'):
212
+ if para.strip():
213
+ cleaned = ' '.join(para.split())
214
+ story.append(Paragraph(cleaned, body_style))
215
+ story.append(Spacer(1, 0.05 * inch))
216
 
217
+ doc.build(story)
218
  buffer.seek(0)
219
  return buffer.getvalue()
220
 
221
  except Exception as e:
222
+ print(f"Simple PDF error: {e}")
223
+ return text_content.encode('utf-8')
224
 
 
225
 
226
+ def extract_text_from_docx(docx_bytes):
227
+ """Extract text from DOCX with formatting preservation"""
228
  try:
229
  from docx import Document
230
+ from docx.oxml import parse_xml
231
+ from docx.oxml.ns import qn
232
 
 
233
  doc = Document(io.BytesIO(docx_bytes))
234
 
 
235
  full_text = []
236
 
237
  # Extract paragraphs
238
  for para in doc.paragraphs:
239
  if para.text.strip():
240
+ # Check if it's a heading (based on style)
241
+ is_heading = 'heading' in para.style.name.lower() if para.style else False
242
+ if is_heading:
243
+ full_text.append(f"\n{para.text.strip()}\n")
244
+ else:
245
+ full_text.append(para.text.strip())
246
 
247
  # Extract tables
248
  for table in doc.tables:
 
252
  if cell.text.strip():
253
  row_text.append(cell.text.strip())
254
  if row_text:
255
+ full_text.append(' | '.join(row_text))
 
 
256
 
257
+ return '\n\n'.join(full_text) if full_text else "No text content found in document."
 
258
 
259
+ except Exception as e:
260
+ print(f"DOCX extraction error: {e}")
261
+ return "Error extracting text from DOCX."
262
+
263
+
264
+ # ===== CONVERSION FUNCTIONS =====
265
+
266
+ def convert_docx_to_pdf(docx_bytes):
267
+ """Convert DOCX to PDF with professional formatting"""
268
+ try:
269
+ text = extract_text_from_docx(docx_bytes)
270
+ return create_professional_pdf(text, "Document")
271
  except Exception as e:
272
  print(f"DOCX to PDF error: {e}")
273
  return docx_bytes
274
 
275
+
276
  def convert_pdf_to_docx(pdf_bytes):
277
+ """Convert PDF to DOCX"""
278
  try:
279
  from PyPDF2 import PdfReader
280
  from docx import Document
281
+ from docx.shared import Inches, Pt
282
+ from docx.enum.text import WD_ALIGN_PARAGRAPH
283
 
 
284
  pdf = PdfReader(io.BytesIO(pdf_bytes))
 
 
285
  doc = Document()
286
 
287
+ # Title
288
+ title = doc.add_heading('Converted from PDF', 0)
289
+ title.alignment = WD_ALIGN_PARAGRAPH.CENTER
290
+
291
+ # Date
292
+ from datetime import datetime
293
+ date_para = doc.add_paragraph(f"Generated: {datetime.now().strftime('%B %d, %Y at %I:%M %p')}")
294
+ date_para.alignment = WD_ALIGN_PARAGRAPH.CENTER
295
+ doc.add_paragraph()
296
+
297
  # Extract text from each page
298
+ for page_num, page in enumerate(pdf.pages, 1):
299
  text = page.extract_text()
300
  if text and text.strip():
301
+ doc.add_heading(f'Page {page_num}', 1)
302
  paragraphs = text.split('\n\n')
303
  for para in paragraphs:
304
  if para.strip():
305
  cleaned = ' '.join(para.split())
306
  doc.add_paragraph(cleaned)
307
+ else:
308
+ doc.add_paragraph(f"[No text extracted from page {page_num}]")
309
+
310
+ if page_num < len(pdf.pages):
311
+ doc.add_page_break()
312
 
313
  buffer = io.BytesIO()
314
  doc.save(buffer)
 
319
  print(f"PDF to DOCX error: {e}")
320
  return pdf_bytes
321
 
322
+
323
  def convert_pdf_to_excel(pdf_bytes):
324
+ """Convert PDF to Excel"""
325
  try:
326
  import openpyxl
327
+ from openpyxl.styles import Font, PatternFill, Alignment
328
  from PyPDF2 import PdfReader
329
 
330
  wb = openpyxl.Workbook()
331
  ws = wb.active
332
  ws.title = "Extracted Data"
333
 
334
+ # Style
335
+ header_font = Font(bold=True, color="FFFFFF")
336
+ header_fill = PatternFill(start_color="4F46E5", end_color="4F46E5", fill_type="solid")
337
+
338
  pdf = PdfReader(io.BytesIO(pdf_bytes))
339
  row = 1
340
 
341
+ # Headers
342
+ ws.cell(row=row, column=1, value="Page")
343
+ ws.cell(row=row, column=2, value="Content")
344
+ ws.cell(row=row, column=1).font = header_font
345
+ ws.cell(row=row, column=1).fill = header_fill
346
+ ws.cell(row=row, column=2).font = header_font
347
+ ws.cell(row=row, column=2).fill = header_fill
348
+ row += 1
349
+
350
+ for page_num, page in enumerate(pdf.pages, 1):
351
  text = page.extract_text()
352
  if text:
353
  lines = text.split('\n')
354
  for line in lines:
355
  if line.strip():
356
+ ws.cell(row=row, column=1, value=page_num)
357
+ ws.cell(row=row, column=2, value=line.strip()[:500])
358
  row += 1
359
 
360
+ # Auto-adjust columns
361
+ for column in ws.columns:
362
+ max_length = 0
363
+ for cell in column:
364
+ try:
365
+ if len(str(cell.value)) > max_length:
366
+ max_length = len(str(cell.value))
367
+ except:
368
+ pass
369
+ adjusted_width = min(max_length + 2, 50)
370
+ ws.column_dimensions[column[0].column_letter].width = adjusted_width
371
+
372
  buffer = io.BytesIO()
373
  wb.save(buffer)
374
  buffer.seek(0)
 
378
  print(f"PDF to Excel error: {e}")
379
  return pdf_bytes
380
 
381
+
382
  def convert_pdf_to_pptx(pdf_bytes):
383
  """Convert PDF to PPTX"""
384
  try:
385
  from pptx import Presentation
386
+ from pptx.util import Inches, Pt
387
  from PyPDF2 import PdfReader
388
 
389
  prs = Presentation()
390
+ title_slide_layout = prs.slide_layouts[0]
391
  content_slide_layout = prs.slide_layouts[1]
392
 
393
+ # Title slide
394
+ slide = prs.slides.add_slide(title_slide_layout)
395
+ title = slide.shapes.title
396
+ subtitle = slide.placeholders[1]
397
+ title.text = "PDF Converted to PowerPoint"
398
+ subtitle.text = f"Generated: {datetime.now().strftime('%B %d, %Y')}"
399
+
400
  pdf = PdfReader(io.BytesIO(pdf_bytes))
401
 
402
+ for i, page in enumerate(pdf.pages):
403
  slide = prs.slides.add_slide(content_slide_layout)
404
  title = slide.shapes.title
405
  content = slide.placeholders[1]
406
 
407
+ title.text = f"Page {i+1}"
408
  text = page.extract_text()
409
  if text and text.strip():
410
  content.text = text[:1000] + "..." if len(text) > 1000 else text
411
  else:
412
+ content.text = "No text extracted from this page"
413
 
414
  buffer = io.BytesIO()
415
  prs.save(buffer)
 
420
  print(f"PDF to PPTX error: {e}")
421
  return pdf_bytes
422
 
423
+
424
  def convert_excel_to_pdf(excel_bytes):
425
  """Convert Excel to PDF"""
426
  try:
427
  import openpyxl
428
+ from reportlab.lib.pagesizes import A4, landscape
429
+ from reportlab.lib import colors
430
+ from reportlab.lib.units import inch, cm
431
+ from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle
432
+ from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
433
+ from reportlab.lib.enums import TA_CENTER, TA_LEFT
434
 
435
  wb = openpyxl.load_workbook(io.BytesIO(excel_bytes))
436
  ws = wb.active
437
 
438
  buffer = io.BytesIO()
439
+ doc = SimpleDocTemplate(
440
+ buffer,
441
+ pagesize=landscape(A4),
442
+ rightMargin=1.5 * cm,
443
+ leftMargin=1.5 * cm,
444
+ topMargin=2 * cm,
445
+ bottomMargin=2 * cm,
446
+ )
447
 
448
+ styles = getSampleStyleSheet()
449
+ title_style = ParagraphStyle('Title', parent=styles['Title'], fontSize=16, alignment=TA_CENTER, spaceAfter=20)
 
 
450
 
451
+ story = []
452
+ story.append(Paragraph("Excel to PDF Conversion", title_style))
453
+ story.append(Spacer(1, 0.2 * inch))
454
 
455
+ # Convert rows
456
+ data = []
457
  for row in ws.iter_rows(values=True):
458
+ row_data = [str(cell) if cell else '' for cell in row]
459
+ if any(row_data):
460
+ data.append(row_data)
461
+
462
+ if data:
463
+ table = Table(data)
464
+ table.setStyle(TableStyle([
465
+ ('BACKGROUND', (0, 0), (-1, 0), colors.grey),
466
+ ('TEXTCOLOR', (0, 0), (-1, 0), colors.whitesmoke),
467
+ ('ALIGN', (0, 0), (-1, -1), 'CENTER'),
468
+ ('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
469
+ ('FONTSIZE', (0, 0), (-1, 0), 10),
470
+ ('BOTTOMPADDING', (0, 0), (-1, 0), 12),
471
+ ('BACKGROUND', (0, 1), (-1, -1), colors.beige),
472
+ ('GRID', (0, 0), (-1, -1), 1, colors.black)
473
+ ]))
474
+ story.append(table)
475
+
476
+ doc.build(story)
477
  buffer.seek(0)
478
  return buffer.getvalue()
479
 
 
481
  print(f"Excel to PDF error: {e}")
482
  return excel_bytes
483
 
484
+
485
  def convert_ppt_to_pdf(pptx_bytes):
486
  """Convert PPTX to PDF"""
487
  try:
488
  from pptx import Presentation
489
+ from reportlab.lib.pagesizes import A4
490
+ from reportlab.lib.units import inch, cm
491
+ from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, PageBreak
492
+ from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
493
+ from reportlab.lib.enums import TA_LEFT, TA_CENTER
494
 
495
  prs = Presentation(io.BytesIO(pptx_bytes))
496
 
497
  buffer = io.BytesIO()
498
+ doc = SimpleDocTemplate(
499
+ buffer,
500
+ pagesize=A4,
501
+ rightMargin=2 * cm,
502
+ leftMargin=2 * cm,
503
+ topMargin=2 * cm,
504
+ bottomMargin=2 * cm,
505
+ )
506
 
507
+ styles = getSampleStyleSheet()
508
+ title_style = ParagraphStyle('Title', parent=styles['Title'], fontSize=16, alignment=TA_CENTER, spaceAfter=20)
509
+ slide_style = ParagraphStyle('Slide', parent=styles['Heading2'], fontSize=14, spaceAfter=10)
510
 
511
+ story = []
512
+ story.append(Paragraph("PowerPoint to PDF Conversion", title_style))
513
+ story.append(Spacer(1, 0.2 * inch))
514
 
515
+ for slide_num, slide in enumerate(prs.slides, 1):
516
+ story.append(Paragraph(f"<b>Slide {slide_num}</b>", slide_style))
517
+ story.append(Spacer(1, 0.1 * inch))
518
+
519
  for shape in slide.shapes:
520
  if hasattr(shape, "text") and shape.text:
521
+ text = shape.text.strip()
522
+ if text:
523
+ story.append(Paragraph(text, styles['Normal']))
524
+ story.append(Spacer(1, 0.05 * inch))
525
 
526
+ if slide_num < len(prs.slides):
527
+ story.append(PageBreak())
528
+
529
+ doc.build(story)
 
 
 
 
 
 
 
 
 
530
  buffer.seek(0)
531
  return buffer.getvalue()
532
 
 
534
  print(f"PPT to PDF error: {e}")
535
  return pptx_bytes
536
 
537
+
538
  def convert_image_to_other(img_bytes, target_format):
539
  """Convert image to different format"""
540
+ try:
541
+ img = Image.open(io.BytesIO(img_bytes))
542
+ output = io.BytesIO()
543
+
544
+ fmt = target_format.upper()
545
+ if fmt == "JPG":
546
+ fmt = "JPEG"
547
+ if img.mode in ('RGBA', 'P'):
548
+ rgb_img = Image.new('RGB', img.size, (255, 255, 255))
549
+ rgb_img.paste(img, mask=img.split()[-1] if img.mode == 'RGBA' else None)
550
+ img = rgb_img
551
+
552
+ img.save(output, format=fmt, quality=95)
553
+ output.seek(0)
554
+ return output.getvalue()
555
+
556
+ except Exception as e:
557
+ print(f"Image conversion error: {e}")
558
+ return img_bytes
559
+
560
+
561
+ def call_hf_api(url, data, max_retries=3):
562
+ """Call Hugging Face API with retry logic"""
563
+ if not HF_TOKEN:
564
+ return None
565
 
566
+ headers = {"Authorization": f"Bearer {HF_TOKEN}"}
 
 
 
 
 
 
567
 
568
+ for attempt in range(max_retries):
569
+ try:
570
+ response = requests.post(url, headers=headers, data=data, timeout=60)
571
+ if response.status_code == 200:
572
+ return response.content
573
+ elif response.status_code == 503:
574
+ time.sleep(2 ** attempt)
575
+ continue
576
+ else:
577
+ return None
578
+ except:
579
+ if attempt < max_retries - 1:
580
+ time.sleep(2 ** attempt)
581
+ continue
582
+ return None
583
+ return None
584
+
585
 
586
  # ===== API ENDPOINTS =====
587
 
 
614
  ext = filename.split('.')[-1].lower() if '.' in filename else ''
615
  target = target_format.lower()
616
 
 
617
  format_map = {
618
  ('docx', 'pdf'): ('application/pdf', 'pdf', convert_docx_to_pdf),
619
  ('pdf', 'docx'): ('application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'docx', convert_pdf_to_docx),
 
639
  )
640
  except Exception as e:
641
  print(f"Conversion error: {e}")
 
642
 
643
+ # Fallback: return original
644
+ return Response(
645
+ content=content,
646
+ media_type='application/octet-stream',
647
+ headers={
648
+ "Content-Disposition": f"attachment; filename=converted.{target}",
649
+ "Content-Length": str(len(content))
650
+ }
651
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
652
 
653
  except Exception as e:
654
  return JSONResponse({"error": str(e)}, status_code=500)
 
674
  except Exception as e:
675
  return JSONResponse({"error": str(e)}, status_code=500)
676
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
677
  @app.post("/api/extract-text")
678
  async def extract_text(file: UploadFile = File(...)):
679
  """Extract text from image using OCR"""
 
682
 
683
  try:
684
  image_bytes = await file.read()
 
685
  url = "https://api-inference.huggingface.co/models/microsoft/trocr-large-printed"
686
+ result = call_hf_api(url, image_bytes)
687
 
688
+ if result:
689
+ text = result.decode('utf-8', errors='ignore')
 
 
690
  return JSONResponse({"text": text, "success": True})
691
  else:
692
  return JSONResponse({"text": "OCR failed", "error": "OCR failed"}, status_code=500)
 
701
 
702
  try:
703
  image_bytes = await file.read()
 
704
  url = "https://api-inference.huggingface.co/models/Salesforce/blip-image-captioning-base"
705
+ result = call_hf_api(url, image_bytes)
706
 
707
+ if result:
708
+ caption = result.decode('utf-8', errors='ignore')
 
 
709
  return JSONResponse({"caption": caption, "success": True})
710
  else:
711
  return JSONResponse({"caption": "Caption generation failed", "error": "Failed"}, status_code=500)
 
728
  if len(text) > 2000:
729
  text = text[:2000]
730
 
 
731
  url = "https://api-inference.huggingface.co/models/facebook/bart-large-cnn"
732
+ result = call_hf_api(url, text.encode())
733
 
734
+ if result:
735
+ summary = result.decode('utf-8', errors='ignore')
 
 
736
  return JSONResponse({"summary": summary, "success": True})
737
  else:
738
  return JSONResponse({"summary": text[:200] + "...", "error": "Failed"}, status_code=500)
 
750
  caption = "CONVERTLY Logo"
751
  if HF_TOKEN:
752
  try:
 
753
  url = "https://api-inference.huggingface.co/models/Salesforce/blip-image-captioning-base"
754
+ result = call_hf_api(url, image_bytes)
755
+ if result:
756
+ caption = result.decode('utf-8', errors='ignore')[:50]
757
  except:
758
  pass
759