new file: app.py

new file:   templates/base.html
This commit is contained in:
SimolZimol
2025-10-12 22:05:53 +02:00
parent 9c70ae1645
commit b6ed71346e
4 changed files with 707 additions and 0 deletions

302
app.py Normal file
View File

@@ -0,0 +1,302 @@
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()
filenames = data.get('filenames', [])
if not filenames:
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 filename in filenames:
file_path = os.path.join(UPLOAD_FOLDER, filename)
if os.path.exists(file_path):
# Bild öffnen und Größe anpassen
with Image.open(file_path) as img:
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
# Bild zur PDF hinzufügen
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)
return jsonify({
'success': True,
'filename': unique_filename,
'original_name': filename,
'page_count': page_count,
'size': os.path.getsize(file_path)
})
return jsonify({'error': 'Nur PDF-Dateien sind erlaubt'}), 400
except Exception as e:
return jsonify({'error': f'Fehler beim Upload: {str(e)}'}), 500
@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()
filenames = data.get('filenames', [])
if len(filenames) < 2:
return jsonify({'error': 'Mindestens 2 PDF-Dateien erforderlich'}), 400
# PDF Merger erstellen
merger = PyPDF2.PdfMerger()
for filename in filenames:
file_path = os.path.join(UPLOAD_FOLDER, filename)
if os.path.exists(file_path):
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(filenames)} PDFs erfolgreich zusammengeführt'
})
except Exception as e:
return jsonify({'error': f'Fehler beim Zusammenführen: {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)

80
templates/base.html Normal file
View File

@@ -0,0 +1,80 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}PDF Editor{% endblock %}</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
<link href="{{ url_for('static', filename='css/style.css') }}" rel="stylesheet">
</head>
<body>
<!-- Navigation -->
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<div class="container">
<a class="navbar-brand" href="{{ url_for('index') }}">
<i class="fas fa-file-pdf me-2"></i>PDF Editor
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav ms-auto">
<li class="nav-item">
<a class="nav-link" href="{{ url_for('index') }}">
<i class="fas fa-home me-1"></i>Home
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ url_for('images_to_pdf_page') }}">
<i class="fas fa-images me-1"></i>Bilder zu PDF
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ url_for('pdf_tools_page') }}">
<i class="fas fa-tools me-1"></i>PDF Tools
</a>
</li>
</ul>
</div>
</div>
</nav>
<!-- Flash Messages -->
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
<div class="container mt-3">
{% for category, message in messages %}
<div class="alert alert-{{ 'danger' if category == 'error' else 'success' }} alert-dismissible fade show" role="alert">
{{ message }}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
{% endfor %}
</div>
{% endif %}
{% endwith %}
<!-- Main Content -->
<main class="container mt-4">
{% block content %}{% endblock %}
</main>
<!-- Footer -->
<footer class="bg-light mt-5 py-4">
<div class="container text-center">
<p class="mb-0 text-muted">
<i class="fas fa-file-pdf me-2"></i>PDF Editor Web App
<span class="mx-2">|</span>
Lokaler Flask Server
</p>
</div>
</footer>
<!-- Scripts -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
<script src="{{ url_for('static', filename='js/main.js') }}"></script>
{% block scripts %}{% endblock %}
</body>
</html>

View File

@@ -0,0 +1,171 @@
{% extends "base.html" %}
{% block title %}Bilder zu PDF - PDF Editor{% endblock %}
{% block content %}
<div class="row">
<div class="col-12">
<div class="page-header mb-4">
<h2>
<i class="fas fa-images me-2"></i>Bilder zu PDF konvertieren
</h2>
<p class="text-muted">
Laden Sie mehrere Bilder hoch und konvertieren Sie diese in eine PDF-Datei.
</p>
</div>
</div>
</div>
<div class="row">
<!-- Upload Area -->
<div class="col-lg-8">
<div class="card">
<div class="card-header">
<h5 class="mb-0">
<i class="fas fa-upload me-2"></i>Bilder hochladen
</h5>
</div>
<div class="card-body">
<!-- File Upload Area -->
<div id="upload-area" class="upload-area border-dashed p-4 text-center">
<div class="upload-content">
<i class="fas fa-cloud-upload-alt fa-3x text-muted mb-3"></i>
<h5>Bilder hier hinziehen oder klicken zum Auswählen</h5>
<p class="text-muted mb-3">
Unterstützte Formate: JPG, PNG, GIF, BMP, TIFF<br>
Maximale Dateigröße: 16 MB pro Datei
</p>
<input type="file" id="file-input" multiple accept="image/*" class="d-none">
<button type="button" class="btn btn-primary" onclick="document.getElementById('file-input').click()">
<i class="fas fa-folder-open me-2"></i>Dateien auswählen
</button>
</div>
</div>
<!-- Progress Bar -->
<div id="upload-progress" class="mt-3" style="display: none;">
<div class="d-flex justify-content-between align-items-center mb-2">
<small class="text-muted">Upload läuft...</small>
<small id="progress-text" class="text-muted">0%</small>
</div>
<div class="progress">
<div id="progress-bar" class="progress-bar" role="progressbar" style="width: 0%"></div>
</div>
</div>
<!-- File List -->
<div id="file-list" class="mt-4" style="display: none;">
<h6>Hochgeladene Dateien:</h6>
<div id="files-container" class="list-group sortable-list">
<!-- Files will be added here dynamically -->
</div>
<div class="mt-3 d-flex gap-2">
<button id="convert-btn" class="btn btn-success" disabled>
<i class="fas fa-file-pdf me-2"></i>Zu PDF konvertieren
</button>
<button id="clear-btn" class="btn btn-outline-secondary">
<i class="fas fa-trash me-2"></i>Alle entfernen
</button>
</div>
</div>
<!-- Result Area -->
<div id="result-area" class="mt-4" style="display: none;">
<div class="alert alert-success">
<div class="d-flex align-items-center">
<i class="fas fa-check-circle fa-2x me-3"></i>
<div class="flex-grow-1">
<h6 class="mb-1">PDF erfolgreich erstellt!</h6>
<p class="mb-0">Ihre Bilder wurden erfolgreich in eine PDF-Datei konvertiert.</p>
</div>
<a id="download-link" href="#" class="btn btn-success">
<i class="fas fa-download me-2"></i>PDF herunterladen
</a>
</div>
</div>
</div>
<!-- Error Area -->
<div id="error-area" class="mt-4" style="display: none;">
<div class="alert alert-danger">
<h6><i class="fas fa-exclamation-triangle me-2"></i>Fehler</h6>
<p id="error-message" class="mb-0"></p>
</div>
</div>
</div>
</div>
</div>
<!-- Sidebar -->
<div class="col-lg-4">
<div class="card">
<div class="card-header">
<h6 class="mb-0">
<i class="fas fa-cog me-2"></i>Einstellungen
</h6>
</div>
<div class="card-body">
<div class="mb-3">
<label class="form-label">Seitenformat:</label>
<select id="page-format" class="form-select">
<option value="A4">A4 (210 × 297 mm)</option>
<option value="Letter">Letter (216 × 279 mm)</option>
<option value="A3">A3 (297 × 420 mm)</option>
<option value="A5">A5 (148 × 210 mm)</option>
</select>
</div>
<div class="mb-3">
<label class="form-label">Bildanpassung:</label>
<select id="image-fit" class="form-select">
<option value="fit">An Seite anpassen</option>
<option value="fill">Seite füllen</option>
<option value="center">Zentrieren</option>
</select>
</div>
<div class="form-check mb-3">
<input class="form-check-input" type="checkbox" id="maintain-aspect-ratio" checked>
<label class="form-check-label">
Seitenverhältnis beibehalten
</label>
</div>
</div>
</div>
<!-- Tips -->
<div class="card mt-3">
<div class="card-header">
<h6 class="mb-0">
<i class="fas fa-lightbulb me-2"></i>Tipps
</h6>
</div>
<div class="card-body">
<ul class="list-unstyled mb-0">
<li class="mb-2">
<i class="fas fa-sort me-2 text-muted"></i>
<small>Ziehen Sie Dateien in der Liste, um die Reihenfolge zu ändern</small>
</li>
<li class="mb-2">
<i class="fas fa-images me-2 text-muted"></i>
<small>Hochauflösende Bilder ergeben bessere PDF-Qualität</small>
</li>
<li class="mb-2">
<i class="fas fa-file-pdf me-2 text-muted"></i>
<small>Jedes Bild wird auf eine separate PDF-Seite platziert</small>
</li>
<li>
<i class="fas fa-shield-alt me-2 text-muted"></i>
<small>Alle Dateien werden lokal verarbeitet - maximale Sicherheit</small>
</li>
</ul>
</div>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
<script src="{{ url_for('static', filename='js/images-to-pdf.js') }}"></script>
{% endblock %}

154
templates/index.html Normal file
View File

@@ -0,0 +1,154 @@
{% extends "base.html" %}
{% block title %}PDF Editor - Home{% endblock %}
{% block content %}
<div class="row">
<div class="col-12">
<!-- Hero Section -->
<div class="jumbotron bg-gradient text-white rounded p-5 mb-5">
<h1 class="display-4">
<i class="fas fa-file-pdf me-3"></i>PDF Editor Web App
</h1>
<p class="lead">
Ihre lokale Lösung für PDF-Bearbeitung: Bilder zu PDF konvertieren,
PDFs zusammenführen, Seiten extrahieren und vieles mehr.
</p>
<hr class="my-4">
<p>Einfach zu bedienen, vollständig lokal und sicher.</p>
</div>
</div>
</div>
<div class="row g-4">
<!-- Bilder zu PDF -->
<div class="col-md-6">
<div class="card h-100 feature-card">
<div class="card-body text-center">
<div class="icon-wrapper mb-3">
<i class="fas fa-images fa-3x text-primary"></i>
</div>
<h5 class="card-title">Bilder zu PDF</h5>
<p class="card-text">
Wählen Sie mehrere Bilder aus und konvertieren Sie diese
in eine einzige PDF-Datei. Unterstützt JPG, PNG, GIF und mehr.
</p>
<div class="features-list mb-3">
<small class="text-muted">
<i class="fas fa-check me-2"></i>Drag & Drop Support<br>
<i class="fas fa-check me-2"></i>Automatische Größenanpassung<br>
<i class="fas fa-check me-2"></i>Reihenfolge sortierbar
</small>
</div>
</div>
<div class="card-footer">
<a href="{{ url_for('images_to_pdf_page') }}" class="btn btn-primary w-100">
<i class="fas fa-arrow-right me-2"></i>Jetzt starten
</a>
</div>
</div>
</div>
<!-- PDF Tools -->
<div class="col-md-6">
<div class="card h-100 feature-card">
<div class="card-body text-center">
<div class="icon-wrapper mb-3">
<i class="fas fa-tools fa-3x text-success"></i>
</div>
<h5 class="card-title">PDF Tools</h5>
<p class="card-text">
Erweiterte PDF-Bearbeitungswerkzeuge für alle Ihre Anforderungen.
Zusammenführen, teilen und konvertieren.
</p>
<div class="features-list mb-3">
<small class="text-muted">
<i class="fas fa-check me-2"></i>PDFs zusammenführen<br>
<i class="fas fa-check me-2"></i>PDF zu Bildern<br>
<i class="fas fa-check me-2"></i>Seiten extrahieren
</small>
</div>
</div>
<div class="card-footer">
<a href="{{ url_for('pdf_tools_page') }}" class="btn btn-success w-100">
<i class="fas fa-arrow-right me-2"></i>Tools öffnen
</a>
</div>
</div>
</div>
</div>
<!-- Info Section -->
<div class="row mt-5">
<div class="col-12">
<div class="card bg-light">
<div class="card-body">
<h5 class="card-title">
<i class="fas fa-info-circle me-2"></i>Wie es funktioniert
</h5>
<div class="row">
<div class="col-md-4 text-center">
<div class="step-icon mb-3">
<i class="fas fa-upload fa-2x text-primary"></i>
</div>
<h6>1. Dateien hochladen</h6>
<p class="small text-muted">
Ziehen Sie Ihre Dateien einfach in den Upload-Bereich
oder klicken Sie zum Auswählen.
</p>
</div>
<div class="col-md-4 text-center">
<div class="step-icon mb-3">
<i class="fas fa-cogs fa-2x text-warning"></i>
</div>
<h6>2. Bearbeiten</h6>
<p class="small text-muted">
Sortieren Sie die Reihenfolge, wählen Sie Optionen
und starten Sie die Verarbeitung.
</p>
</div>
<div class="col-md-4 text-center">
<div class="step-icon mb-3">
<i class="fas fa-download fa-2x text-success"></i>
</div>
<h6>3. Herunterladen</h6>
<p class="small text-muted">
Laden Sie Ihre fertige PDF-Datei oder
das Ergebnis direkt herunter.
</p>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Features -->
<div class="row mt-5">
<div class="col-12">
<h3 class="text-center mb-4">Warum PDF Editor wählen?</h3>
<div class="row g-3">
<div class="col-md-3 text-center">
<i class="fas fa-shield-alt fa-2x text-primary mb-2"></i>
<h6>100% Lokal</h6>
<small class="text-muted">Ihre Dateien verlassen nie Ihren Computer</small>
</div>
<div class="col-md-3 text-center">
<i class="fas fa-bolt fa-2x text-warning mb-2"></i>
<h6>Schnell</h6>
<small class="text-muted">Keine Upload-Wartezeiten, sofortige Verarbeitung</small>
</div>
<div class="col-md-3 text-center">
<i class="fas fa-mobile-alt fa-2x text-success mb-2"></i>
<h6>Responsiv</h6>
<small class="text-muted">Funktioniert auf Desktop, Tablet und Handy</small>
</div>
<div class="col-md-3 text-center">
<i class="fas fa-code fa-2x text-info mb-2"></i>
<h6>Open Source</h6>
<small class="text-muted">Transparenter Code, den Sie verstehen können</small>
</div>
</div>
</div>
</div>
{% endblock %}