From ea76ff9a4f97ac367523811a6f6ee905b4594baa Mon Sep 17 00:00:00 2001 From: bernd Date: Tue, 29 Sep 2026 22:47:11 +0200 Subject: [PATCH] =?UTF-8?q?upload=20button=20eingef=C3=BCgt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- editor.py | 108 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 108 insertions(+) diff --git a/editor.py b/editor.py index dc98067..d8aa13d 100644 --- a/editor.py +++ b/editor.py @@ -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; }

Aufnahmen bearbeiten

+
+ + + MP3-Datei vom PC auswählen +
+
{% if recordings %} @@ -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 {}
DateinameGrößeDauer