From cfeb05befa43663fb5cbe74164670498f25194ad Mon Sep 17 00:00:00 2001 From: bernd Date: Tue, 29 Sep 2026 09:09:08 +0200 Subject: [PATCH] =?UTF-8?q?umbenennen=20und=20l=C3=B6schen=20hinzugef?= =?UTF-8?q?=C3=BCgt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- editor.py | 168 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 165 insertions(+), 3 deletions(-) diff --git a/editor.py b/editor.py index 36a9d09..3aa587d 100644 --- a/editor.py +++ b/editor.py @@ -64,6 +64,77 @@ def safe_filename(filename): return filename +def clean_new_filename(filename): + """Validate and normalize a user-supplied MP3 filename.""" + if not isinstance(filename, str): + return None + filename = filename.strip() + if not filename: + return None + if Path(filename).name != filename: + return None + if filename.lower().endswith(".mp3"): + filename = filename[:-4] + if not filename or filename in {".", ".."}: + return None + if any(ch in filename for ch in '/\\\x00'): + return None + return filename + ".mp3" + + +def directory_for_kind(kind): + if kind == "recording": + return RECORDINGS_DIR + if kind == "finished": + return FINISHED_DIR + return None + + +def path_for_kind(kind, filename): + filename = safe_filename(filename) + directory = directory_for_kind(kind) + if filename is None or directory is None: + return None + return directory / filename + + +def rename_file(kind, old_filename, new_filename): + old_path = path_for_kind(kind, old_filename) + new_filename = clean_new_filename(new_filename) + if old_path is None or new_filename is None or not old_path.is_file(): + raise ValueError("Ungültiger Dateiname") + + new_path = old_path.with_name(new_filename) + if new_path.exists(): + raise ValueError("Eine Datei mit diesem Namen existiert bereits") + + old_waveform = waveform_path(old_path.name) if kind == "recording" else None + new_waveform = waveform_path(new_path.name) if kind == "recording" else None + + old_path.rename(new_path) + if old_waveform and old_waveform.is_file(): + try: + old_waveform.rename(new_waveform) + except OSError: + # The audio file was renamed successfully; an old waveform cache + # is harmless and will be regenerated under the new name. + pass + return new_path + + +def delete_file(kind, filename): + path = path_for_kind(kind, filename) + if path is None or not path.is_file(): + raise ValueError("Datei nicht gefunden") + + path.unlink() + if kind == "recording": + try: + waveform_path(path.name).unlink(missing_ok=True) + except OSError: + pass + + def source_path(filename): filename = safe_filename(filename) if filename is None: @@ -221,6 +292,9 @@ h1 { margin:0 0 6px; font-size:28px; } .controls { display:flex; flex-wrap:wrap; gap:8px; align-items:center; margin-top:18px; } button, a.button { border:0; border-radius:6px; padding:11px 16px; background:#333; color:#fff; cursor:pointer; text-decoration:none; font-size:15px; } button:hover, a.button:hover { background:#444; } +.button.danger { background:#4a2020; } +.button.danger:hover { background:#6a2b2b; } +.actions { display:flex; gap:6px; flex-wrap:wrap; } button.primary { background:#1769aa; } button.danger { background:#8b0000; } button:disabled { opacity:.45; cursor:not-allowed; } @@ -289,8 +363,9 @@ button:disabled { opacity:.45; cursor:not-allowed; }
Original herunterladen +
-
Die Originalaufnahme bleibt unverändert. Aus dem gewählten Bereich wird eine fertige Aufnahme im separaten Ausgabeordner erstellt.
+
Beim Erstellen einer fertigen Aufnahme bleibt das Original unverändert. Das Original kann hier bewusst gelöscht werden; bereits erstellte fertige Aufnahmen bleiben dabei erhalten.
@@ -384,6 +459,23 @@ function setEndAtCurrent() { function msg(text, ok) { const el=document.getElementById('message'); el.textContent=text; el.className='message '+(ok?'ok':'error'); } +async function deleteOriginal() { + if (!confirm('Originalaufnahme wirklich löschen?\n\n' + filename + '\n\nBereits erstellte fertige Aufnahmen bleiben erhalten.')) return; + + audio.pause(); + const response = await fetch('/api/file', { + method: 'POST', + headers: {'Content-Type': 'application/json'}, + body: JSON.stringify({action: 'delete', kind: 'recording', filename}) + }); + const result = await response.json(); + if (!result.success) { + alert(result.error || 'Löschen fehlgeschlagen.'); + return; + } + location.href = '/'; +} + async function trim() { if (end-start < 0.5) return msg('Der ausgewählte Bereich ist zu kurz.',false); if (!confirm(`Aufnahme auf ${fmt(start)} bis ${fmt(end)} kürzen?`)) return; @@ -449,7 +541,11 @@ th { color:#aaa; font-weight:normal; } {{ item.datetime }} {{ item.size }} {{ item.duration }} -✂ Bearbeiten + + ✂ Bearbeiten + + + {% endfor %} @@ -469,7 +565,11 @@ th { color:#aaa; font-weight:normal; } {{ item.datetime }} {{ item.size }} {{ item.duration }} -▶ Abspielen / Download + + ▶ Abspielen / Download + + + {% endfor %} @@ -479,6 +579,43 @@ th { color:#aaa; font-weight:normal; } @@ -583,6 +720,31 @@ def finished_file(filename): return send_from_directory(FINISHED_DIR, path.name, mimetype="audio/mpeg", as_attachment=False) +@app.route("/api/file", methods=["POST"]) +def api_file(): + data = request.get_json(silent=True) or {} + action = data.get("action") + kind = data.get("kind") + filename = data.get("filename") + + if kind not in {"recording", "finished"}: + return jsonify({"success": False, "error": "Ungültiger Dateityp"}), 400 + + try: + if action == "rename": + path = rename_file(kind, filename, data.get("new_filename")) + return jsonify({"success": True, "filename": path.name}) + if action == "delete": + delete_file(kind, filename) + return jsonify({"success": True}) + return jsonify({"success": False, "error": "Ungültige Aktion"}), 400 + except ValueError as exc: + return jsonify({"success": False, "error": str(exc)}), 400 + except OSError as exc: + print(f"[file] operation failed for {filename}: {exc}", flush=True) + return jsonify({"success": False, "error": "Dateioperation fehlgeschlagen"}), 500 + + @app.route("/api/edit", methods=["POST"]) def api_edit(): data = request.get_json(silent=True) or {}