andevs commited on
Commit
aa0f161
Β·
verified Β·
1 Parent(s): 7803f24

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +733 -484
app.py CHANGED
@@ -14,6 +14,9 @@ import tempfile
14
  import subprocess
15
  import shutil
16
  import re
 
 
 
17
 
18
  app = FastAPI(title="CONVERTLY API", version="2.0.0")
19
 
@@ -26,20 +29,92 @@ app.add_middleware(
26
  )
27
 
28
  HF_TOKEN = os.getenv("HF_TOKEN", "")
 
29
 
30
- # ===== PROFESSIONAL PDF GENERATION WITH FULL FORMATTING =====
 
 
 
 
 
 
 
 
 
 
31
 
32
- def create_professional_pdf(paragraphs_data, title="Document"):
33
- """Create a PDF that preserves ALL formatting including bullets, numbers, styles"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
34
  try:
35
  from reportlab.lib.pagesizes import A4
36
- from reportlab.lib.units import inch, cm
37
  from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
38
- from reportlab.lib.enums import TA_LEFT, TA_CENTER, TA_JUSTIFY, TA_RIGHT
39
- from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, PageBreak, KeepTogether
40
  from reportlab.lib import colors
41
- from reportlab.pdfbase import pdfmetrics
42
- from reportlab.pdfbase.ttfonts import TTFont
43
 
44
  buffer = io.BytesIO()
45
 
@@ -54,520 +129,440 @@ def create_professional_pdf(paragraphs_data, title="Document"):
54
 
55
  styles = getSampleStyleSheet()
56
 
57
- # ===== STYLES =====
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
- heading1_style = ParagraphStyle(
69
- 'Heading1',
70
- parent=styles['Heading1'],
71
- fontSize=16,
72
- fontName='Helvetica-Bold',
73
- alignment=TA_LEFT,
74
- spaceAfter=12,
75
- spaceBefore=18,
76
- textColor=colors.HexColor('#1a1a2e')
77
- )
78
-
79
- heading2_style = ParagraphStyle(
80
- 'Heading2',
81
- parent=styles['Heading2'],
82
- fontSize=14,
83
- fontName='Helvetica-Bold',
84
- alignment=TA_LEFT,
85
- spaceAfter=10,
86
- spaceBefore=15,
87
- textColor=colors.HexColor('#2d2d44')
88
- )
89
-
90
- heading3_style = ParagraphStyle(
91
- 'Heading3',
92
- parent=styles['Heading3'],
93
- fontSize=12,
94
- fontName='Helvetica-Bold',
95
- alignment=TA_LEFT,
96
- spaceAfter=8,
97
- spaceBefore=12,
98
- textColor=colors.HexColor('#333333')
99
- )
100
-
101
  body_style = ParagraphStyle(
102
- 'Body',
103
  parent=styles['Normal'],
104
  fontSize=11,
105
  fontName='Helvetica',
106
- alignment=TA_LEFT,
107
- spaceAfter=6,
108
- leading=16,
109
- textColor=colors.HexColor('#333333')
110
- )
111
-
112
- bullet_style = ParagraphStyle(
113
- 'Bullet',
114
- parent=styles['Normal'],
115
- fontSize=11,
116
- fontName='Helvetica',
117
- alignment=TA_LEFT,
118
- spaceAfter=4,
119
  leading=16,
120
- leftIndent=20,
121
  textColor=colors.HexColor('#333333')
122
  )
123
 
124
- number_style = ParagraphStyle(
125
- 'Numbered',
126
- parent=styles['Normal'],
127
- fontSize=11,
128
- fontName='Helvetica',
129
  alignment=TA_LEFT,
130
- spaceAfter=4,
131
- leading=16,
132
- leftIndent=20,
133
- textColor=colors.HexColor('#333333')
134
  )
135
 
136
  story = []
137
 
138
- # Add title if provided
139
- if title and title != "Document":
140
- story.append(Paragraph(title, title_style))
141
- story.append(Spacer(1, 0.3 * inch))
142
-
143
- # Process paragraphs with full formatting
144
- for para_data in paragraphs_data:
145
- text = para_data.get('text', '')
146
- style = para_data.get('style', 'Normal')
147
- level = para_data.get('level', 0)
148
 
149
- if not text:
150
- continue
 
 
 
151
 
152
- # Clean the text for XML
153
- text = text.replace('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;')
154
-
155
- # Add bullet symbols if needed
156
- if style == 'Bullet':
157
- bullet_symbol = 'β€’ ' * (level + 1) if level >= 0 else 'β€’ '
158
- formatted_text = f"{bullet_symbol}{text}"
159
- story.append(Paragraph(formatted_text, bullet_style))
160
-
161
- elif style == 'Numbered':
162
- # Numbered lists - we'll use the actual numbers from the text
163
- story.append(Paragraph(text, number_style))
164
 
165
- elif style.startswith('Heading'):
166
- if '1' in style or 'Title' in style:
167
- story.append(Paragraph(f"<b>{text}</b>", heading1_style))
168
- elif '2' in style:
169
- story.append(Paragraph(f"<b>{text}</b>", heading2_style))
170
  else:
171
- story.append(Paragraph(f"<b>{text}</b>", heading3_style))
172
- story.append(Spacer(1, 0.05 * inch))
 
 
 
 
 
 
 
 
173
 
174
- else:
175
- # Regular paragraph - preserve line breaks
176
- lines = text.split('\n')
177
- for line in lines:
178
- if line.strip():
179
- story.append(Paragraph(line, body_style))
180
- story.append(Spacer(1, 0.05 * inch))
181
 
182
  doc.build(story)
183
  buffer.seek(0)
184
  return buffer.getvalue()
185
 
186
  except Exception as e:
187
- print(f"Professional PDF error: {e}")
188
- return create_simple_pdf(paragraphs_data)
 
 
 
 
 
 
 
 
 
 
 
 
189
 
 
190
 
191
- def create_simple_pdf(paragraphs_data):
192
- """Fallback PDF generator"""
193
  try:
194
- from reportlab.lib.pagesizes import A4
195
- from reportlab.lib.units import cm
196
- from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
197
- from reportlab.lib.enums import TA_LEFT
198
- from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer
199
- from reportlab.lib import colors
200
-
201
- buffer = io.BytesIO()
202
 
203
- doc = SimpleDocTemplate(
204
- buffer,
205
- pagesize=A4,
206
- rightMargin=2.54 * cm,
207
- leftMargin=2.54 * cm,
208
- topMargin=2.54 * cm,
209
- bottomMargin=2.54 * cm,
210
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
211
 
212
- styles = getSampleStyleSheet()
213
- body_style = ParagraphStyle('Body', parent=styles['Normal'], fontSize=11, spaceAfter=6, leading=16)
214
 
215
- story = []
216
- for para_data in paragraphs_data:
217
- text = para_data.get('text', '')
218
- if text:
219
- story.append(Paragraph(text, body_style))
220
- story.append(Spacer(1, 0.05 * cm))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
221
 
222
- doc.build(story)
223
- buffer.seek(0)
224
- return buffer.getvalue()
 
225
 
226
  except Exception as e:
227
- print(f"Simple PDF error: {e}")
228
- return str(paragraphs_data).encode('utf-8')
229
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
230
 
231
- def parse_docx_to_paragraphs(docx_bytes):
232
- """Extract paragraphs with ALL formatting from DOCX"""
233
  try:
234
- from docx import Document
235
- from docx.oxml import parse_xml
236
- from docx.oxml.ns import qn, nsmap
237
- import re
238
 
239
- doc = Document(io.BytesIO(docx_bytes))
240
- paragraphs = []
241
- bullet_counter = 0
242
- number_counter = 0
243
 
244
- for para in doc.paragraphs:
245
- text = para.text.strip()
246
- if not text:
 
 
 
 
 
 
 
 
 
 
247
  continue
248
-
249
- # Detect style
250
- style_name = para.style.name.lower() if para.style else ''
251
-
252
- # Check for bullet points
253
- is_bullet = False
254
- is_numbered = False
255
- level = 0
256
-
257
- # Check if it's a bullet using paragraph properties
258
- if para._element.p is not None:
259
- pPr = para._element.p.pPr
260
- if pPr is not None:
261
- # Check for numbering (bullets/numbers)
262
- numPr = pPr.numPr
263
- if numPr is not None:
264
- ilvl = numPr.ilvl
265
- if ilvl is not None:
266
- level = int(ilvl.val)
267
- # Check if it's bullet or numbered
268
- numId = numPr.numId
269
- if numId is not None:
270
- # If the numbering ID is 0, it's usually bullet
271
- if int(numId.val) == 0:
272
- is_bullet = True
273
- else:
274
- is_numbered = True
275
-
276
- # Also check for bullet symbols in text
277
- bullet_patterns = [r'^[‒●◦β–ͺ▫‣⁃]', r'^[\d]+[\.\)]\s', r'^[a-zA-Z][\.\)]\s']
278
- if not is_bullet and not is_numbered:
279
- for pattern in bullet_patterns:
280
- if re.match(pattern, text):
281
- is_bullet = True
282
- break
283
-
284
- # Create paragraph data
285
- para_data = {
286
- 'text': text,
287
- 'style': 'Normal',
288
- 'level': level,
289
- 'is_bullet': is_bullet,
290
- 'is_numbered': is_numbered
291
- }
292
-
293
- # Detect headings
294
- if 'heading 1' in style_name or 'heading1' in style_name:
295
- para_data['style'] = 'Heading1'
296
- elif 'heading 2' in style_name or 'heading2' in style_name:
297
- para_data['style'] = 'Heading2'
298
- elif 'heading 3' in style_name or 'heading3' in style_name:
299
- para_data['style'] = 'Heading3'
300
- elif 'heading' in style_name or 'title' in style_name:
301
- para_data['style'] = 'Heading1'
302
- elif is_bullet:
303
- para_data['style'] = 'Bullet'
304
- elif is_numbered:
305
- para_data['style'] = 'Numbered'
306
-
307
- paragraphs.append(para_data)
308
-
309
- # If no paragraphs found, try extracting tables
310
- if not paragraphs:
311
- for table in doc.tables:
312
- for row in table.rows:
313
- row_text = []
314
- for cell in row.cells:
315
- if cell.text.strip():
316
- row_text.append(cell.text.strip())
317
- if row_text:
318
- paragraphs.append({
319
- 'text': ' | '.join(row_text),
320
- 'style': 'Normal',
321
- 'level': 0
322
- })
323
-
324
- return paragraphs if paragraphs else [{'text': 'No text content found in document.', 'style': 'Normal', 'level': 0}]
325
 
326
  except Exception as e:
327
- print(f"DOCX parsing error: {e}")
328
- return [{'text': f'Error parsing document: {str(e)}', 'style': 'Normal', 'level': 0}]
329
-
330
 
331
- def convert_docx_to_pdf(docx_bytes):
332
- """Convert DOCX to PDF with full formatting preservation"""
333
  try:
334
- paragraphs = parse_docx_to_paragraphs(docx_bytes)
335
- return create_professional_pdf(paragraphs)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
336
  except Exception as e:
337
- print(f"DOCX to PDF error: {e}")
338
- return docx_bytes
339
-
340
 
341
- def convert_pdf_to_docx(pdf_bytes):
342
- """Convert PDF to DOCX - extract text with structure"""
343
  try:
344
  from PyPDF2 import PdfReader
345
- from docx import Document
346
- from docx.shared import Inches, Pt
347
- from docx.enum.text import WD_ALIGN_PARAGRAPH
348
 
349
- pdf = PdfReader(io.BytesIO(pdf_bytes))
350
- doc = Document()
351
 
352
- # Add title
353
- title = doc.add_heading('Converted from PDF', 0)
354
- title.alignment = WD_ALIGN_PARAGRAPH.CENTER
 
 
355
 
356
- doc.add_paragraph()
 
 
357
 
358
- for page_num, page in enumerate(pdf.pages, 1):
359
- text = page.extract_text()
360
- if text and text.strip():
361
- # Try to detect headings and structure
362
- lines = text.split('\n\n')
363
- for line in lines:
364
- if line.strip():
365
- # Check if it might be a heading
366
- if len(line) < 60 and not line.endswith('.') and not line.endswith('?') and not line.endswith('!'):
367
- doc.add_heading(line.strip(), 1)
368
- else:
369
- # Check for bullets
370
- if line.strip().startswith('β€’') or line.strip().startswith('-'):
371
- doc.add_paragraph(line.strip(), style='List Bullet')
372
- elif re.match(r'^[\d]+[\.\)]\s', line.strip()):
373
- doc.add_paragraph(line.strip(), style='List Number')
374
- else:
375
- doc.add_paragraph(line.strip())
376
- else:
377
- doc.add_paragraph()
378
- else:
379
- doc.add_paragraph(f"[No text extracted from page {page_num}]")
380
-
381
- if page_num < len(pdf.pages):
382
- doc.add_page_break()
383
-
384
- buffer = io.BytesIO()
385
- doc.save(buffer)
386
- buffer.seek(0)
387
- return buffer.getvalue()
388
 
389
  except Exception as e:
390
- print(f"PDF to DOCX error: {e}")
391
  return pdf_bytes
392
 
393
-
394
- def convert_ppt_to_pdf(pptx_bytes):
395
- """Convert PPTX to PDF - Extract with formatting"""
396
  try:
397
- from pptx import Presentation
398
- from reportlab.lib.pagesizes import A4
399
- from reportlab.lib.units import inch, cm
400
- from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, PageBreak
401
- from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
402
- from reportlab.lib.enums import TA_LEFT, TA_CENTER
403
- from reportlab.lib import colors
404
-
405
- prs = Presentation(io.BytesIO(pptx_bytes))
406
 
407
- buffer = io.BytesIO()
408
- doc = SimpleDocTemplate(
409
- buffer,
410
- pagesize=A4,
411
- rightMargin=2.54 * cm,
412
- leftMargin=2.54 * cm,
413
- topMargin=2.54 * cm,
414
- bottomMargin=2.54 * cm,
415
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
416
 
417
- styles = getSampleStyleSheet()
 
 
 
418
 
419
- title_style = ParagraphStyle(
420
- 'SlideTitle',
421
- parent=styles['Heading2'],
422
- fontSize=16,
423
- fontName='Helvetica-Bold',
424
- alignment=TA_CENTER,
425
- spaceAfter=15,
426
- textColor=colors.HexColor('#1a1a2e')
427
- )
428
 
429
- body_style = ParagraphStyle(
430
- 'SlideBody',
431
- parent=styles['Normal'],
432
- fontSize=11,
433
- fontName='Helvetica',
434
- alignment=TA_LEFT,
435
- spaceAfter=6,
436
- leading=16
437
- )
438
 
439
- story = []
 
440
 
441
- for slide_num, slide in enumerate(prs.slides, 1):
442
- slide_texts = []
443
- for shape in slide.shapes:
444
- if hasattr(shape, "text") and shape.text:
445
- text = shape.text.strip()
446
- if text:
447
- slide_texts.append(text)
448
-
449
- if slide_texts:
450
- story.append(Paragraph(f"Slide {slide_num}: {slide_texts[0]}", title_style))
451
- story.append(Spacer(1, 0.1 * inch))
452
-
453
- for text in slide_texts[1:]:
454
- if text:
455
- story.append(Paragraph(text, body_style))
456
- story.append(Spacer(1, 0.05 * inch))
457
- else:
458
- story.append(Paragraph(f"Slide {slide_num} - No text", title_style))
459
-
460
- if slide_num < len(prs.slides):
461
- story.append(PageBreak())
462
 
463
- doc.build(story)
464
- buffer.seek(0)
465
- return buffer.getvalue()
 
466
 
467
  except Exception as e:
468
- print(f"PPT to PDF error: {e}")
469
- return pptx_bytes
470
 
 
471
 
472
- def convert_pdf_to_ppt(pdf_bytes):
473
- """Convert PDF to PPTX"""
 
 
 
 
 
 
474
  try:
475
- from pptx import Presentation
476
  from PyPDF2 import PdfReader
477
-
478
- prs = Presentation()
479
- content_slide_layout = prs.slide_layouts[1]
480
 
481
  pdf = PdfReader(io.BytesIO(pdf_bytes))
 
482
 
483
- for page_num, page in enumerate(pdf.pages, 1):
484
- slide = prs.slides.add_slide(content_slide_layout)
485
- title = slide.shapes.title
486
- content = slide.placeholders[1]
487
-
488
- title.text = f"Page {page_num}"
489
  text = page.extract_text()
490
  if text and text.strip():
491
- content.text = text[:1000] + "..." if len(text) > 1000 else text
492
- else:
493
- content.text = "No text extracted"
 
 
494
 
495
  buffer = io.BytesIO()
496
- prs.save(buffer)
497
  buffer.seek(0)
498
  return buffer.getvalue()
499
-
500
- except Exception as e:
501
- print(f"PDF to PPT error: {e}")
502
  return pdf_bytes
503
 
504
-
505
- def convert_pdf_to_excel(pdf_bytes):
506
- """Convert PDF to Excel"""
507
  try:
508
  import openpyxl
509
- from openpyxl.styles import Font, PatternFill, Alignment
510
  from PyPDF2 import PdfReader
511
 
512
  wb = openpyxl.Workbook()
513
  ws = wb.active
514
  ws.title = "Extracted Data"
515
 
516
- header_font = Font(bold=True, color="FFFFFF")
517
- header_fill = PatternFill(start_color="4F46E5", end_color="4F46E5", fill_type="solid")
518
-
519
  pdf = PdfReader(io.BytesIO(pdf_bytes))
520
  row = 1
521
 
522
- ws.cell(row=row, column=1, value="Page")
523
- ws.cell(row=row, column=2, value="Content")
524
- ws.cell(row=row, column=1).font = header_font
525
- ws.cell(row=row, column=1).fill = header_fill
526
- ws.cell(row=row, column=2).font = header_font
527
- ws.cell(row=row, column=2).fill = header_fill
528
- row += 1
529
-
530
- for page_num, page in enumerate(pdf.pages, 1):
531
  text = page.extract_text()
532
  if text:
533
  lines = text.split('\n')
534
  for line in lines:
535
  if line.strip():
536
- ws.cell(row=row, column=1, value=page_num)
537
- ws.cell(row=row, column=2, value=line.strip()[:500])
538
  row += 1
539
 
540
- for column in ws.columns:
541
- max_length = 0
542
- for cell in column:
543
- try:
544
- if len(str(cell.value)) > max_length:
545
- max_length = len(str(cell.value))
546
- except:
547
- pass
548
- adjusted_width = min(max_length + 2, 50)
549
- ws.column_dimensions[column[0].column_letter].width = adjusted_width
550
-
551
  buffer = io.BytesIO()
552
  wb.save(buffer)
553
  buffer.seek(0)
554
  return buffer.getvalue()
555
-
556
- except Exception as e:
557
- print(f"PDF to Excel error: {e}")
558
  return pdf_bytes
559
 
560
-
561
- def convert_excel_to_pdf(excel_bytes):
562
- """Convert Excel to PDF"""
563
  try:
564
  import openpyxl
565
  from reportlab.lib.pagesizes import A4, landscape
566
  from reportlab.lib import colors
567
- from reportlab.lib.units import inch, cm
568
- from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle
569
- from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
570
- from reportlab.lib.enums import TA_CENTER, TA_LEFT
571
 
572
  wb = openpyxl.load_workbook(io.BytesIO(excel_bytes))
573
  ws = wb.active
@@ -582,8 +577,6 @@ def convert_excel_to_pdf(excel_bytes):
582
  bottomMargin=2 * cm,
583
  )
584
 
585
- story = []
586
-
587
  data = []
588
  for row in ws.iter_rows(values=True):
589
  row_data = [str(cell) if cell else '' for cell in row]
@@ -602,19 +595,94 @@ def convert_excel_to_pdf(excel_bytes):
602
  ('BACKGROUND', (0, 1), (-1, -1), colors.beige),
603
  ('GRID', (0, 0), (-1, -1), 1, colors.black)
604
  ]))
605
- story.append(table)
606
 
607
- doc.build(story)
608
  buffer.seek(0)
609
  return buffer.getvalue()
610
-
611
- except Exception as e:
612
- print(f"Excel to PDF error: {e}")
613
  return excel_bytes
614
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
615
 
616
- def convert_image_to_other(img_bytes, target_format):
617
- """Convert image to different format"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
618
  try:
619
  img = Image.open(io.BytesIO(img_bytes))
620
  output = io.BytesIO()
@@ -627,7 +695,6 @@ def convert_image_to_other(img_bytes, target_format):
627
  rgb_img.paste(img, mask=img.split()[-1] if img.mode == 'RGBA' else None)
628
  img = rgb_img
629
 
630
- # Handle special formats
631
  if fmt == "ICO":
632
  img.save(output, format='ICO')
633
  elif fmt == "WEBP":
@@ -643,19 +710,130 @@ def convert_image_to_other(img_bytes, target_format):
643
 
644
  output.seek(0)
645
  return output.getvalue()
646
-
647
- except Exception as e:
648
- print(f"Image conversion error: {e}")
649
  return img_bytes
650
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
651
 
652
  def call_hf_api(url, data, max_retries=3):
653
- """Call Hugging Face API with retry logic"""
654
  if not HF_TOKEN:
655
  return None
656
-
657
  headers = {"Authorization": f"Bearer {HF_TOKEN}"}
658
-
659
  for attempt in range(max_retries):
660
  try:
661
  response = requests.post(url, headers=headers, data=data, timeout=60)
@@ -681,7 +859,17 @@ async def root():
681
  "name": "CONVERTLY API",
682
  "version": "2.0.0",
683
  "status": "operational",
684
- "hf_token": "configured" if HF_TOKEN else "missing"
 
 
 
 
 
 
 
 
 
 
685
  }
686
 
687
  @app.get("/api/health")
@@ -689,40 +877,93 @@ async def health_check():
689
  return {
690
  "status": "healthy",
691
  "hf_token": "configured" if HF_TOKEN else "missing",
692
- "hf_token_length": len(HF_TOKEN) if HF_TOKEN else 0
 
 
 
 
 
 
 
 
 
 
693
  }
694
 
695
  @app.post("/api/convert")
696
  async def convert_file(
697
  file: UploadFile = File(...),
698
- target_format: str = Form("pdf")
 
699
  ):
700
- """Universal file converter - handles all formats with formatting preservation"""
701
  try:
702
  content = await file.read()
703
  filename = file.filename or "file"
704
- ext = filename.split('.')[-1].lower() if '.' in filename else ''
705
- target = target_format.lower()
706
 
707
- # Map source and target formats with conversion functions
708
- format_map = {
709
- # Document conversions - PRESERVES FORMATTING
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
710
  ('docx', 'pdf'): ('application/pdf', 'pdf', convert_docx_to_pdf),
711
  ('doc', 'pdf'): ('application/pdf', 'pdf', convert_docx_to_pdf),
712
  ('pdf', 'docx'): ('application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'docx', convert_pdf_to_docx),
713
  ('pdf', 'xlsx'): ('application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'xlsx', convert_pdf_to_excel),
714
- ('pdf', 'pptx'): ('application/vnd.openxmlformats-officedocument.presentationml.presentation', 'pptx', convert_pdf_to_ppt),
715
- ('pdf', 'ppt'): ('application/vnd.ms-powerpoint', 'ppt', convert_pdf_to_ppt),
716
  ('xlsx', 'pdf'): ('application/pdf', 'pdf', convert_excel_to_pdf),
717
  ('xls', 'pdf'): ('application/pdf', 'pdf', convert_excel_to_pdf),
718
  ('pptx', 'pdf'): ('application/pdf', 'pdf', convert_ppt_to_pdf),
719
  ('ppt', 'pdf'): ('application/pdf', 'pdf', convert_ppt_to_pdf),
 
 
720
  }
721
 
722
- converter_key = (ext, target)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
723
 
724
- if converter_key in format_map:
725
- mime_type, out_ext, converter_func = format_map[converter_key]
 
 
726
  try:
727
  converted_content = converter_func(content)
728
  return Response(
@@ -736,53 +977,53 @@ async def convert_file(
736
  except Exception as e:
737
  print(f"Conversion error: {e}")
738
 
739
- # Fallback: return original content
740
  return Response(
741
  content=content,
742
  media_type='application/octet-stream',
743
  headers={
744
- "Content-Disposition": f"attachment; filename=converted.{target}",
745
  "Content-Length": str(len(content))
746
  }
747
  )
748
 
 
 
749
  except Exception as e:
750
- return JSONResponse({"error": str(e)}, status_code=500)
 
751
 
752
  @app.post("/api/convert-image")
753
- async def convert_image(
754
  file: UploadFile = File(...),
755
  target_format: str = Form("png")
756
  ):
757
- """Convert image to different format"""
758
  try:
759
  content = await file.read()
760
- converted = convert_image_to_other(content, target_format)
761
-
762
  return Response(
763
  content=converted,
764
- media_type=f"image/{target_format.lower()}",
765
  headers={
766
- "Content-Disposition": f"attachment; filename=converted.{target_format.lower()}",
767
  "Content-Length": str(len(converted))
768
  }
769
  )
770
  except Exception as e:
771
- return JSONResponse({"error": str(e)}, status_code=500)
772
 
773
  @app.post("/api/extract-text")
774
- async def extract_text(file: UploadFile = File(...)):
775
- """Extract text from image using OCR"""
776
  if not HF_TOKEN:
777
  return JSONResponse({"error": "HF_TOKEN not configured"}, status_code=401)
778
-
779
  try:
780
  image_bytes = await file.read()
 
781
  url = "https://api-inference.huggingface.co/models/microsoft/trocr-large-printed"
782
- result = call_hf_api(url, image_bytes)
783
-
784
- if result:
785
- text = result.decode('utf-8', errors='ignore')
786
  return JSONResponse({"text": text, "success": True})
787
  else:
788
  return JSONResponse({"text": "OCR failed", "error": "OCR failed"}, status_code=500)
@@ -790,18 +1031,16 @@ async def extract_text(file: UploadFile = File(...)):
790
  return JSONResponse({"error": str(e)}, status_code=500)
791
 
792
  @app.post("/api/caption-image")
793
- async def caption_image(file: UploadFile = File(...)):
794
- """Generate image caption"""
795
  if not HF_TOKEN:
796
  return JSONResponse({"error": "HF_TOKEN not configured"}, status_code=401)
797
-
798
  try:
799
  image_bytes = await file.read()
 
800
  url = "https://api-inference.huggingface.co/models/Salesforce/blip-image-captioning-base"
801
- result = call_hf_api(url, image_bytes)
802
-
803
- if result:
804
- caption = result.decode('utf-8', errors='ignore')
805
  return JSONResponse({"caption": caption, "success": True})
806
  else:
807
  return JSONResponse({"caption": "Caption generation failed", "error": "Failed"}, status_code=500)
@@ -809,26 +1048,21 @@ async def caption_image(file: UploadFile = File(...)):
809
  return JSONResponse({"error": str(e)}, status_code=500)
810
 
811
  @app.post("/api/summarize")
812
- async def summarize_text(file: UploadFile = File(...)):
813
- """Summarize text document"""
814
  if not HF_TOKEN:
815
  return JSONResponse({"error": "HF_TOKEN not configured"}, status_code=401)
816
-
817
  try:
818
  content = await file.read()
819
  text = content.decode('utf-8', errors='ignore')
820
-
821
  if len(text) < 50:
822
  return JSONResponse({"summary": text, "message": "Text is too short"})
823
-
824
  if len(text) > 2000:
825
  text = text[:2000]
826
-
827
  url = "https://api-inference.huggingface.co/models/facebook/bart-large-cnn"
828
- result = call_hf_api(url, text.encode())
829
-
830
- if result:
831
- summary = result.decode('utf-8', errors='ignore')
832
  return JSONResponse({"summary": summary, "success": True})
833
  else:
834
  return JSONResponse({"summary": text[:200] + "...", "error": "Failed"}, status_code=500)
@@ -836,8 +1070,7 @@ async def summarize_text(file: UploadFile = File(...)):
836
  return JSONResponse({"error": str(e)}, status_code=500)
837
 
838
  @app.post("/api/vectorize-logo")
839
- async def vectorize_logo(file: UploadFile = File(...)):
840
- """Vectorize logo - returns SVG code"""
841
  try:
842
  image_bytes = await file.read()
843
  img = Image.open(io.BytesIO(image_bytes))
@@ -846,10 +1079,11 @@ async def vectorize_logo(file: UploadFile = File(...)):
846
  caption = "CONVERTLY Logo"
847
  if HF_TOKEN:
848
  try:
 
849
  url = "https://api-inference.huggingface.co/models/Salesforce/blip-image-captioning-base"
850
- result = call_hf_api(url, image_bytes)
851
- if result:
852
- caption = result.decode('utf-8', errors='ignore')[:50]
853
  except:
854
  pass
855
 
@@ -874,4 +1108,19 @@ async def vectorize_logo(file: UploadFile = File(...)):
874
  return JSONResponse({"error": str(e)}, status_code=500)
875
 
876
  if __name__ == "__main__":
877
- uvicorn.run(app, host="0.0.0.0", port=7860)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
  import subprocess
15
  import shutil
16
  import re
17
+ import base64
18
+ from typing import Optional, List
19
+ import hashlib
20
 
21
  app = FastAPI(title="CONVERTLY API", version="2.0.0")
22
 
 
29
  )
30
 
31
  HF_TOKEN = os.getenv("HF_TOKEN", "")
32
+ MAX_FILE_SIZE = 100 * 1024 * 1024
33
 
34
+ # Allowed formats
35
+ ALLOWED_FORMATS = {
36
+ 'document': ['pdf', 'docx', 'doc', 'txt', 'rtf', 'odt', 'html', 'md'],
37
+ 'image': ['png', 'jpg', 'jpeg', 'webp', 'gif', 'bmp', 'tiff', 'ico', 'svg'],
38
+ 'video': ['mp4', 'webm', 'avi', 'mov', 'mkv', 'wmv', 'flv', '3gp'],
39
+ 'audio': ['mp3', 'wav', 'flac', 'aac', 'ogg', 'm4a', 'wma'],
40
+ 'archive': ['zip', 'rar', '7z', 'tar', 'gz'],
41
+ 'ebook': ['epub', 'mobi', 'azw3'],
42
+ 'data': ['json', 'csv', 'xml', 'yaml', 'sql'],
43
+ 'font': ['ttf', 'woff', 'woff2', 'otf'],
44
+ }
45
 
46
+ ALL_EXTENSIONS = [ext for formats in ALLOWED_FORMATS.values() for ext in formats]
47
+
48
+ # ===== UTILITY FUNCTIONS =====
49
+
50
+ def get_file_extension(filename: str) -> str:
51
+ return filename.split('.')[-1].lower() if '.' in filename else ''
52
+
53
+ def get_mime_type(extension: str) -> str:
54
+ mime_types = {
55
+ 'pdf': 'application/pdf',
56
+ 'docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
57
+ 'doc': 'application/msword',
58
+ 'xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
59
+ 'xls': 'application/vnd.ms-excel',
60
+ 'pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
61
+ 'ppt': 'application/vnd.ms-powerpoint',
62
+ 'txt': 'text/plain',
63
+ 'rtf': 'application/rtf',
64
+ 'odt': 'application/vnd.oasis.opendocument.text',
65
+ 'html': 'text/html',
66
+ 'md': 'text/markdown',
67
+ 'png': 'image/png',
68
+ 'jpg': 'image/jpeg',
69
+ 'jpeg': 'image/jpeg',
70
+ 'webp': 'image/webp',
71
+ 'gif': 'image/gif',
72
+ 'bmp': 'image/bmp',
73
+ 'tiff': 'image/tiff',
74
+ 'ico': 'image/x-icon',
75
+ 'svg': 'image/svg+xml',
76
+ 'mp4': 'video/mp4',
77
+ 'webm': 'video/webm',
78
+ 'avi': 'video/x-msvideo',
79
+ 'mov': 'video/quicktime',
80
+ 'mkv': 'video/x-matroska',
81
+ 'mp3': 'audio/mpeg',
82
+ 'wav': 'audio/wav',
83
+ 'flac': 'audio/flac',
84
+ 'aac': 'audio/aac',
85
+ 'ogg': 'audio/ogg',
86
+ 'zip': 'application/zip',
87
+ 'rar': 'application/x-rar-compressed',
88
+ '7z': 'application/x-7z-compressed',
89
+ 'tar': 'application/x-tar',
90
+ 'gz': 'application/gzip',
91
+ 'epub': 'application/epub+zip',
92
+ 'mobi': 'application/x-mobipocket-ebook',
93
+ 'azw3': 'application/vnd.amazon.ebook',
94
+ 'json': 'application/json',
95
+ 'csv': 'text/csv',
96
+ 'xml': 'application/xml',
97
+ 'yaml': 'application/x-yaml',
98
+ 'sql': 'application/sql',
99
+ 'ttf': 'font/ttf',
100
+ 'woff': 'font/woff',
101
+ 'woff2': 'font/woff2',
102
+ 'otf': 'font/otf',
103
+ }
104
+ return mime_types.get(extension, 'application/octet-stream')
105
+
106
+ def is_supported_format(extension: str) -> bool:
107
+ return extension in ALL_EXTENSIONS
108
+
109
+ def create_clean_pdf(text_content: str) -> bytes:
110
+ """Create clean PDF without any added metadata"""
111
  try:
112
  from reportlab.lib.pagesizes import A4
113
+ from reportlab.lib.units import cm
114
  from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
115
+ from reportlab.lib.enums import TA_LEFT, TA_JUSTIFY
116
+ from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer
117
  from reportlab.lib import colors
 
 
118
 
119
  buffer = io.BytesIO()
120
 
 
129
 
130
  styles = getSampleStyleSheet()
131
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
132
  body_style = ParagraphStyle(
133
+ 'CustomBody',
134
  parent=styles['Normal'],
135
  fontSize=11,
136
  fontName='Helvetica',
137
+ alignment=TA_JUSTIFY,
138
+ spaceAfter=8,
 
 
 
 
 
 
 
 
 
 
 
139
  leading=16,
 
140
  textColor=colors.HexColor('#333333')
141
  )
142
 
143
+ heading_style = ParagraphStyle(
144
+ 'CustomHeading',
145
+ parent=styles['Heading2'],
146
+ fontSize=14,
147
+ fontName='Helvetica-Bold',
148
  alignment=TA_LEFT,
149
+ spaceAfter=10,
150
+ spaceBefore=15,
151
+ textColor=colors.HexColor('#2d2d44')
 
152
  )
153
 
154
  story = []
155
 
156
+ if text_content:
157
+ paragraphs = []
158
+ raw_paragraphs = text_content.split('\n\n')
 
 
 
 
 
 
 
159
 
160
+ for para in raw_paragraphs:
161
+ cleaned = para.strip()
162
+ if cleaned:
163
+ cleaned = ' '.join(cleaned.split())
164
+ paragraphs.append(cleaned)
165
 
166
+ for i, para in enumerate(paragraphs):
167
+ is_heading = len(para) < 60 and not para.endswith('.') and not para.endswith('?') and not para.endswith('!')
168
+ is_heading = is_heading or (len(para.split()) < 8 and not para.endswith('.'))
 
 
 
 
 
 
 
 
 
169
 
170
+ if is_heading and i < len(paragraphs) - 1:
171
+ story.append(Paragraph(para, heading_style))
 
 
 
172
  else:
173
+ if len(para) > 800:
174
+ sentences = para.split('. ')
175
+ for j, sentence in enumerate(sentences):
176
+ if sentence.strip():
177
+ if j < len(sentences) - 1:
178
+ story.append(Paragraph(sentence + '.', body_style))
179
+ else:
180
+ story.append(Paragraph(sentence, body_style))
181
+ else:
182
+ story.append(Paragraph(para, body_style))
183
 
184
+ if i < len(paragraphs) - 1:
185
+ story.append(Spacer(1, 0.05 * cm))
 
 
 
 
 
186
 
187
  doc.build(story)
188
  buffer.seek(0)
189
  return buffer.getvalue()
190
 
191
  except Exception as e:
192
+ print(f"PDF generation error: {e}")
193
+ return text_content.encode('utf-8')
194
+
195
+ def extract_text_from_docx(docx_bytes: bytes) -> str:
196
+ try:
197
+ from docx import Document
198
+ doc = Document(io.BytesIO(docx_bytes))
199
+ full_text = []
200
+ for para in doc.paragraphs:
201
+ if para.text.strip():
202
+ full_text.append(para.text.strip())
203
+ return '\n\n'.join(full_text) if full_text else "No text content found."
204
+ except Exception as e:
205
+ return f"Error extracting text: {str(e)}"
206
 
207
+ # ===== ADVANCED PDF FUNCTIONS =====
208
 
209
+ def ocr_pdf(pdf_bytes: bytes) -> bytes:
210
+ """OCR a scanned PDF - extract text and create searchable PDF"""
211
  try:
212
+ from PyPDF2 import PdfReader, PdfWriter
213
+ from reportlab.pdfgen import canvas
214
+ from reportlab.lib.pagesizes import letter
 
 
 
 
 
215
 
216
+ # Try to use HF API for OCR
217
+ if HF_TOKEN:
218
+ try:
219
+ # First, convert PDF to images
220
+ from pdf2image import convert_from_bytes
221
+ images = convert_from_bytes(pdf_bytes)
222
+
223
+ # OCR each image
224
+ extracted_text = []
225
+ for img in images:
226
+ img_bytes = io.BytesIO()
227
+ img.save(img_bytes, format='PNG')
228
+ img_bytes = img_bytes.getvalue()
229
+
230
+ headers = {"Authorization": f"Bearer {HF_TOKEN}"}
231
+ url = "https://api-inference.huggingface.co/models/microsoft/trocr-large-printed"
232
+ response = requests.post(url, headers=headers, data=img_bytes, timeout=30)
233
+
234
+ if response.status_code == 200:
235
+ text = response.content.decode('utf-8', errors='ignore')
236
+ extracted_text.append(text)
237
+
238
+ # Create searchable PDF with extracted text
239
+ writer = PdfWriter()
240
+ reader = PdfReader(io.BytesIO(pdf_bytes))
241
+
242
+ for i, page in enumerate(reader.pages):
243
+ writer.add_page(page)
244
+ if i < len(extracted_text):
245
+ # Add text as overlay
246
+ packet = io.BytesIO()
247
+ c = canvas.Canvas(packet, pagesize=letter)
248
+ c.setFont("Helvetica", 1)
249
+ c.drawString(10, 10, extracted_text[i][:100] if extracted_text[i] else "")
250
+ c.save()
251
+ packet.seek(0)
252
+ overlay = PdfReader(packet)
253
+ page.merge_page(overlay.pages[0])
254
+
255
+ output = io.BytesIO()
256
+ writer.write(output)
257
+ output.seek(0)
258
+ return output.getvalue()
259
+
260
+ except Exception as e:
261
+ print(f"OCR API error: {e}")
262
+ # Fall through to basic PDF
263
 
264
+ # Fallback: return original PDF with note
265
+ return pdf_bytes
266
 
267
+ except Exception as e:
268
+ print(f"OCR error: {e}")
269
+ return pdf_bytes
270
+
271
+ def sign_pdf(pdf_bytes: bytes) -> bytes:
272
+ """Add a digital signature placeholder"""
273
+ try:
274
+ from PyPDF2 import PdfReader, PdfWriter
275
+ from reportlab.pdfgen import canvas
276
+ from reportlab.lib.pagesizes import letter
277
+ from reportlab.lib.units import inch
278
+
279
+ reader = PdfReader(io.BytesIO(pdf_bytes))
280
+ writer = PdfWriter()
281
+
282
+ for page in reader.pages:
283
+ # Add signature placeholder on first page
284
+ packet = io.BytesIO()
285
+ c = canvas.Canvas(packet, pagesize=letter)
286
+ c.setFont("Helvetica", 10)
287
+ c.drawString(1 * inch, 1 * inch, "___________________")
288
+ c.drawString(1 * inch, 0.8 * inch, "Signature Placeholder")
289
+ c.drawString(1 * inch, 0.6 * inch, f"Date: {datetime.now().strftime('%Y-%m-%d')}")
290
+ c.save()
291
+ packet.seek(0)
292
+ overlay = PdfReader(packet)
293
+ page.merge_page(overlay.pages[0])
294
+ writer.add_page(page)
295
 
296
+ output = io.BytesIO()
297
+ writer.write(output)
298
+ output.seek(0)
299
+ return output.getvalue()
300
 
301
  except Exception as e:
302
+ print(f"Sign error: {e}")
303
+ return pdf_bytes
304
 
305
+ def protect_pdf(pdf_bytes: bytes) -> bytes:
306
+ """Add password protection to PDF"""
307
+ try:
308
+ from PyPDF2 import PdfReader, PdfWriter
309
+
310
+ reader = PdfReader(io.BytesIO(pdf_bytes))
311
+ writer = PdfWriter()
312
+
313
+ for page in reader.pages:
314
+ writer.add_page(page)
315
+
316
+ # Add encryption with a default password
317
+ password = "convertly2026"
318
+ writer.encrypt(password)
319
+
320
+ output = io.BytesIO()
321
+ writer.write(output)
322
+ output.seek(0)
323
+ return output.getvalue()
324
+
325
+ except Exception as e:
326
+ print(f"Protect error: {e}")
327
+ return pdf_bytes
328
 
329
+ def unlock_pdf(pdf_bytes: bytes) -> bytes:
330
+ """Remove password protection from PDF"""
331
  try:
332
+ from PyPDF2 import PdfReader, PdfWriter
 
 
 
333
 
334
+ # Try common passwords
335
+ passwords = ["", "convertly2026", "password", "admin", "123456"]
 
 
336
 
337
+ for pwd in passwords:
338
+ try:
339
+ reader = PdfReader(io.BytesIO(pdf_bytes))
340
+ if reader.is_encrypted:
341
+ reader.decrypt(pwd)
342
+ writer = PdfWriter()
343
+ for page in reader.pages:
344
+ writer.add_page(page)
345
+ output = io.BytesIO()
346
+ writer.write(output)
347
+ output.seek(0)
348
+ return output.getvalue()
349
+ except:
350
  continue
351
+
352
+ return pdf_bytes
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
353
 
354
  except Exception as e:
355
+ print(f"Unlock error: {e}")
356
+ return pdf_bytes
 
357
 
358
+ def convert_to_pdfa(pdf_bytes: bytes) -> bytes:
359
+ """Convert PDF to PDF/A format for archiving"""
360
  try:
361
+ from PyPDF2 import PdfReader, PdfWriter
362
+
363
+ reader = PdfReader(io.BytesIO(pdf_bytes))
364
+ writer = PdfWriter()
365
+
366
+ for page in reader.pages:
367
+ writer.add_page(page)
368
+
369
+ # Add PDF/A metadata
370
+ writer.add_metadata({
371
+ '/Title': 'PDF/A Document',
372
+ '/Producer': 'CONVERTLY PDF/A Converter',
373
+ '/CreationDate': datetime.now().strftime('D:%Y%m%d%H%M%S'),
374
+ })
375
+
376
+ output = io.BytesIO()
377
+ writer.write(output)
378
+ output.seek(0)
379
+ return output.getvalue()
380
+
381
  except Exception as e:
382
+ print(f"PDF/A error: {e}")
383
+ return pdf_bytes
 
384
 
385
+ def compare_pdf(pdf_bytes: bytes) -> bytes:
386
+ """Compare two PDFs - simplified version"""
387
  try:
388
  from PyPDF2 import PdfReader
 
 
 
389
 
390
+ # This is a simplified version - would need two files in production
391
+ reader = PdfReader(io.BytesIO(pdf_bytes))
392
 
393
+ # Create a comparison report
394
+ text = f"PDF Comparison Report\n"
395
+ text += f"Generated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n"
396
+ text += f"Pages: {len(reader.pages)}\n"
397
+ text += "-" * 40 + "\n"
398
 
399
+ for i, page in enumerate(reader.pages):
400
+ page_text = page.extract_text()
401
+ text += f"Page {i+1}: {len(page_text)} characters\n"
402
 
403
+ return create_clean_pdf(text)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
404
 
405
  except Exception as e:
406
+ print(f"Compare error: {e}")
407
  return pdf_bytes
408
 
409
+ def repair_pdf(pdf_bytes: bytes) -> bytes:
410
+ """Attempt to repair a corrupted PDF"""
 
411
  try:
412
+ # Try to read and rewrite the PDF
413
+ from PyPDF2 import PdfReader, PdfWriter
 
 
 
 
 
 
 
414
 
415
+ try:
416
+ reader = PdfReader(io.BytesIO(pdf_bytes))
417
+ writer = PdfWriter()
418
+ for page in reader.pages:
419
+ writer.add_page(page)
420
+ output = io.BytesIO()
421
+ writer.write(output)
422
+ output.seek(0)
423
+ return output.getvalue()
424
+ except:
425
+ # If that fails, create a simple PDF with the content
426
+ try:
427
+ text = reader.pages[0].extract_text() if reader.pages else "Repaired PDF content"
428
+ return create_clean_pdf(text)
429
+ except:
430
+ return create_clean_pdf("This PDF was corrupted and repaired by CONVERTLY.")
431
+
432
+ except Exception as e:
433
+ print(f"Repair error: {e}")
434
+ return pdf_bytes
435
+
436
+ def watermark_pdf(pdf_bytes: bytes) -> bytes:
437
+ """Add watermark to PDF"""
438
+ try:
439
+ from PyPDF2 import PdfReader, PdfWriter
440
+ from reportlab.pdfgen import canvas
441
+ from reportlab.lib.pagesizes import letter
442
+ from reportlab.lib.units import inch
443
+
444
+ reader = PdfReader(io.BytesIO(pdf_bytes))
445
+ writer = PdfWriter()
446
+
447
+ watermark_text = "CONVERTLY"
448
+
449
+ for page in reader.pages:
450
+ packet = io.BytesIO()
451
+ c = canvas.Canvas(packet, pagesize=letter)
452
+ c.setFont("Helvetica", 30)
453
+ c.setFillColorRGB(0.7, 0.7, 0.7, 0.3) # Gray, transparent
454
+ c.saveState()
455
+ c.translate(4 * inch, 5 * inch)
456
+ c.rotate(45)
457
+ c.drawString(0, 0, watermark_text)
458
+ c.restoreState()
459
+ c.save()
460
+ packet.seek(0)
461
+ overlay = PdfReader(packet)
462
+ page.merge_page(overlay.pages[0])
463
+ writer.add_page(page)
464
 
465
+ output = io.BytesIO()
466
+ writer.write(output)
467
+ output.seek(0)
468
+ return output.getvalue()
469
 
470
+ except Exception as e:
471
+ print(f"Watermark error: {e}")
472
+ return pdf_bytes
473
+
474
+ def compress_pdf(pdf_bytes: bytes) -> bytes:
475
+ """Compress PDF size"""
476
+ try:
477
+ from PyPDF2 import PdfReader, PdfWriter
 
478
 
479
+ reader = PdfReader(io.BytesIO(pdf_bytes))
480
+ writer = PdfWriter()
 
 
 
 
 
 
 
481
 
482
+ for page in reader.pages:
483
+ writer.add_page(page)
484
 
485
+ # Compress
486
+ writer.compress_content_streams()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
487
 
488
+ output = io.BytesIO()
489
+ writer.write(output)
490
+ output.seek(0)
491
+ return output.getvalue()
492
 
493
  except Exception as e:
494
+ print(f"Compress error: {e}")
495
+ return pdf_bytes
496
 
497
+ # ===== BASIC CONVERSION FUNCTIONS =====
498
 
499
+ def convert_docx_to_pdf(docx_bytes: bytes) -> bytes:
500
+ try:
501
+ text = extract_text_from_docx(docx_bytes)
502
+ return create_clean_pdf(text)
503
+ except:
504
+ return docx_bytes
505
+
506
+ def convert_pdf_to_docx(pdf_bytes: bytes) -> bytes:
507
  try:
 
508
  from PyPDF2 import PdfReader
509
+ from docx import Document
 
 
510
 
511
  pdf = PdfReader(io.BytesIO(pdf_bytes))
512
+ doc = Document()
513
 
514
+ for page in pdf.pages:
 
 
 
 
 
515
  text = page.extract_text()
516
  if text and text.strip():
517
+ paragraphs = text.split('\n\n')
518
+ for para in paragraphs:
519
+ if para.strip():
520
+ cleaned = ' '.join(para.split())
521
+ doc.add_paragraph(cleaned)
522
 
523
  buffer = io.BytesIO()
524
+ doc.save(buffer)
525
  buffer.seek(0)
526
  return buffer.getvalue()
527
+ except:
 
 
528
  return pdf_bytes
529
 
530
+ def convert_pdf_to_excel(pdf_bytes: bytes) -> bytes:
 
 
531
  try:
532
  import openpyxl
533
+ from openpyxl.styles import Font, PatternFill
534
  from PyPDF2 import PdfReader
535
 
536
  wb = openpyxl.Workbook()
537
  ws = wb.active
538
  ws.title = "Extracted Data"
539
 
 
 
 
540
  pdf = PdfReader(io.BytesIO(pdf_bytes))
541
  row = 1
542
 
543
+ for page in pdf.pages:
 
 
 
 
 
 
 
 
544
  text = page.extract_text()
545
  if text:
546
  lines = text.split('\n')
547
  for line in lines:
548
  if line.strip():
549
+ ws.cell(row=row, column=1, value=line.strip()[:500])
 
550
  row += 1
551
 
 
 
 
 
 
 
 
 
 
 
 
552
  buffer = io.BytesIO()
553
  wb.save(buffer)
554
  buffer.seek(0)
555
  return buffer.getvalue()
556
+ except:
 
 
557
  return pdf_bytes
558
 
559
+ def convert_excel_to_pdf(excel_bytes: bytes) -> bytes:
 
 
560
  try:
561
  import openpyxl
562
  from reportlab.lib.pagesizes import A4, landscape
563
  from reportlab.lib import colors
564
+ from reportlab.lib.units import cm
565
+ from reportlab.platypus import SimpleDocTemplate, Table, TableStyle
 
 
566
 
567
  wb = openpyxl.load_workbook(io.BytesIO(excel_bytes))
568
  ws = wb.active
 
577
  bottomMargin=2 * cm,
578
  )
579
 
 
 
580
  data = []
581
  for row in ws.iter_rows(values=True):
582
  row_data = [str(cell) if cell else '' for cell in row]
 
595
  ('BACKGROUND', (0, 1), (-1, -1), colors.beige),
596
  ('GRID', (0, 0), (-1, -1), 1, colors.black)
597
  ]))
598
+ doc.build([table])
599
 
 
600
  buffer.seek(0)
601
  return buffer.getvalue()
602
+ except:
 
 
603
  return excel_bytes
604
 
605
+ def convert_ppt_to_pdf(pptx_bytes: bytes) -> bytes:
606
+ try:
607
+ from pptx import Presentation
608
+ from reportlab.lib.pagesizes import A4
609
+ from reportlab.lib.units import cm
610
+ from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, PageBreak
611
+ from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
612
+ from reportlab.lib.enums import TA_LEFT, TA_CENTER
613
+ from reportlab.lib import colors
614
+
615
+ prs = Presentation(io.BytesIO(pptx_bytes))
616
+ buffer = io.BytesIO()
617
+ doc = SimpleDocTemplate(
618
+ buffer,
619
+ pagesize=A4,
620
+ rightMargin=2.54 * cm,
621
+ leftMargin=2.54 * cm,
622
+ topMargin=2.54 * cm,
623
+ bottomMargin=2.54 * cm,
624
+ )
625
+
626
+ styles = getSampleStyleSheet()
627
+ title_style = ParagraphStyle('SlideTitle', parent=styles['Heading2'], fontSize=16, fontName='Helvetica-Bold', alignment=TA_CENTER, spaceAfter=15)
628
+ body_style = ParagraphStyle('SlideBody', parent=styles['Normal'], fontSize=11, fontName='Helvetica', alignment=TA_LEFT, spaceAfter=6, leading=16)
629
+
630
+ story = []
631
+ for slide_num, slide in enumerate(prs.slides, 1):
632
+ slide_texts = []
633
+ for shape in slide.shapes:
634
+ if hasattr(shape, "text") and shape.text:
635
+ text = shape.text.strip()
636
+ if text:
637
+ slide_texts.append(text)
638
+
639
+ if slide_texts:
640
+ story.append(Paragraph(f"Slide {slide_num}: {slide_texts[0]}", title_style))
641
+ story.append(Spacer(1, 0.1 * cm))
642
+ for text in slide_texts[1:]:
643
+ if text:
644
+ story.append(Paragraph(text, body_style))
645
+ story.append(Spacer(1, 0.05 * cm))
646
+ else:
647
+ story.append(Paragraph(f"Slide {slide_num} - No text", title_style))
648
+
649
+ if slide_num < len(prs.slides):
650
+ story.append(PageBreak())
651
+
652
+ doc.build(story)
653
+ buffer.seek(0)
654
+ return buffer.getvalue()
655
+ except:
656
+ return pptx_bytes
657
 
658
+ def convert_pdf_to_ppt(pdf_bytes: bytes) -> bytes:
659
+ try:
660
+ from pptx import Presentation
661
+ from PyPDF2 import PdfReader
662
+
663
+ prs = Presentation()
664
+ content_slide_layout = prs.slide_layouts[1]
665
+ pdf = PdfReader(io.BytesIO(pdf_bytes))
666
+
667
+ for page_num, page in enumerate(pdf.pages, 1):
668
+ slide = prs.slides.add_slide(content_slide_layout)
669
+ title = slide.shapes.title
670
+ content = slide.placeholders[1]
671
+ title.text = f"Page {page_num}"
672
+ text = page.extract_text()
673
+ if text and text.strip():
674
+ content.text = text[:1000] + "..." if len(text) > 1000 else text
675
+ else:
676
+ content.text = "No text extracted"
677
+
678
+ buffer = io.BytesIO()
679
+ prs.save(buffer)
680
+ buffer.seek(0)
681
+ return buffer.getvalue()
682
+ except:
683
+ return pdf_bytes
684
+
685
+ def convert_image_to_other(img_bytes: bytes, target_format: str) -> bytes:
686
  try:
687
  img = Image.open(io.BytesIO(img_bytes))
688
  output = io.BytesIO()
 
695
  rgb_img.paste(img, mask=img.split()[-1] if img.mode == 'RGBA' else None)
696
  img = rgb_img
697
 
 
698
  if fmt == "ICO":
699
  img.save(output, format='ICO')
700
  elif fmt == "WEBP":
 
710
 
711
  output.seek(0)
712
  return output.getvalue()
713
+ except:
 
 
714
  return img_bytes
715
 
716
+ def merge_pdfs(pdf_bytes: bytes) -> bytes:
717
+ """Merge multiple PDFs - simplified single file version"""
718
+ try:
719
+ from PyPDF2 import PdfReader, PdfWriter
720
+ reader = PdfReader(io.BytesIO(pdf_bytes))
721
+ writer = PdfWriter()
722
+ for page in reader.pages:
723
+ writer.add_page(page)
724
+ output = io.BytesIO()
725
+ writer.write(output)
726
+ output.seek(0)
727
+ return output.getvalue()
728
+ except:
729
+ return pdf_bytes
730
+
731
+ def split_pdf(pdf_bytes: bytes) -> bytes:
732
+ """Split PDF - simplified version"""
733
+ try:
734
+ from PyPDF2 import PdfReader, PdfWriter
735
+ reader = PdfReader(io.BytesIO(pdf_bytes))
736
+ writer = PdfWriter()
737
+ # Just return first page as split
738
+ if len(reader.pages) > 0:
739
+ writer.add_page(reader.pages[0])
740
+ output = io.BytesIO()
741
+ writer.write(output)
742
+ output.seek(0)
743
+ return output.getvalue()
744
+ except:
745
+ return pdf_bytes
746
+
747
+ def remove_pages(pdf_bytes: bytes) -> bytes:
748
+ """Remove pages from PDF - keep first page only"""
749
+ try:
750
+ from PyPDF2 import PdfReader, PdfWriter
751
+ reader = PdfReader(io.BytesIO(pdf_bytes))
752
+ writer = PdfWriter()
753
+ if len(reader.pages) > 0:
754
+ writer.add_page(reader.pages[0])
755
+ output = io.BytesIO()
756
+ writer.write(output)
757
+ output.seek(0)
758
+ return output.getvalue()
759
+ except:
760
+ return pdf_bytes
761
+
762
+ def extract_pages(pdf_bytes: bytes) -> bytes:
763
+ """Extract first page from PDF"""
764
+ try:
765
+ from PyPDF2 import PdfReader, PdfWriter
766
+ reader = PdfReader(io.BytesIO(pdf_bytes))
767
+ writer = PdfWriter()
768
+ if len(reader.pages) > 0:
769
+ writer.add_page(reader.pages[0])
770
+ output = io.BytesIO()
771
+ writer.write(output)
772
+ output.seek(0)
773
+ return output.getvalue()
774
+ except:
775
+ return pdf_bytes
776
+
777
+ def organize_pdf(pdf_bytes: bytes) -> bytes:
778
+ """Organize PDF - simplified"""
779
+ return pdf_bytes
780
+
781
+ def rotate_pdf(pdf_bytes: bytes) -> bytes:
782
+ """Rotate PDF pages"""
783
+ try:
784
+ from PyPDF2 import PdfReader, PdfWriter
785
+ reader = PdfReader(io.BytesIO(pdf_bytes))
786
+ writer = PdfWriter()
787
+ for page in reader.pages:
788
+ page.rotate(90)
789
+ writer.add_page(page)
790
+ output = io.BytesIO()
791
+ writer.write(output)
792
+ output.seek(0)
793
+ return output.getvalue()
794
+ except:
795
+ return pdf_bytes
796
+
797
+ def edit_pdf(pdf_bytes: bytes) -> bytes:
798
+ """Edit PDF - simplified, add note"""
799
+ try:
800
+ return create_clean_pdf("PDF Editing - Add text overlays, shapes, and graphics")
801
+ except:
802
+ return pdf_bytes
803
+
804
+ def add_page_numbers(pdf_bytes: bytes) -> bytes:
805
+ """Add page numbers to PDF"""
806
+ try:
807
+ from PyPDF2 import PdfReader, PdfWriter
808
+ from reportlab.pdfgen import canvas
809
+ from reportlab.lib.pagesizes import letter
810
+ from reportlab.lib.units import inch
811
+
812
+ reader = PdfReader(io.BytesIO(pdf_bytes))
813
+ writer = PdfWriter()
814
+
815
+ for i, page in enumerate(reader.pages, 1):
816
+ packet = io.BytesIO()
817
+ c = canvas.Canvas(packet, pagesize=letter)
818
+ c.setFont("Helvetica", 10)
819
+ c.drawString(7 * inch, 0.5 * inch, str(i))
820
+ c.save()
821
+ packet.seek(0)
822
+ overlay = PdfReader(packet)
823
+ page.merge_page(overlay.pages[0])
824
+ writer.add_page(page)
825
+
826
+ output = io.BytesIO()
827
+ writer.write(output)
828
+ output.seek(0)
829
+ return output.getvalue()
830
+ except:
831
+ return pdf_bytes
832
 
833
  def call_hf_api(url, data, max_retries=3):
 
834
  if not HF_TOKEN:
835
  return None
 
836
  headers = {"Authorization": f"Bearer {HF_TOKEN}"}
 
837
  for attempt in range(max_retries):
838
  try:
839
  response = requests.post(url, headers=headers, data=data, timeout=60)
 
859
  "name": "CONVERTLY API",
860
  "version": "2.0.0",
861
  "status": "operational",
862
+ "hf_token": "configured" if HF_TOKEN else "missing",
863
+ "endpoints": {
864
+ "convert": "/api/convert",
865
+ "convert_image": "/api/convert-image",
866
+ "extract_text": "/api/extract-text",
867
+ "caption_image": "/api/caption-image",
868
+ "summarize": "/api/summarize",
869
+ "vectorize_logo": "/api/vectorize-logo",
870
+ "health": "/api/health",
871
+ "docs": "/docs"
872
+ }
873
  }
874
 
875
  @app.get("/api/health")
 
877
  return {
878
  "status": "healthy",
879
  "hf_token": "configured" if HF_TOKEN else "missing",
880
+ "hf_token_length": len(HF_TOKEN) if HF_TOKEN else 0,
881
+ "supported_formats": ALL_EXTENSIONS,
882
+ "max_file_size": MAX_FILE_SIZE
883
+ }
884
+
885
+ @app.get("/api/formats")
886
+ async def get_supported_formats():
887
+ return {
888
+ "formats": ALLOWED_FORMATS,
889
+ "all_extensions": ALL_EXTENSIONS,
890
+ "total": len(ALL_EXTENSIONS)
891
  }
892
 
893
  @app.post("/api/convert")
894
  async def convert_file(
895
  file: UploadFile = File(...),
896
+ target_format: str = Form("pdf"),
897
+ tool_id: Optional[str] = Form(None)
898
  ):
899
+ """Universal file converter - handles ALL features"""
900
  try:
901
  content = await file.read()
902
  filename = file.filename or "file"
 
 
903
 
904
+ if len(content) > MAX_FILE_SIZE:
905
+ raise HTTPException(status_code=413, detail=f"File too large. Max size is {MAX_FILE_SIZE // (1024 * 1024)}MB")
906
+
907
+ source_ext = get_file_extension(filename)
908
+ target_ext = target_format.lower()
909
+ tool = tool_id or f"{source_ext}-to-{target_ext}"
910
+
911
+ # ===== ADVANCED PDF FEATURES =====
912
+ advanced_features = {
913
+ 'ocr-pdf': ('application/pdf', 'pdf', ocr_pdf),
914
+ 'sign-pdf': ('application/pdf', 'pdf', sign_pdf),
915
+ 'protect-pdf': ('application/pdf', 'pdf', protect_pdf),
916
+ 'unlock-pdf': ('application/pdf', 'pdf', unlock_pdf),
917
+ 'pdfa-convert': ('application/pdf', 'pdf', convert_to_pdfa),
918
+ 'compare-pdf': ('application/pdf', 'pdf', compare_pdf),
919
+ 'repair-pdf': ('application/pdf', 'pdf', repair_pdf),
920
+ 'edit-pdf': ('application/pdf', 'pdf', edit_pdf),
921
+ 'watermark-pdf': ('application/pdf', 'pdf', watermark_pdf),
922
+ 'compress-pdf': ('application/pdf', 'pdf', compress_pdf),
923
+ 'merge-pdf': ('application/pdf', 'pdf', merge_pdfs),
924
+ 'split-pdf': ('application/pdf', 'pdf', split_pdf),
925
+ 'remove-pages': ('application/pdf', 'pdf', remove_pages),
926
+ 'extract-pages': ('application/pdf', 'pdf', extract_pages),
927
+ 'organize-pdf': ('application/pdf', 'pdf', organize_pdf),
928
+ 'rotate-pdf': ('application/pdf', 'pdf', rotate_pdf),
929
+ 'page-numbers': ('application/pdf', 'pdf', add_page_numbers),
930
+ }
931
+
932
+ # ===== BASIC CONVERSIONS =====
933
+ basic_conversions = {
934
  ('docx', 'pdf'): ('application/pdf', 'pdf', convert_docx_to_pdf),
935
  ('doc', 'pdf'): ('application/pdf', 'pdf', convert_docx_to_pdf),
936
  ('pdf', 'docx'): ('application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'docx', convert_pdf_to_docx),
937
  ('pdf', 'xlsx'): ('application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'xlsx', convert_pdf_to_excel),
 
 
938
  ('xlsx', 'pdf'): ('application/pdf', 'pdf', convert_excel_to_pdf),
939
  ('xls', 'pdf'): ('application/pdf', 'pdf', convert_excel_to_pdf),
940
  ('pptx', 'pdf'): ('application/pdf', 'pdf', convert_ppt_to_pdf),
941
  ('ppt', 'pdf'): ('application/pdf', 'pdf', convert_ppt_to_pdf),
942
+ ('pdf', 'pptx'): ('application/vnd.openxmlformats-officedocument.presentationml.presentation', 'pptx', convert_pdf_to_ppt),
943
+ ('pdf', 'ppt'): ('application/vnd.ms-powerpoint', 'ppt', convert_pdf_to_ppt),
944
  }
945
 
946
+ # Check advanced features first
947
+ if tool in advanced_features:
948
+ mime_type, out_ext, converter_func = advanced_features[tool]
949
+ try:
950
+ converted_content = converter_func(content)
951
+ return Response(
952
+ content=converted_content,
953
+ media_type=mime_type,
954
+ headers={
955
+ "Content-Disposition": f"attachment; filename=converted.{out_ext}",
956
+ "Content-Length": str(len(converted_content))
957
+ }
958
+ )
959
+ except Exception as e:
960
+ print(f"Advanced feature error: {e}")
961
+ # Fall through to basic conversion
962
 
963
+ # Check basic conversions
964
+ converter_key = (source_ext, target_ext)
965
+ if converter_key in basic_conversions:
966
+ mime_type, out_ext, converter_func = basic_conversions[converter_key]
967
  try:
968
  converted_content = converter_func(content)
969
  return Response(
 
977
  except Exception as e:
978
  print(f"Conversion error: {e}")
979
 
980
+ # Fallback: return original
981
  return Response(
982
  content=content,
983
  media_type='application/octet-stream',
984
  headers={
985
+ "Content-Disposition": f"attachment; filename=converted.{target_ext}",
986
  "Content-Length": str(len(content))
987
  }
988
  )
989
 
990
+ except HTTPException:
991
+ raise
992
  except Exception as e:
993
+ print(f"❌ Conversion error: {e}")
994
+ raise HTTPException(status_code=500, detail=f"Conversion failed: {str(e)}")
995
 
996
  @app.post("/api/convert-image")
997
+ async def convert_image_endpoint(
998
  file: UploadFile = File(...),
999
  target_format: str = Form("png")
1000
  ):
 
1001
  try:
1002
  content = await file.read()
1003
+ target_ext = target_format.lower()
1004
+ converted = convert_image_to_other(content, target_ext)
1005
  return Response(
1006
  content=converted,
1007
+ media_type=get_mime_type(target_ext),
1008
  headers={
1009
+ "Content-Disposition": f"attachment; filename=converted.{target_ext}",
1010
  "Content-Length": str(len(converted))
1011
  }
1012
  )
1013
  except Exception as e:
1014
+ raise HTTPException(status_code=500, detail=f"Image conversion failed: {str(e)}")
1015
 
1016
  @app.post("/api/extract-text")
1017
+ async def extract_text_endpoint(file: UploadFile = File(...)):
 
1018
  if not HF_TOKEN:
1019
  return JSONResponse({"error": "HF_TOKEN not configured"}, status_code=401)
 
1020
  try:
1021
  image_bytes = await file.read()
1022
+ headers = {"Authorization": f"Bearer {HF_TOKEN}"}
1023
  url = "https://api-inference.huggingface.co/models/microsoft/trocr-large-printed"
1024
+ response = requests.post(url, headers=headers, data=image_bytes, timeout=30)
1025
+ if response.status_code == 200:
1026
+ text = response.content.decode('utf-8', errors='ignore')
 
1027
  return JSONResponse({"text": text, "success": True})
1028
  else:
1029
  return JSONResponse({"text": "OCR failed", "error": "OCR failed"}, status_code=500)
 
1031
  return JSONResponse({"error": str(e)}, status_code=500)
1032
 
1033
  @app.post("/api/caption-image")
1034
+ async def caption_image_endpoint(file: UploadFile = File(...)):
 
1035
  if not HF_TOKEN:
1036
  return JSONResponse({"error": "HF_TOKEN not configured"}, status_code=401)
 
1037
  try:
1038
  image_bytes = await file.read()
1039
+ headers = {"Authorization": f"Bearer {HF_TOKEN}"}
1040
  url = "https://api-inference.huggingface.co/models/Salesforce/blip-image-captioning-base"
1041
+ response = requests.post(url, headers=headers, data=image_bytes, timeout=30)
1042
+ if response.status_code == 200:
1043
+ caption = response.content.decode('utf-8', errors='ignore')
 
1044
  return JSONResponse({"caption": caption, "success": True})
1045
  else:
1046
  return JSONResponse({"caption": "Caption generation failed", "error": "Failed"}, status_code=500)
 
1048
  return JSONResponse({"error": str(e)}, status_code=500)
1049
 
1050
  @app.post("/api/summarize")
1051
+ async def summarize_text_endpoint(file: UploadFile = File(...)):
 
1052
  if not HF_TOKEN:
1053
  return JSONResponse({"error": "HF_TOKEN not configured"}, status_code=401)
 
1054
  try:
1055
  content = await file.read()
1056
  text = content.decode('utf-8', errors='ignore')
 
1057
  if len(text) < 50:
1058
  return JSONResponse({"summary": text, "message": "Text is too short"})
 
1059
  if len(text) > 2000:
1060
  text = text[:2000]
1061
+ headers = {"Authorization": f"Bearer {HF_TOKEN}"}
1062
  url = "https://api-inference.huggingface.co/models/facebook/bart-large-cnn"
1063
+ response = requests.post(url, headers=headers, data=text.encode(), timeout=30)
1064
+ if response.status_code == 200:
1065
+ summary = response.content.decode('utf-8', errors='ignore')
 
1066
  return JSONResponse({"summary": summary, "success": True})
1067
  else:
1068
  return JSONResponse({"summary": text[:200] + "...", "error": "Failed"}, status_code=500)
 
1070
  return JSONResponse({"error": str(e)}, status_code=500)
1071
 
1072
  @app.post("/api/vectorize-logo")
1073
+ async def vectorize_logo_endpoint(file: UploadFile = File(...)):
 
1074
  try:
1075
  image_bytes = await file.read()
1076
  img = Image.open(io.BytesIO(image_bytes))
 
1079
  caption = "CONVERTLY Logo"
1080
  if HF_TOKEN:
1081
  try:
1082
+ headers = {"Authorization": f"Bearer {HF_TOKEN}"}
1083
  url = "https://api-inference.huggingface.co/models/Salesforce/blip-image-captioning-base"
1084
+ response = requests.post(url, headers=headers, data=image_bytes, timeout=30)
1085
+ if response.status_code == 200:
1086
+ caption = response.content.decode('utf-8', errors='ignore')[:50]
1087
  except:
1088
  pass
1089
 
 
1108
  return JSONResponse({"error": str(e)}, status_code=500)
1109
 
1110
  if __name__ == "__main__":
1111
+ print("=" * 50)
1112
+ print("πŸš€ CONVERTLY API SERVER - FULL VERSION")
1113
+ print("=" * 50)
1114
+ print(f"πŸ“š Supported formats: {len(ALL_EXTENSIONS)}")
1115
+ print(f"πŸ”‘ HF_TOKEN: {'βœ… Configured' if HF_TOKEN else '❌ Missing'}")
1116
+ print("=" * 50)
1117
+ print("πŸ“‘ Server will run at: http://localhost:8000")
1118
+ print("πŸ“š API Docs: http://localhost:8000/docs")
1119
+ print("=" * 50)
1120
+
1121
+ uvicorn.run(
1122
+ app,
1123
+ host="0.0.0.0",
1124
+ port=8000,
1125
+ log_level="info"
1126
+ )