upload button eingefügt

This commit is contained in:
2026-09-29 22:47:11 +02:00
parent 52c0a68549
commit ea76ff9a4f
+108
View File
@@ -41,6 +41,8 @@ FILE_OBSERVER = None
# ---------------------------------------------------------------------------
app = Flask(__name__)
MAX_UPLOAD_MB = int(os.environ.get("MAX_UPLOAD_MB", "2048"))
app.config["MAX_CONTENT_LENGTH"] = MAX_UPLOAD_MB * 1024 * 1024
# ---------------------------------------------------------------------------
@@ -1070,6 +1072,12 @@ h1 { margin:0 0 6px; }
.filter-bar input, .filter-bar select { width:100%; margin-top:5px; padding:9px; background:#111; color:#eee; border:1px solid #555; border-radius:5px; font-size:15px; }
.filter-bar .search { grid-column:auto; }
.filter-info { color:#999; font-size:13px; margin-bottom:10px; }
.upload-bar { display:flex; align-items:center; gap:10px; flex-wrap:wrap; margin-bottom:16px; }
.upload-info { color:#999; font-size:13px; }
.upload-progress { display:none; flex:1 1 240px; min-width:180px; height:18px; background:#292929; border-radius:9px; overflow:hidden; }
.upload-progress.active { display:block; }
.upload-progress-bar { width:0; height:100%; background:#1769aa; transition:width .15s ease; }
table { width:100%; border-collapse:collapse; }
th,td { padding:10px 8px; border-bottom:1px solid #333; text-align:left; }
th { color:#aaa; font-weight:normal; }
@@ -1100,6 +1108,12 @@ th { color:#aaa; font-weight:normal; }
<div class="section">
<h2>Aufnahmen bearbeiten</h2>
<div class="upload-bar">
<input type="file" id="uploadFile" accept="audio/mpeg,.mp3" hidden>
<button class="button" type="button" onclick="document.getElementById('uploadFile').click()">⬆ Vortrag hochladen</button>
<span class="upload-info" id="uploadInfo">MP3-Datei vom PC auswählen</span>
<div class="upload-progress" id="uploadProgress"><div class="upload-progress-bar" id="uploadProgressBar"></div></div>
</div>
{% if recordings %}
<table>
<thead><tr><th>Dateiname</th><th>Größe</th><th>Dauer</th><th></th></tr></thead>
@@ -1255,6 +1269,66 @@ async function refreshFileLists() {
}
}
const uploadFile = document.getElementById('uploadFile');
const uploadInfo = document.getElementById('uploadInfo');
const uploadProgress = document.getElementById('uploadProgress');
const uploadProgressBar = document.getElementById('uploadProgressBar');
uploadFile?.addEventListener('change', () => {
const file = uploadFile.files?.[0];
if (!file) return;
if (!file.name.toLowerCase().endsWith('.mp3')) {
alert('Bitte eine MP3-Datei auswählen.');
uploadFile.value = '';
return;
}
const formData = new FormData();
formData.append('file', file);
const xhr = new XMLHttpRequest();
uploadProgress.classList.add('active');
uploadProgressBar.style.width = '0%';
uploadInfo.textContent = `Upload: ${file.name}`;
xhr.open('POST', '/api/upload');
xhr.upload.addEventListener('progress', event => {
if (!event.lengthComputable) return;
const percent = Math.round(event.loaded / event.total * 100);
uploadProgressBar.style.width = percent + '%';
uploadInfo.textContent = `Upload: ${file.name} – ${percent} %`;
});
xhr.addEventListener('load', () => {
let result = {};
try { result = JSON.parse(xhr.responseText); } catch (_) {}
if (xhr.status >= 200 && xhr.status < 300 && result.success) {
uploadProgressBar.style.width = '100%';
uploadInfo.textContent = `${result.filename} wurde hochgeladen.`;
setTimeout(() => location.reload(), 500);
} else {
uploadProgress.classList.remove('active');
uploadInfo.textContent = 'Upload fehlgeschlagen';
alert(result.error || `Upload fehlgeschlagen (HTTP ${xhr.status})`);
}
});
xhr.addEventListener('error', () => {
uploadProgress.classList.remove('active');
uploadInfo.textContent = 'Upload fehlgeschlagen';
alert('Netzwerkfehler beim Upload.');
});
xhr.addEventListener('abort', () => {
uploadProgress.classList.remove('active');
uploadInfo.textContent = 'Upload abgebrochen';
});
xhr.send(formData);
});
const events = new EventSource('/api/events');
events.onmessage = () => refreshFileLists();
events.onerror = () => { /* EventSource reconnects automatically. */ };
@@ -1484,6 +1558,40 @@ def finished_file(filename):
return send_from_directory(FINISHED_DIR, path.name, mimetype="audio/mpeg", as_attachment=False)
@app.route("/api/upload", methods=["POST"])
def api_upload():
upload = request.files.get("file")
if upload is None or not upload.filename:
return jsonify({"success": False, "error": "Keine Datei ausgewählt"}), 400
original_name = Path(upload.filename).name
if original_name != upload.filename:
return jsonify({"success": False, "error": "Ungültiger Dateiname"}), 400
if not original_name.lower().endswith(".mp3"):
return jsonify({"success": False, "error": "Bitte eine MP3-Datei auswählen"}), 400
filename = original_name
target = RECORDINGS_DIR / filename
if target.exists():
return jsonify({"success": False, "error": f"Eine Datei mit dem Namen '{filename}' existiert bereits"}), 409
temp = RECORDINGS_DIR / f".{filename}.{uuid.uuid4().hex}.uploading"
try:
RECORDINGS_DIR.mkdir(parents=True, exist_ok=True)
upload.save(temp)
os.replace(temp, target)
print(f"[upload] Uploaded recording: {filename}", flush=True)
return jsonify({"success": True, "filename": filename})
except OSError as exc:
try:
temp.unlink(missing_ok=True)
except OSError:
pass
print(f"[upload] upload failed for {filename}: {exc}", flush=True)
return jsonify({"success": False, "error": "Upload konnte nicht gespeichert werden"}), 500
@app.route("/api/file", methods=["POST"])
def api_file():
data = request.get_json(silent=True) or {}