From e1dcf9c49dc1846992bcca382f04d4e82245313d Mon Sep 17 00:00:00 2001 From: bernd Date: Tue, 29 Sep 2026 11:05:10 +0200 Subject: [PATCH] recorder.py aktualisiert --- recorder.py | 91 +++++++++++++++++++++++++---------------------------- 1 file changed, 42 insertions(+), 49 deletions(-) diff --git a/recorder.py b/recorder.py index 36ea024..1c40b9d 100644 --- a/recorder.py +++ b/recorder.py @@ -39,6 +39,10 @@ RECORDINGS_DIR = Path( os.environ.get("RECORDINGS_DIR", "/recordings") ) +FINISHED_DIR = Path( + os.environ.get("FINISHED_DIR", "/finished") +) + WEB_PORT = int( os.environ.get("WEB_PORT", "8080") ) @@ -133,8 +137,8 @@ def format_duration(seconds): return f"{minutes}:{seconds:02d}" -def get_duration(filename): - path = RECORDINGS_DIR / filename +def get_duration(filename, directory=RECORDINGS_DIR): + path = directory / filename try: result = subprocess.run( @@ -285,55 +289,54 @@ def start_recording(): def normalize_recording(filename): - """Normalize a completed MP3 and replace it only after success.""" - if not NORMALIZE: - return - + """Normalize a completed MP3 in place in RECORDINGS_DIR.""" source = RECORDINGS_DIR / filename if not source.exists(): - return + return False + + if not NORMALIZE: + print(f"[normalize] disabled: {filename}", flush=True) + return True temp = RECORDINGS_DIR / (filename + ".normalizing.mp3") cmd = [ - "ffmpeg", - "-hide_banner", - "-loglevel", "warning", - "-y", + "ffmpeg", "-hide_banner", "-loglevel", "warning", "-y", "-i", str(source), - "-af", - f"loudnorm=I={NORMALIZE_TARGET}:TP={NORMALIZE_TRUE_PEAK}:LRA={NORMALIZE_LRA}", + "-af", ( + f"loudnorm=I={NORMALIZE_TARGET}:" + f"TP={NORMALIZE_TRUE_PEAK}:" + f"LRA={NORMALIZE_LRA}" + ), "-c:a", "libmp3lame", "-b:a", "128k", str(temp), ] + print(f"[normalize] starting: {filename}", flush=True) try: result = subprocess.run( cmd, - stdout=subprocess.DEVNULL, - stderr=subprocess.PIPE, + capture_output=True, text=True, - check=False, + timeout=1800, ) - - if result.returncode != 0 or not temp.exists() or temp.stat().st_size == 0: + if result.returncode != 0: print( - f"[normalize] failed for {filename}: " - f"{result.stderr.strip()[-1000:]}", + f"[normalize] ffmpeg failed ({result.returncode}): " + f"{result.stderr[-2000:]}", flush=True, ) temp.unlink(missing_ok=True) - return + return False - # Only replace the original after a complete successful conversion. temp.replace(source) print(f"[normalize] completed: {filename}", flush=True) - + return True except Exception as exc: - print(f"[normalize] exception for {filename}: {exc}", flush=True) + print(f"[normalize] error: {exc}", flush=True) temp.unlink(missing_ok=True) - + return False def stop_recording(): global ffmpeg_process @@ -378,10 +381,9 @@ def stop_recording(): recording_filename = None recording_started = None - # The live recording is now complete. Normalize it before it appears - # as the finished recording in the web UI. - if filename and NORMALIZE: - normalizing_filename = filename + # The live recording is now complete. Normalize it in RECORDINGS_DIR. + if filename: + normalizing_filename = filename if NORMALIZE else None try: normalize_recording(filename) finally: @@ -763,14 +765,14 @@ a.button:hover { Download @@ -848,36 +850,22 @@ def render_index(): files = [] - RECORDINGS_DIR.mkdir( - parents=True, - exist_ok=True, - ) + FINISHED_DIR.mkdir(parents=True, exist_ok=True) - for path in RECORDINGS_DIR.glob("*.mp3"): + for path in FINISHED_DIR.glob("*.mp3"): try: stat = path.stat() - - # Skip the currently active file from the finished list. - if path.name == recording_filename: - continue - files.append({ "filename": path.name, "datetime": format_datetime(stat.st_mtime), "size": format_size(stat.st_size), - "duration": format_duration( - get_duration(path.name) - ), + "duration": format_duration(get_duration(path.name, FINISHED_DIR)), "mtime": stat.st_mtime, }) - except FileNotFoundError: continue - files.sort( - key=lambda item: item["mtime"], - reverse=True, - ) + files.sort(key=lambda item: item["mtime"], reverse=True) with state_lock: process = ffmpeg_process @@ -926,6 +914,11 @@ def api_status(): return render_index() +@app.route("/finished/") +def finished(filename): + return send_from_directory(FINISHED_DIR, filename, as_attachment=False) + + @app.route("/recordings/") def recording(filename): path = RECORDINGS_DIR / filename