import os import uuid from flask import Flask, render_template, request, jsonify, send_file, redirect, url_for, flash from werkzeug.utils import secure_filename from PIL import Image import PyPDF2 from reportlab.pdfgen import canvas from reportlab.lib.pagesizes import letter, A4 from reportlab.lib.utils import ImageReader import io import zipfile import pdf2image import tempfile import shutil from datetime import datetime app = Flask(__name__) app.secret_key = 'your-secret-key-change-this' # Konfiguration UPLOAD_FOLDER = 'uploads' OUTPUT_FOLDER = 'output' ALLOWED_IMAGE_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif', 'bmp', 'tiff'} ALLOWED_PDF_EXTENSIONS = {'pdf'} MAX_FILE_SIZE = 16 * 1024 * 1024 # 16MB app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER app.config['OUTPUT_FOLDER'] = OUTPUT_FOLDER app.config['MAX_CONTENT_LENGTH'] = MAX_FILE_SIZE def allowed_file(filename, extensions): """Überprüft ob die Datei erlaubt ist""" return '.' in filename and \ filename.rsplit('.', 1)[1].lower() in extensions def create_unique_filename(filename): """Erstellt einen eindeutigen Dateinamen""" name, ext = os.path.splitext(filename) unique_id = str(uuid.uuid4())[:8] return f"{name}_{unique_id}{ext}" def cleanup_old_files(): """Löscht alte Dateien aus Upload und Output Ordnern""" current_time = datetime.now() for folder in [UPLOAD_FOLDER, OUTPUT_FOLDER]: if os.path.exists(folder): for filename in os.listdir(folder): file_path = os.path.join(folder, filename) if os.path.isfile(file_path): file_time = datetime.fromtimestamp(os.path.getctime(file_path)) # Lösche Dateien die älter als 1 Stunde sind if (current_time - file_time).seconds > 3600: os.remove(file_path) @app.route('/') def index(): """Hauptseite mit Navigation""" cleanup_old_files() return render_template('index.html') @app.route('/images-to-pdf') def images_to_pdf_page(): """Seite für Bilder zu PDF Konvertierung""" return render_template('images_to_pdf.html') @app.route('/pdf-tools') def pdf_tools_page(): """Seite für PDF Tools""" return render_template('pdf_tools.html') @app.route('/api/upload-images', methods=['POST']) def upload_images(): """API Endpunkt für Bilder Upload""" try: if 'files' not in request.files: return jsonify({'error': 'Keine Dateien ausgewählt'}), 400 files = request.files.getlist('files') if not files or files[0].filename == '': return jsonify({'error': 'Keine Dateien ausgewählt'}), 400 uploaded_files = [] for file in files: if file and allowed_file(file.filename, ALLOWED_IMAGE_EXTENSIONS): filename = secure_filename(file.filename) unique_filename = create_unique_filename(filename) file_path = os.path.join(UPLOAD_FOLDER, unique_filename) file.save(file_path) uploaded_files.append({ 'filename': unique_filename, 'original_name': filename, 'size': os.path.getsize(file_path) }) if not uploaded_files: return jsonify({'error': 'Keine gültigen Bilddateien gefunden'}), 400 return jsonify({ 'success': True, 'files': uploaded_files, 'message': f'{len(uploaded_files)} Dateien erfolgreich hochgeladen' }) except Exception as e: return jsonify({'error': f'Fehler beim Upload: {str(e)}'}), 500 @app.route('/api/convert-images-to-pdf', methods=['POST']) def convert_images_to_pdf(): """Konvertiert hochgeladene Bilder zu PDF""" try: data = request.get_json() files_data = data.get('files', []) if not files_data: return jsonify({'error': 'Keine Dateien ausgewählt'}), 400 # PDF erstellen output_filename = f"converted_images_{str(uuid.uuid4())[:8]}.pdf" output_path = os.path.join(OUTPUT_FOLDER, output_filename) # Reportlab für PDF Erstellung c = canvas.Canvas(output_path, pagesize=A4) page_width, page_height = A4 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): # Bild öffnen und rotieren with Image.open(file_path) as img: # Bild rotieren falls nötig if rotation != 0: img = img.rotate(-rotation, expand=True) # Negative Rotation für korrekte Richtung img_width, img_height = img.size # Seitenverhältnis berechnen aspect_ratio = img_width / img_height # Maximale Größe auf der Seite (mit Rändern) max_width = page_width - 40 max_height = page_height - 40 if aspect_ratio > 1: # Querformat new_width = min(max_width, img_width) new_height = new_width / aspect_ratio else: # Hochformat new_height = min(max_height, img_height) new_width = new_height * aspect_ratio # Zentrieren x = (page_width - new_width) / 2 y = (page_height - new_height) / 2 # Temporäres rotiertes Bild speichern if rotation != 0: temp_path = os.path.join(UPLOAD_FOLDER, f"temp_rotated_{filename}") img.save(temp_path) c.drawImage(temp_path, x, y, width=new_width, height=new_height) # Temporäre Datei löschen os.remove(temp_path) else: c.drawImage(file_path, x, y, width=new_width, height=new_height) c.showPage() c.save() return jsonify({ 'success': True, 'filename': output_filename, 'message': 'PDF erfolgreich erstellt' }) except Exception as e: return jsonify({'error': f'Fehler bei der Konvertierung: {str(e)}'}), 500 @app.route('/api/upload-pdf', methods=['POST']) def upload_pdf(): """API Endpunkt für PDF Upload""" try: if 'file' not in request.files: return jsonify({'error': 'Keine Datei ausgewählt'}), 400 file = request.files['file'] if file.filename == '': return jsonify({'error': 'Keine Datei ausgewählt'}), 400 if file and allowed_file(file.filename, ALLOWED_PDF_EXTENSIONS): filename = secure_filename(file.filename) unique_filename = create_unique_filename(filename) file_path = os.path.join(UPLOAD_FOLDER, unique_filename) file.save(file_path) # PDF Info lesen with open(file_path, 'rb') as pdf_file: pdf_reader = PyPDF2.PdfReader(pdf_file) page_count = len(pdf_reader.pages) # PDF Vorschau erstellen print(f"Erstelle Vorschau für: {unique_filename}") preview_filename = create_pdf_preview(unique_filename) if preview_filename: print(f"Vorschau erfolgreich erstellt: {preview_filename}") else: print("Vorschau-Erstellung fehlgeschlagen, verwende Fallback") return jsonify({ 'success': True, 'filename': unique_filename, 'original_name': filename, 'page_count': page_count, 'size': os.path.getsize(file_path), 'preview': preview_filename, 'rotation': 0, # Standardrotation 'has_preview': preview_filename is not None }) return jsonify({'error': 'Nur PDF-Dateien sind erlaubt'}), 400 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) # Vorschau-Dateiname generieren preview_filename = f"preview_{pdf_filename.rsplit('.', 1)[0]}.png" preview_path = os.path.join(UPLOAD_FOLDER, preview_filename) # PDF zu Bild konvertieren (nur erste Seite) try: # Versuche mit pdf2image (benötigt Poppler) images = pdf2image.convert_from_path( pdf_path, first_page=1, last_page=1, dpi=150, fmt='PNG' ) if images: # Bild speichern images[0].save(preview_path, 'PNG') print(f"PDF-Vorschau erstellt: {preview_filename}") return preview_filename except ImportError: print("pdf2image nicht verfügbar - PDF-Vorschau übersprungen") return None except Exception as pdf2image_error: print(f"pdf2image Fehler: {str(pdf2image_error)}") # Fallback: Versuche mit PyPDF2 + reportlab eine einfache Vorschau zu erstellen try: return create_simple_pdf_preview(pdf_path, preview_path, pdf_filename) except Exception as fallback_error: print(f"Fallback Fehler: {str(fallback_error)}") return None return None except Exception as e: print(f"Allgemeiner Fehler beim Erstellen der PDF-Vorschau: {str(e)}") return None def create_simple_pdf_preview(pdf_path, preview_path, pdf_filename): """Erstellt eine einfache Text-basierte PDF-Vorschau als Fallback""" try: from PIL import Image, ImageDraw, ImageFont # Lese PDF-Informationen with open(pdf_path, 'rb') as pdf_file: pdf_reader = PyPDF2.PdfReader(pdf_file) page_count = len(pdf_reader.pages) # Versuche Text der ersten Seite zu extrahieren first_page_text = "" if page_count > 0: try: first_page_text = pdf_reader.pages[0].extract_text()[:200] except: first_page_text = "PDF-Inhalt" # Erstelle ein einfaches Vorschaubild img = Image.new('RGB', (300, 400), color='white') draw = ImageDraw.Draw(img) # Versuche Standard-Font zu laden try: font = ImageFont.truetype("arial.ttf", 12) title_font = ImageFont.truetype("arial.ttf", 16) except: font = ImageFont.load_default() title_font = ImageFont.load_default() # Zeichne PDF-Vorschau draw.rectangle([(10, 10), (290, 390)], outline='black', width=2) draw.text((20, 20), f"PDF: {pdf_filename}", fill='black', font=title_font) draw.text((20, 50), f"Seiten: {page_count}", fill='gray', font=font) # Text-Vorschau if first_page_text: # Text umbrechen words = first_page_text.split() lines = [] current_line = "" for word in words[:50]: # Maximal 50 Wörter if len(current_line + word) < 35: current_line += word + " " else: lines.append(current_line.strip()) current_line = word + " " if len(lines) >= 15: # Maximal 15 Zeilen break if current_line: lines.append(current_line.strip()) y_pos = 80 for line in lines: draw.text((20, y_pos), line, fill='black', font=font) y_pos += 18 # Speichere Vorschaubild img.save(preview_path, 'PNG') print(f"Fallback PDF-Vorschau erstellt: {preview_path}") return os.path.basename(preview_path) except Exception as e: print(f"Fehler bei Fallback-Vorschau: {str(e)}") return None @app.route('/api/pdf-to-images', methods=['POST']) def pdf_to_images(): """Konvertiert PDF zu Bildern""" try: data = request.get_json() filename = data.get('filename') if not filename: return jsonify({'error': 'Keine Datei angegeben'}), 400 file_path = os.path.join(UPLOAD_FOLDER, filename) if not os.path.exists(file_path): return jsonify({'error': 'Datei nicht gefunden'}), 404 # PDF zu Bildern konvertieren images = pdf2image.convert_from_path(file_path, dpi=200) # ZIP-Datei erstellen zip_filename = f"pdf_images_{str(uuid.uuid4())[:8]}.zip" zip_path = os.path.join(OUTPUT_FOLDER, zip_filename) with zipfile.ZipFile(zip_path, 'w') as zip_file: for i, image in enumerate(images): img_filename = f"page_{i+1}.png" img_buffer = io.BytesIO() image.save(img_buffer, format='PNG') img_buffer.seek(0) zip_file.writestr(img_filename, img_buffer.read()) return jsonify({ 'success': True, 'filename': zip_filename, 'page_count': len(images), 'message': f'{len(images)} Seiten als Bilder exportiert' }) except Exception as e: return jsonify({'error': f'Fehler bei der Konvertierung: {str(e)}'}), 500 @app.route('/api/merge-pdfs', methods=['POST']) def merge_pdfs(): """Fügt mehrere PDFs zusammen""" try: data = request.get_json() files_data = data.get('files', []) if len(files_data) < 2: return jsonify({'error': 'Mindestens 2 PDF-Dateien erforderlich'}), 400 # PDF Merger erstellen merger = PyPDF2.PdfMerger() 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): 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" output_path = os.path.join(OUTPUT_FOLDER, output_filename) with open(output_path, 'wb') as output_file: merger.write(output_file) merger.close() return jsonify({ 'success': True, 'filename': output_filename, '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""" try: file_path = os.path.join(UPLOAD_FOLDER, filename) if not os.path.exists(file_path): print(f"Datei nicht gefunden: {file_path}") return "Datei nicht gefunden", 404 return send_file(file_path) except Exception as e: print(f"Fehler beim Servieren der Datei {filename}: {str(e)}") return f"Fehler beim Laden der Datei: {str(e)}", 500 @app.route('/debug/pdf-preview/') def debug_pdf_preview(filename): """Debug-Route für PDF-Vorschau-Probleme""" try: pdf_path = os.path.join(UPLOAD_FOLDER, filename) if not os.path.exists(pdf_path): return jsonify({'error': f'PDF nicht gefunden: {pdf_path}'}) # Teste PDF-Vorschau-Erstellung preview_filename = create_pdf_preview(filename) return jsonify({ 'pdf_file': filename, 'pdf_exists': os.path.exists(pdf_path), 'preview_created': preview_filename is not None, 'preview_filename': preview_filename, 'preview_path': os.path.join(UPLOAD_FOLDER, preview_filename) if preview_filename else None, 'preview_exists': os.path.exists(os.path.join(UPLOAD_FOLDER, preview_filename)) if preview_filename else False }) except Exception as e: return jsonify({'error': f'Debug-Fehler: {str(e)}'}) @app.route('/debug/check-dependencies') def debug_check_dependencies(): """Überprüft verfügbare Bibliotheken""" deps = {} try: import pdf2image deps['pdf2image'] = 'verfügbar' # Teste Poppler try: pdf2image.convert_from_bytes(b'dummy') except Exception as e: if 'poppler' in str(e).lower(): deps['poppler'] = 'nicht gefunden' else: deps['poppler'] = 'möglicherweise verfügbar' except ImportError: deps['pdf2image'] = 'nicht installiert' try: import PyPDF2 deps['PyPDF2'] = 'verfügbar' except ImportError: deps['PyPDF2'] = 'nicht installiert' try: from PIL import Image deps['Pillow'] = 'verfügbar' except ImportError: deps['Pillow'] = 'nicht installiert' return jsonify(deps) @app.route('/download/') def download_file(filename): """Download einer generierten Datei""" try: file_path = os.path.join(OUTPUT_FOLDER, filename) if not os.path.exists(file_path): flash('Datei nicht gefunden', 'error') return redirect(url_for('index')) return send_file(file_path, as_attachment=True) except Exception as e: flash(f'Fehler beim Download: {str(e)}', 'error') return redirect(url_for('index')) if __name__ == '__main__': # Ordner erstellen falls sie nicht existieren os.makedirs(UPLOAD_FOLDER, exist_ok=True) os.makedirs(OUTPUT_FOLDER, exist_ok=True) app.run(debug=True, host='127.0.0.1', port=5000)