jebin2 commited on
Commit
2b3b710
Β·
1 Parent(s): 53abf4b
PDF/__pycache__/images_to_pdf.cpython-313.pyc ADDED
Binary file (5.62 kB). View file
 
PDF/images_to_pdf.html ADDED
@@ -0,0 +1,662 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <title>Images to PDF - Tools Collection</title>
8
+ <style>
9
+ * {
10
+ margin: 0;
11
+ padding: 0;
12
+ box-sizing: border-box;
13
+ }
14
+
15
+ body {
16
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
17
+ background: linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%);
18
+ min-height: 100vh;
19
+ color: #e4e4e4;
20
+ }
21
+
22
+ .container {
23
+ max-width: 1000px;
24
+ margin: 0 auto;
25
+ padding: 40px 20px;
26
+ }
27
+
28
+ .header {
29
+ text-align: center;
30
+ margin-bottom: 40px;
31
+ }
32
+
33
+ .back-button {
34
+ position: absolute;
35
+ left: 20px;
36
+ top: 20px;
37
+ display: inline-flex;
38
+ align-items: center;
39
+ gap: 8px;
40
+ padding: 12px 20px;
41
+ background: rgba(255, 255, 255, 0.1);
42
+ border: 1px solid rgba(255, 255, 255, 0.2);
43
+ border-radius: 8px;
44
+ color: #e4e4e4;
45
+ text-decoration: none;
46
+ font-size: 0.9rem;
47
+ transition: all 0.3s ease;
48
+ }
49
+
50
+ .back-button:hover {
51
+ background: rgba(255, 255, 255, 0.2);
52
+ transform: translateX(-5px);
53
+ }
54
+
55
+ .back-button svg {
56
+ width: 16px;
57
+ height: 16px;
58
+ flex-shrink: 0;
59
+ }
60
+
61
+ .logo {
62
+ font-size: 4rem;
63
+ margin-bottom: 15px;
64
+ }
65
+
66
+ h1 {
67
+ font-size: 2.5rem;
68
+ background: linear-gradient(135deg, #e94560, #f39c12);
69
+ -webkit-background-clip: text;
70
+ -webkit-text-fill-color: transparent;
71
+ background-clip: text;
72
+ margin-bottom: 10px;
73
+ }
74
+
75
+ .subtitle {
76
+ color: #8892b0;
77
+ font-size: 1.1rem;
78
+ }
79
+
80
+ .tool-card {
81
+ background: rgba(255, 255, 255, 0.05);
82
+ border-radius: 20px;
83
+ padding: 40px;
84
+ backdrop-filter: blur(10px);
85
+ border: 1px solid rgba(255, 255, 255, 0.1);
86
+ }
87
+
88
+ .upload-section {
89
+ border: 2px dashed rgba(233, 69, 96, 0.5);
90
+ border-radius: 15px;
91
+ padding: 60px 40px;
92
+ text-align: center;
93
+ cursor: pointer;
94
+ transition: all 0.3s ease;
95
+ margin-bottom: 30px;
96
+ }
97
+
98
+ .upload-section:hover {
99
+ border-color: #e94560;
100
+ background: rgba(233, 69, 96, 0.1);
101
+ }
102
+
103
+ .upload-section.dragover {
104
+ border-color: #e94560;
105
+ background: rgba(233, 69, 96, 0.15);
106
+ transform: scale(1.02);
107
+ }
108
+
109
+ .upload-icon {
110
+ font-size: 3rem;
111
+ margin-bottom: 15px;
112
+ }
113
+
114
+ .upload-text {
115
+ font-size: 1.2rem;
116
+ margin-bottom: 10px;
117
+ }
118
+
119
+ .upload-hint {
120
+ color: #8892b0;
121
+ font-size: 0.9rem;
122
+ }
123
+
124
+ #file-input {
125
+ display: none;
126
+ }
127
+
128
+ .info-box {
129
+ background: rgba(23, 162, 184, 0.1);
130
+ border: 1px solid rgba(23, 162, 184, 0.3);
131
+ border-radius: 10px;
132
+ padding: 15px 20px;
133
+ margin-bottom: 30px;
134
+ display: flex;
135
+ align-items: center;
136
+ gap: 12px;
137
+ }
138
+
139
+ .info-box span {
140
+ font-size: 1.3rem;
141
+ }
142
+
143
+ .info-box p {
144
+ font-size: 0.9rem;
145
+ margin: 0;
146
+ }
147
+
148
+ .files-preview {
149
+ margin-bottom: 30px;
150
+ }
151
+
152
+ .preview-title {
153
+ font-size: 1.1rem;
154
+ margin-bottom: 15px;
155
+ color: #e4e4e4;
156
+ }
157
+
158
+ .files-grid {
159
+ display: grid;
160
+ grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
161
+ gap: 15px;
162
+ }
163
+
164
+ .file-card {
165
+ background: rgba(255, 255, 255, 0.05);
166
+ border-radius: 10px;
167
+ padding: 15px;
168
+ text-align: center;
169
+ position: relative;
170
+ }
171
+
172
+ .file-card .remove-button {
173
+ position: absolute;
174
+ top: 5px;
175
+ right: 5px;
176
+ width: 24px;
177
+ height: 24px;
178
+ border-radius: 50%;
179
+ border: none;
180
+ background: #dc3545;
181
+ color: white;
182
+ cursor: pointer;
183
+ font-size: 14px;
184
+ display: flex;
185
+ align-items: center;
186
+ justify-content: center;
187
+ }
188
+
189
+ .file-preview {
190
+ width: 100%;
191
+ height: 100px;
192
+ border-radius: 8px;
193
+ overflow: hidden;
194
+ margin-bottom: 10px;
195
+ background: rgba(0, 0, 0, 0.2);
196
+ display: flex;
197
+ align-items: center;
198
+ justify-content: center;
199
+ }
200
+
201
+ .file-preview img {
202
+ max-width: 100%;
203
+ max-height: 100%;
204
+ object-fit: contain;
205
+ }
206
+
207
+ .file-info h4 {
208
+ font-size: 0.8rem;
209
+ white-space: nowrap;
210
+ overflow: hidden;
211
+ text-overflow: ellipsis;
212
+ margin-bottom: 5px;
213
+ }
214
+
215
+ .file-info p {
216
+ font-size: 0.7rem;
217
+ color: #8892b0;
218
+ }
219
+
220
+ .file-status {
221
+ display: flex;
222
+ align-items: center;
223
+ justify-content: center;
224
+ gap: 5px;
225
+ margin-top: 8px;
226
+ }
227
+
228
+ .status-indicator {
229
+ width: 8px;
230
+ height: 8px;
231
+ border-radius: 50%;
232
+ background: #28a745;
233
+ }
234
+
235
+ .status-indicator.processing {
236
+ background: #ffc107;
237
+ animation: pulse 1s infinite;
238
+ }
239
+
240
+ .status-indicator.error {
241
+ background: #dc3545;
242
+ }
243
+
244
+ .action-buttons {
245
+ display: flex;
246
+ gap: 15px;
247
+ flex-wrap: wrap;
248
+ }
249
+
250
+ .btn {
251
+ padding: 15px 30px;
252
+ border-radius: 10px;
253
+ border: none;
254
+ font-size: 1rem;
255
+ font-weight: 600;
256
+ cursor: pointer;
257
+ transition: all 0.3s ease;
258
+ display: inline-flex;
259
+ align-items: center;
260
+ gap: 8px;
261
+ }
262
+
263
+ .btn-primary {
264
+ background: linear-gradient(135deg, #e94560, #f39c12);
265
+ color: white;
266
+ }
267
+
268
+ .btn-primary:hover:not(:disabled) {
269
+ transform: translateY(-2px);
270
+ box-shadow: 0 5px 20px rgba(233, 69, 96, 0.4);
271
+ }
272
+
273
+ .btn-primary:disabled {
274
+ opacity: 0.5;
275
+ cursor: not-allowed;
276
+ }
277
+
278
+ .btn-secondary {
279
+ background: rgba(255, 255, 255, 0.1);
280
+ color: #e4e4e4;
281
+ border: 1px solid rgba(255, 255, 255, 0.2);
282
+ }
283
+
284
+ .btn-secondary:hover {
285
+ background: rgba(255, 255, 255, 0.2);
286
+ }
287
+
288
+ .btn-success {
289
+ background: linear-gradient(135deg, #28a745, #20c997);
290
+ color: white;
291
+ display: none;
292
+ }
293
+
294
+ .progress-section {
295
+ display: none;
296
+ margin-bottom: 20px;
297
+ }
298
+
299
+ .progress-bar-container {
300
+ height: 8px;
301
+ background: rgba(255, 255, 255, 0.1);
302
+ border-radius: 4px;
303
+ overflow: hidden;
304
+ margin-bottom: 10px;
305
+ }
306
+
307
+ .progress-bar {
308
+ height: 100%;
309
+ background: linear-gradient(135deg, #e94560, #f39c12);
310
+ width: 0%;
311
+ transition: width 0.3s ease;
312
+ }
313
+
314
+ .progress-text {
315
+ font-size: 0.9rem;
316
+ color: #8892b0;
317
+ }
318
+
319
+ .error-message {
320
+ display: none;
321
+ background: rgba(220, 53, 69, 0.1);
322
+ border: 1px solid rgba(220, 53, 69, 0.3);
323
+ border-radius: 10px;
324
+ padding: 15px 20px;
325
+ margin-bottom: 20px;
326
+ color: #dc3545;
327
+ }
328
+
329
+ .success-message {
330
+ display: none;
331
+ background: rgba(40, 167, 69, 0.1);
332
+ border: 1px solid rgba(40, 167, 69, 0.3);
333
+ border-radius: 10px;
334
+ padding: 15px 20px;
335
+ margin-bottom: 20px;
336
+ color: #28a745;
337
+ }
338
+
339
+ @keyframes pulse {
340
+ 0% {
341
+ transform: scale(1);
342
+ opacity: 1;
343
+ }
344
+
345
+ 50% {
346
+ transform: scale(1.05);
347
+ opacity: 0.8;
348
+ }
349
+
350
+ 100% {
351
+ transform: scale(1);
352
+ opacity: 1;
353
+ }
354
+ }
355
+ </style>
356
+ </head>
357
+
358
+ <body>
359
+ <a href="/" class="back-button">
360
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
361
+ <path d="M19 12H5M12 19l-7-7 7-7" />
362
+ </svg>
363
+ Back to Tools
364
+ </a>
365
+
366
+ <div class="container">
367
+ <div class="header">
368
+ <div class="logo">πŸ“„</div>
369
+ <h1>Images to PDF</h1>
370
+ <p class="subtitle">Convert multiple images to a single PDF file</p>
371
+ </div>
372
+
373
+ <div class="tool-card">
374
+ <div class="info-box">
375
+ <span>ℹ️</span>
376
+ <p>Upload multiple images and convert them to a single PDF. Drag to reorder before converting.</p>
377
+ </div>
378
+
379
+ <div id="error-message" class="error-message"></div>
380
+ <div id="success-message" class="success-message"></div>
381
+
382
+ <div class="upload-section" id="upload-section">
383
+ <div class="upload-icon">πŸ“€</div>
384
+ <p class="upload-text">Drop images here or click to browse</p>
385
+ <p class="upload-hint">Supports JPG, PNG, WebP, BMP, GIF, TIFF</p>
386
+ <input type="file" id="file-input" accept="image/*" multiple>
387
+ </div>
388
+
389
+ <div class="files-preview" id="files-preview" style="display: none;">
390
+ <h3 class="preview-title">πŸ“‹ Selected Images</h3>
391
+ <div class="files-grid" id="files-grid"></div>
392
+ </div>
393
+
394
+ <div class="progress-section" id="progress-section">
395
+ <div class="progress-bar-container">
396
+ <div class="progress-bar" id="progress-bar"></div>
397
+ </div>
398
+ <p class="progress-text" id="progress-text">0% complete</p>
399
+ </div>
400
+
401
+ <div class="action-buttons">
402
+ <button class="btn btn-primary" id="convert-button" disabled>
403
+ πŸ“„ Convert to PDF
404
+ </button>
405
+ <button class="btn btn-secondary" id="clear-button">
406
+ πŸ—‘οΈ Clear All
407
+ </button>
408
+ <button class="btn btn-success" id="download-button">
409
+ ⬇️ Download PDF
410
+ </button>
411
+ </div>
412
+ </div>
413
+ </div>
414
+
415
+ <script>
416
+ let selectedFiles = [];
417
+ let pdfUrl = null;
418
+
419
+ document.addEventListener('DOMContentLoaded', function () {
420
+ setupEventListeners();
421
+ });
422
+
423
+ function setupEventListeners() {
424
+ const uploadSection = document.getElementById('upload-section');
425
+ const fileInput = document.getElementById('file-input');
426
+ const convertButton = document.getElementById('convert-button');
427
+ const clearButton = document.getElementById('clear-button');
428
+ const downloadButton = document.getElementById('download-button');
429
+
430
+ uploadSection.addEventListener('click', () => fileInput.click());
431
+ uploadSection.addEventListener('dragover', handleDragOver);
432
+ uploadSection.addEventListener('dragleave', handleDragLeave);
433
+ uploadSection.addEventListener('drop', handleDrop);
434
+ fileInput.addEventListener('change', handleFileSelect);
435
+
436
+ convertButton.addEventListener('click', convertToPDF);
437
+ clearButton.addEventListener('click', clearAllFiles);
438
+ downloadButton.addEventListener('click', downloadPDF);
439
+ }
440
+
441
+ function showMessage(message, type = 'error') {
442
+ const errorMsg = document.getElementById('error-message');
443
+ const successMsg = document.getElementById('success-message');
444
+
445
+ errorMsg.style.display = 'none';
446
+ successMsg.style.display = 'none';
447
+
448
+ if (type === 'error') {
449
+ errorMsg.textContent = message;
450
+ errorMsg.style.display = 'block';
451
+ } else {
452
+ successMsg.textContent = message;
453
+ successMsg.style.display = 'block';
454
+ }
455
+
456
+ setTimeout(() => {
457
+ errorMsg.style.display = 'none';
458
+ successMsg.style.display = 'none';
459
+ }, 5000);
460
+ }
461
+
462
+ function handleDragOver(e) {
463
+ e.preventDefault();
464
+ e.currentTarget.classList.add('dragover');
465
+ }
466
+
467
+ function handleDragLeave(e) {
468
+ e.preventDefault();
469
+ e.currentTarget.classList.remove('dragover');
470
+ }
471
+
472
+ function handleDrop(e) {
473
+ e.preventDefault();
474
+ e.currentTarget.classList.remove('dragover');
475
+ const files = Array.from(e.dataTransfer.files);
476
+ processFiles(files);
477
+ }
478
+
479
+ function handleFileSelect(e) {
480
+ const files = Array.from(e.target.files);
481
+ processFiles(files);
482
+ }
483
+
484
+ function generateUniqueId(file, length = 12) {
485
+ let ext = "." + file.name.split(".")[file.name.split(".").length - 1];
486
+ const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
487
+ return Array.from({ length }, () => chars[Math.floor(Math.random() * chars.length)]).join('') + ext;
488
+ }
489
+
490
+ function processFiles(files) {
491
+ files.forEach((file) => {
492
+ if (!file.type.startsWith('image/')) {
493
+ showMessage(`${file.name} is not an image file`, 'error');
494
+ return;
495
+ }
496
+
497
+ const id = generateUniqueId(file);
498
+ const fileData = {
499
+ id: id,
500
+ file: file,
501
+ name: file.name,
502
+ size: formatFileSize(file.size),
503
+ status: 'ready',
504
+ preview: null
505
+ };
506
+
507
+ const reader = new FileReader();
508
+ reader.onload = (e) => {
509
+ fileData.preview = e.target.result;
510
+ updateUI();
511
+ };
512
+ reader.readAsDataURL(file);
513
+
514
+ selectedFiles.push(fileData);
515
+ });
516
+
517
+ updateUI();
518
+ }
519
+
520
+ function formatFileSize(bytes) {
521
+ if (bytes === 0) return '0 Bytes';
522
+ const k = 1024;
523
+ const sizes = ['Bytes', 'KB', 'MB', 'GB'];
524
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
525
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
526
+ }
527
+
528
+ function updateUI() {
529
+ const filesPreview = document.getElementById('files-preview');
530
+ const convertButton = document.getElementById('convert-button');
531
+
532
+ if (selectedFiles.length > 0) {
533
+ filesPreview.style.display = 'block';
534
+ convertButton.disabled = false;
535
+ renderFileCards();
536
+ } else {
537
+ filesPreview.style.display = 'none';
538
+ convertButton.disabled = true;
539
+ }
540
+ }
541
+
542
+ function renderFileCards() {
543
+ const filesGrid = document.getElementById('files-grid');
544
+
545
+ filesGrid.innerHTML = selectedFiles.map((file, index) => `
546
+ <div class="file-card" id="file-${file.id}">
547
+ <button class="remove-button" onclick="removeFile('${file.id}')">Γ—</button>
548
+ <div class="file-preview">
549
+ ${file.preview ? `<img src="${file.preview}" alt="${file.name}">` : '<span>πŸ–ΌοΈ</span>'}
550
+ </div>
551
+ <div class="file-info">
552
+ <h4>${index + 1}. ${file.name}</h4>
553
+ <p>${file.size}</p>
554
+ </div>
555
+ </div>
556
+ `).join('');
557
+ }
558
+
559
+ function removeFile(fileId) {
560
+ selectedFiles = selectedFiles.filter(file => file.id !== fileId);
561
+ updateUI();
562
+ if (selectedFiles.length === 0) {
563
+ document.getElementById('download-button').style.display = 'none';
564
+ }
565
+ }
566
+
567
+ function clearAllFiles() {
568
+ selectedFiles = [];
569
+ pdfUrl = null;
570
+ updateUI();
571
+ document.getElementById('download-button').style.display = 'none';
572
+ document.getElementById('progress-section').style.display = 'none';
573
+ }
574
+
575
+ async function convertToPDF() {
576
+ if (selectedFiles.length === 0) return;
577
+
578
+ const progressSection = document.getElementById('progress-section');
579
+ const progressBar = document.getElementById('progress-bar');
580
+ const progressText = document.getElementById('progress-text');
581
+ const convertButton = document.getElementById('convert-button');
582
+
583
+ progressSection.style.display = 'block';
584
+ convertButton.disabled = true;
585
+ convertButton.innerHTML = 'πŸ”„ Converting...';
586
+
587
+ try {
588
+ // Upload all files first
589
+ const uploadedIds = [];
590
+ for (let i = 0; i < selectedFiles.length; i++) {
591
+ const file = selectedFiles[i];
592
+
593
+ const formData = new FormData();
594
+ formData.append('image', file.file);
595
+ formData.append('id', file.id);
596
+
597
+ await fetch('/pdf/upload', {
598
+ method: 'POST',
599
+ body: formData
600
+ });
601
+
602
+ uploadedIds.push(file.id);
603
+
604
+ const progress = Math.round(((i + 1) / selectedFiles.length) * 50);
605
+ progressBar.style.width = progress + '%';
606
+ progressText.textContent = `Uploading: ${progress}%`;
607
+ }
608
+
609
+ // Convert to PDF
610
+ progressText.textContent = 'Converting to PDF...';
611
+ progressBar.style.width = '75%';
612
+
613
+ const convertFormData = new FormData();
614
+ convertFormData.append('ids', uploadedIds.join(','));
615
+ convertFormData.append('output_name', 'images.pdf');
616
+
617
+ const response = await fetch('/pdf/convert', {
618
+ method: 'POST',
619
+ body: convertFormData
620
+ });
621
+
622
+ if (!response.ok) {
623
+ throw new Error('Conversion failed');
624
+ }
625
+
626
+ const result = await response.json();
627
+ pdfUrl = '/pdf/download?id=' + result.new_filename;
628
+
629
+ progressBar.style.width = '100%';
630
+ progressText.textContent = '100% complete';
631
+
632
+ document.getElementById('download-button').style.display = 'inline-flex';
633
+ showMessage('PDF created successfully!', 'success');
634
+
635
+ } catch (error) {
636
+ console.error('Conversion error:', error);
637
+ showMessage('Error converting to PDF: ' + error.message, 'error');
638
+ }
639
+
640
+ convertButton.disabled = false;
641
+ convertButton.innerHTML = 'πŸ“„ Convert to PDF';
642
+
643
+ setTimeout(() => {
644
+ progressSection.style.display = 'none';
645
+ }, 1000);
646
+ }
647
+
648
+ function downloadPDF() {
649
+ if (pdfUrl) {
650
+ const link = document.createElement('a');
651
+ link.href = pdfUrl;
652
+ link.download = 'images.pdf';
653
+ link.target = '_blank';
654
+ document.body.appendChild(link);
655
+ link.click();
656
+ document.body.removeChild(link);
657
+ }
658
+ }
659
+ </script>
660
+ </body>
661
+
662
+ </html>
PDF/images_to_pdf.py ADDED
@@ -0,0 +1,113 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Images to PDF Converter Module
3
+
4
+ This module provides functionality to convert multiple images into a single PDF file.
5
+ """
6
+
7
+ import os
8
+ from PIL import Image
9
+ from pathlib import Path
10
+ from custom_logger import logger_config
11
+
12
+
13
+ class ImagesToPDF:
14
+ """
15
+ Convert multiple images into a single PDF file.
16
+ """
17
+
18
+ def __init__(self):
19
+ self.current_path = os.path.abspath(os.path.join(os.path.dirname(__file__)))
20
+ self.input_dir = os.path.join(self.current_path, "input")
21
+ self.output_dir = os.path.join(self.current_path, "output")
22
+ self.supported_formats = ['jpg', 'jpeg', 'png', 'webp', 'bmp', 'gif', 'tiff', 'tif']
23
+
24
+ # Create directories if they don't exist
25
+ os.makedirs(self.input_dir, exist_ok=True)
26
+ os.makedirs(self.output_dir, exist_ok=True)
27
+
28
+ def _validate_image(self, filename: str) -> bool:
29
+ """Check if file is a supported image format."""
30
+ ext = Path(filename).suffix.lower().lstrip('.')
31
+ return ext in self.supported_formats
32
+
33
+ def convert_to_pdf(self, image_ids: list, output_name: str = "output.pdf") -> str:
34
+ """
35
+ Convert multiple images to a single PDF.
36
+
37
+ Args:
38
+ image_ids: List of image file IDs in the input directory
39
+ output_name: Name for the output PDF file
40
+
41
+ Returns:
42
+ Path to the generated PDF file, or None on failure
43
+ """
44
+ try:
45
+ if not image_ids:
46
+ raise ValueError("No images provided for conversion")
47
+
48
+ images = []
49
+ first_image = None
50
+
51
+ for img_id in image_ids:
52
+ img_path = os.path.join(self.input_dir, img_id)
53
+
54
+ if not os.path.exists(img_path):
55
+ logger_config.warning(f"Image not found: {img_path}")
56
+ continue
57
+
58
+ # Open and convert image to RGB (PDF requires RGB)
59
+ img = Image.open(img_path)
60
+
61
+ # Handle transparency by converting to RGB with white background
62
+ if img.mode in ('RGBA', 'LA', 'P'):
63
+ # Create white background
64
+ background = Image.new('RGB', img.size, (255, 255, 255))
65
+ if img.mode == 'P':
66
+ img = img.convert('RGBA')
67
+ background.paste(img, mask=img.split()[-1] if img.mode == 'RGBA' else None)
68
+ img = background
69
+ elif img.mode != 'RGB':
70
+ img = img.convert('RGB')
71
+
72
+ if first_image is None:
73
+ first_image = img
74
+ else:
75
+ images.append(img)
76
+
77
+ if first_image is None:
78
+ raise ValueError("No valid images found for conversion")
79
+
80
+ # Generate output path
81
+ output_path = os.path.join(self.output_dir, output_name)
82
+
83
+ # Save as PDF
84
+ if images:
85
+ first_image.save(
86
+ output_path,
87
+ save_all=True,
88
+ append_images=images,
89
+ resolution=100.0
90
+ )
91
+ else:
92
+ first_image.save(output_path, resolution=100.0)
93
+
94
+ logger_config.success(f"PDF created successfully: {output_path}")
95
+
96
+ # Cleanup input files
97
+ for img_id in image_ids:
98
+ img_path = os.path.join(self.input_dir, img_id)
99
+ if os.path.exists(img_path):
100
+ os.unlink(img_path)
101
+
102
+ return output_path
103
+
104
+ except Exception as e:
105
+ logger_config.error(f"PDF conversion failed: {str(e)}")
106
+ return None
107
+
108
+ def upload(self, file_id: str, file_content: bytes):
109
+ """Save uploaded file to input directory."""
110
+ file_path = os.path.join(self.input_dir, file_id)
111
+ with open(file_path, 'wb') as f:
112
+ f.write(file_content)
113
+ logger_config.info(f"File uploaded: {file_path}")
__pycache__/main.cpython-313.pyc CHANGED
Binary files a/__pycache__/main.cpython-313.pyc and b/__pycache__/main.cpython-313.pyc differ
 
main.py CHANGED
@@ -8,6 +8,7 @@ from pydantic import BaseModel
8
  from image.converter import Converter
9
  from image.remove_metadata import RemoveMetadata
10
  from image.remove_background import RemoveBackground
 
11
  import mimetypes
12
 
13
  app = FastAPI(title="Tools Collection", description="Collection of utility tools")
@@ -52,8 +53,7 @@ FEATURES = {
52
  "icon": "πŸ“„",
53
  "features": ["images_to_pdf"],
54
  "folder": "pdf",
55
- "tags": ["pdf", "merge", "convert", "images", "document"],
56
- "coming_soon": True
57
  },
58
 
59
  "video": {
@@ -95,6 +95,71 @@ async def image_tools(request: Request):
95
  async def video_player():
96
  return RedirectResponse(url="https://jebin2.github.io/JellyJump/player.html", status_code=302)
97
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
98
  @app.post("/image/upload")
99
  async def upload_image(
100
  id: str = Form(...),
 
8
  from image.converter import Converter
9
  from image.remove_metadata import RemoveMetadata
10
  from image.remove_background import RemoveBackground
11
+ from PDF.images_to_pdf import ImagesToPDF
12
  import mimetypes
13
 
14
  app = FastAPI(title="Tools Collection", description="Collection of utility tools")
 
53
  "icon": "πŸ“„",
54
  "features": ["images_to_pdf"],
55
  "folder": "pdf",
56
+ "tags": ["pdf", "merge", "convert", "images", "document"]
 
57
  },
58
 
59
  "video": {
 
95
  async def video_player():
96
  return RedirectResponse(url="https://jebin2.github.io/JellyJump/player.html", status_code=302)
97
 
98
+ @app.get("/pdf/images_to_pdf", response_class=HTMLResponse)
99
+ async def pdf_images_to_pdf(request: Request):
100
+ template = env.get_template("PDF/images_to_pdf.html")
101
+ html_content = template.render(request=request)
102
+ return HTMLResponse(content=html_content)
103
+
104
+ @app.post("/pdf/upload")
105
+ async def upload_pdf_image(
106
+ id: str = Form(...),
107
+ image: UploadFile = File(...)
108
+ ):
109
+ try:
110
+ converter = ImagesToPDF()
111
+ contents = await image.read()
112
+ converter.upload(id, contents)
113
+
114
+ return JSONResponse({
115
+ "success": True,
116
+ "message": "Image uploaded successfully"
117
+ })
118
+ except Exception as e:
119
+ logger_config.error(f"Upload failed: {str(e)}")
120
+ raise HTTPException(status_code=500, detail=str(e))
121
+
122
+ @app.post("/pdf/convert")
123
+ async def convert_images_to_pdf(
124
+ ids: str = Form(...),
125
+ output_name: str = Form("output.pdf")
126
+ ):
127
+ try:
128
+ # Parse comma-separated IDs
129
+ image_ids = [id.strip() for id in ids.split(',') if id.strip()]
130
+
131
+ converter = ImagesToPDF()
132
+ output_path = converter.convert_to_pdf(image_ids, output_name)
133
+
134
+ if output_path is None:
135
+ raise ValueError("PDF conversion failed")
136
+
137
+ return JSONResponse({
138
+ "success": True,
139
+ "message": "PDF created successfully",
140
+ "new_filename": output_path.split("/")[-1]
141
+ })
142
+ except Exception as e:
143
+ logger_config.error(f"PDF conversion failed: {str(e)}")
144
+ raise HTTPException(status_code=500, detail=str(e))
145
+
146
+ @app.get("/pdf/download")
147
+ async def download_pdf(id: str = Query(...)):
148
+ try:
149
+ converter = ImagesToPDF()
150
+ file_path = f"{converter.output_dir}/{id}"
151
+ if os.path.exists(file_path):
152
+ media_type, _ = mimetypes.guess_type(file_path)
153
+ return FileResponse(
154
+ path=file_path,
155
+ media_type=media_type or "application/pdf",
156
+ filename=id
157
+ )
158
+ raise HTTPException(status_code=404, detail="File not found")
159
+ except Exception as e:
160
+ logger_config.error(f"Download failed: {str(e)}")
161
+ raise HTTPException(status_code=500, detail=str(e))
162
+
163
  @app.post("/image/upload")
164
  async def upload_image(
165
  id: str = Form(...),