Files
PDF-WEB/app.py
SimolZimol 1097fa0c64 modified: QUICKSTART.md
modified:   app.py
	deleted:    output/.gitkeep
	modified:   static/css/style.css
	modified:   static/js/pdf-tools.js
	modified:   templates/pdf_tools.html
	deleted:    uploads/.gitkeep
2025-10-12 23:50:26 +02:00

397 lines
14 KiB
Python

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
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),
'preview': preview_filename,
'rotation': 0 # Standardrotation
})
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)
# 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"""
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/<filename>')
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):
return "Datei nicht gefunden", 404
return send_file(file_path)
except Exception as e:
return f"Fehler beim Laden der Datei: {str(e)}", 500
@app.route('/download/<filename>')
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)