diff --git a/QUICKSTART.md b/QUICKSTART.md index 2eb4816..a49100d 100644 --- a/QUICKSTART.md +++ b/QUICKSTART.md @@ -52,6 +52,10 @@ Die App läuft jetzt lokal auf Ihrem Computer. Sie können: - ✅ **NEU: Bildorientierung ändern** - 90°, 180°, 270° Rotation - ✅ **NEU: Verschiedene Vorschaugrößen** - Klein, Mittel, Groß - ✅ **NEU: Bildmodal** - Bilder in voller Größe anzeigen +- ✅ **NEU: PDF-Vorschau** - Erste Seite als Thumbnail anzeigen +- ✅ **NEU: PDF-Rotation** - PDFs vor dem Zusammenführen drehen +- ✅ **NEU: PDF-Reihenfolge** - PDFs durch Ziehen sortieren +- ✅ **NEU: PDF-Modal** - PDF-Seiten in voller Größe betrachten - ✅ Vollständig offline arbeiten ## Bei Problemen: diff --git a/app.py b/app.py index 58a3146..93d81e6 100644 --- a/app.py +++ b/app.py @@ -204,12 +204,17 @@ def upload_pdf(): pdf_reader = PyPDF2.PdfReader(pdf_file) page_count = len(pdf_reader.pages) + # PDF Vorschau erstellen + preview_filename = create_pdf_preview(unique_filename) + return jsonify({ 'success': True, 'filename': unique_filename, 'original_name': filename, 'page_count': page_count, - 'size': os.path.getsize(file_path) + 'size': os.path.getsize(file_path), + 'preview': preview_filename, + 'rotation': 0 # Standardrotation }) return jsonify({'error': 'Nur PDF-Dateien sind erlaubt'}), 400 @@ -217,6 +222,29 @@ def upload_pdf(): except Exception as e: return jsonify({'error': f'Fehler beim Upload: {str(e)}'}), 500 +def create_pdf_preview(pdf_filename): + """Erstellt eine Vorschau der ersten PDF-Seite""" + try: + pdf_path = os.path.join(UPLOAD_FOLDER, pdf_filename) + + # PDF zu Bild konvertieren (nur erste Seite) + images = pdf2image.convert_from_path(pdf_path, first_page=1, last_page=1, dpi=150) + + if images: + # Vorschau-Dateiname generieren + preview_filename = f"preview_{pdf_filename.rsplit('.', 1)[0]}.png" + preview_path = os.path.join(UPLOAD_FOLDER, preview_filename) + + # Bild speichern + images[0].save(preview_path, 'PNG') + return preview_filename + + return None + + except Exception as e: + print(f"Fehler beim Erstellen der PDF-Vorschau: {str(e)}") + return None + @app.route('/api/pdf-to-images', methods=['POST']) def pdf_to_images(): """Konvertiert PDF zu Bildern""" @@ -262,18 +290,31 @@ def merge_pdfs(): """Fügt mehrere PDFs zusammen""" try: data = request.get_json() - filenames = data.get('filenames', []) + files_data = data.get('files', []) - if len(filenames) < 2: + if len(files_data) < 2: return jsonify({'error': 'Mindestens 2 PDF-Dateien erforderlich'}), 400 # PDF Merger erstellen merger = PyPDF2.PdfMerger() - for filename in filenames: + for file_data in files_data: + filename = file_data.get('filename') + rotation = file_data.get('rotation', 0) + file_path = os.path.join(UPLOAD_FOLDER, filename) if os.path.exists(file_path): - merger.append(file_path) + if rotation != 0: + # PDF mit Rotation erstellen + rotated_pdf_path = create_rotated_pdf(file_path, rotation) + if rotated_pdf_path: + merger.append(rotated_pdf_path) + # Temporäre rotierte PDF löschen nach dem Hinzufügen + os.remove(rotated_pdf_path) + else: + merger.append(file_path) # Fallback ohne Rotation + else: + merger.append(file_path) # Zusammengeführte PDF speichern output_filename = f"merged_pdf_{str(uuid.uuid4())[:8]}.pdf" @@ -287,12 +328,37 @@ def merge_pdfs(): return jsonify({ 'success': True, 'filename': output_filename, - 'message': f'{len(filenames)} PDFs erfolgreich zusammengeführt' + 'message': f'{len(files_data)} PDFs erfolgreich zusammengeführt' }) except Exception as e: return jsonify({'error': f'Fehler beim Zusammenführen: {str(e)}'}), 500 +def create_rotated_pdf(pdf_path, rotation): + """Erstellt eine rotierte Version der PDF""" + try: + # Temporären Dateinamen für rotierte PDF erstellen + temp_filename = f"temp_rotated_{str(uuid.uuid4())[:8]}.pdf" + temp_path = os.path.join(UPLOAD_FOLDER, temp_filename) + + with open(pdf_path, 'rb') as input_file: + pdf_reader = PyPDF2.PdfReader(input_file) + pdf_writer = PyPDF2.PdfWriter() + + for page in pdf_reader.pages: + # Seite rotieren + rotated_page = page.rotate(rotation) + pdf_writer.add_page(rotated_page) + + with open(temp_path, 'wb') as output_file: + pdf_writer.write(output_file) + + return temp_path + + except Exception as e: + print(f"Fehler beim Rotieren der PDF: {str(e)}") + return None + @app.route('/uploads/') def serve_uploaded_file(filename): """Serviert hochgeladene Dateien für die Vorschau""" diff --git a/output/.gitkeep b/output/.gitkeep deleted file mode 100644 index 39b028c..0000000 --- a/output/.gitkeep +++ /dev/null @@ -1,2 +0,0 @@ -# Diese Datei sorgt dafür, dass der output Ordner in Git erhalten bleibt -# Der Ordnerinhalt wird durch .gitignore ignoriert \ No newline at end of file diff --git a/static/css/style.css b/static/css/style.css index ee24e72..0cc5847 100644 --- a/static/css/style.css +++ b/static/css/style.css @@ -485,6 +485,75 @@ footer { object-fit: contain; } +/* PDF Preview */ +.pdf-preview { + max-width: 120px; + max-height: 120px; + object-fit: cover; + border-radius: var(--border-radius); + border: 2px solid #e9ecef; + transition: all 0.3s ease; + cursor: pointer; + background-color: #f8f9fa; +} + +.pdf-preview:hover { + border-color: var(--danger-color); + transform: scale(1.05); +} + +.pdf-preview.size-small { + max-width: 80px; + max-height: 80px; +} + +.pdf-preview.size-medium { + max-width: 120px; + max-height: 120px; +} + +.pdf-preview.size-large { + max-width: 160px; + max-height: 160px; +} + +/* PDF item enhanced */ +.pdf-item-enhanced { + padding: 1rem; + border-radius: var(--border-radius); + transition: all 0.3s ease; +} + +.pdf-item-enhanced:hover { + background-color: rgba(220, 53, 69, 0.05); + transform: translateY(-2px); + box-shadow: var(--box-shadow-lg); +} + +.pdf-info { + flex: 1; + min-width: 0; +} + +.pdf-controls { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: flex-end; +} + +/* PDF no-preview fallback */ +.pdf-no-preview { + display: flex; + align-items: center; + justify-content: center; + background-color: #f8f9fa; + border: 2px dashed #dee2e6; + border-radius: var(--border-radius); + color: var(--danger-color); + font-size: 2rem; +} + /* File item enhanced */ .file-item-enhanced { padding: 1rem; diff --git a/static/js/pdf-tools.js b/static/js/pdf-tools.js index 2bd41a2..99b3807 100644 --- a/static/js/pdf-tools.js +++ b/static/js/pdf-tools.js @@ -160,22 +160,56 @@ function displayMergeFiles() { function createMergeFileItem(file, index) { const div = document.createElement('div'); - div.className = 'list-group-item'; + div.className = 'list-group-item pdf-item-enhanced'; div.dataset.index = index; + // Initialize rotation if not set + if (file.rotation === undefined) { + file.rotation = 0; + } + + // Create preview element + const previewElement = file.preview ? + `${file.original_name}` : + `
+ +
`; + div.innerHTML = `
-
- +
+ ${previewElement}
-
+
${file.original_name}
${file.page_count} Seiten • ${formatFileSize(file.size)}
+
+ + + ${file.rotation}° +
-
- + +
+
@@ -210,6 +244,108 @@ function removeMergeFile(index) { showNotification('PDF-Datei entfernt.', 'info'); } +function rotatePdf(index, degrees) { + if (index >= 0 && index < pdfToolsFiles.length) { + pdfToolsFiles[index].rotation = (pdfToolsFiles[index].rotation + degrees) % 360; + if (pdfToolsFiles[index].rotation < 0) { + pdfToolsFiles[index].rotation += 360; + } + displayMergeFiles(); + showNotification(`PDF um ${degrees}° gedreht.`, 'info'); + } +} + +function movePdfUp(index) { + if (index > 0) { + [pdfToolsFiles[index], pdfToolsFiles[index - 1]] = [pdfToolsFiles[index - 1], pdfToolsFiles[index]]; + displayMergeFiles(); + } +} + +function movePdfDown(index) { + if (index < pdfToolsFiles.length - 1) { + [pdfToolsFiles[index], pdfToolsFiles[index + 1]] = [pdfToolsFiles[index + 1], pdfToolsFiles[index]]; + displayMergeFiles(); + } +} + +function showPdfModal(previewFilename, originalName, index) { + // Create modal if it doesn't exist + let modal = document.getElementById('pdf-modal'); + if (!modal) { + modal = document.createElement('div'); + modal.className = 'modal fade image-modal'; + modal.id = 'pdf-modal'; + modal.setAttribute('tabindex', '-1'); + modal.setAttribute('aria-hidden', 'true'); + + modal.innerHTML = ` + + `; + + document.body.appendChild(modal); + } else { + // Update existing modal + document.getElementById('pdf-modal-title').textContent = originalName; + const modalPreview = document.getElementById('modal-pdf-preview'); + modalPreview.src = `/uploads/${previewFilename}`; + modalPreview.alt = originalName; + modalPreview.className = `img-fluid rotate-${pdfToolsFiles[index].rotation}`; + + // Update rotation buttons + const rotateButtons = modal.querySelectorAll('.btn-outline-danger'); + rotateButtons[0].onclick = () => rotatePdfInModal(index, -90); + rotateButtons[1].onclick = () => rotatePdfInModal(index, 90); + } + + // Show modal + const bootstrapModal = new bootstrap.Modal(modal); + bootstrapModal.show(); +} + +function rotatePdfInModal(index, degrees) { + rotatePdf(index, degrees); + + // Update modal preview + const modalPreview = document.getElementById('modal-pdf-preview'); + modalPreview.className = `img-fluid rotate-${pdfToolsFiles[index].rotation}`; +} + +function updatePdfPreviewSize() { + const previewSize = document.getElementById('pdf-preview-size')?.value || 'medium'; + const previews = document.querySelectorAll('.pdf-preview'); + + previews.forEach(preview => { + // Remove existing size classes + preview.classList.remove('size-small', 'size-medium', 'size-large'); + // Add new size class + preview.classList.add('size-' + previewSize); + }); +} + async function mergePdfs() { if (pdfToolsFiles.length < 2) { showError('Mindestens 2 PDF-Dateien erforderlich.'); @@ -220,11 +356,16 @@ async function mergePdfs() { processingModal.show(); try { - const filenames = pdfToolsFiles.map(file => file.filename); + // Include rotation data for each file + const filesWithRotation = pdfToolsFiles.map(file => ({ + filename: file.filename, + rotation: file.rotation || 0, + original_name: file.original_name + })); const response = await makeRequest('/api/merge-pdfs', { method: 'POST', - body: JSON.stringify({ filenames }) + body: JSON.stringify({ files: filesWithRotation }) }); if (response.success) { @@ -283,9 +424,30 @@ function displaySplitFile() { document.getElementById('split-file-details').textContent = `${currentPdfFile.page_count} Seiten • ${formatFileSize(currentPdfFile.size)}`; + // PDF-Vorschau anzeigen falls verfügbar + const previewContainer = document.getElementById('split-preview-container'); + if (currentPdfFile.preview) { + previewContainer.innerHTML = ` + ${currentPdfFile.original_name} + `; + } else { + previewContainer.innerHTML = ` +
+ +
+ `; + } + splitFileInfo.style.display = 'block'; } +function showSplitPdfModal() { + if (currentPdfFile && currentPdfFile.preview) { + showPdfModal(currentPdfFile.preview, currentPdfFile.original_name, 0); + } +} + async function convertPdfToImages() { if (!currentPdfFile) { showError('Keine PDF-Datei ausgewählt.'); @@ -339,4 +501,11 @@ function showError(message) { } // Global functions -window.removeMergeFile = removeMergeFile; \ No newline at end of file +window.removeMergeFile = removeMergeFile; +window.rotatePdf = rotatePdf; +window.movePdfUp = movePdfUp; +window.movePdfDown = movePdfDown; +window.showPdfModal = showPdfModal; +window.rotatePdfInModal = rotatePdfInModal; +window.updatePdfPreviewSize = updatePdfPreviewSize; +window.showSplitPdfModal = showSplitPdfModal; \ No newline at end of file diff --git a/templates/pdf_tools.html b/templates/pdf_tools.html index 754d658..08f0018 100644 --- a/templates/pdf_tools.html +++ b/templates/pdf_tools.html @@ -99,6 +99,31 @@
+
+
+ Einstellungen +
+
+
+
+ + +
+ +
+ + +
+
+
+ +
Anleitung @@ -108,9 +133,17 @@
  1. Wählen Sie mindestens 2 PDF-Dateien aus
  2. Sortieren Sie die Reihenfolge durch Ziehen
  3. +
  4. Rotieren Sie PDFs bei Bedarf
  5. Klicken Sie auf "PDFs zusammenführen"
  6. Laden Sie die zusammengeführte PDF herunter
+ +
+ + + Tipp: Klicken Sie auf eine PDF-Vorschau für eine größere Ansicht der ersten Seite. + +
@@ -142,7 +175,9 @@
- +
+ +
diff --git a/uploads/.gitkeep b/uploads/.gitkeep deleted file mode 100644 index d842cca..0000000 --- a/uploads/.gitkeep +++ /dev/null @@ -1,2 +0,0 @@ -# Diese Datei sorgt dafür, dass der uploads Ordner in Git erhalten bleibt -# Der Ordnerinhalt wird durch .gitignore ignoriert \ No newline at end of file