recorder.py aktualisiert

This commit is contained in:
2026-09-29 11:05:10 +02:00
parent e78a406bcb
commit e1dcf9c49d
+42 -49
View File
@@ -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 {
<td>
<audio controls preload="none">
<source src="/recordings/{{ item.filename }}"
<source src="/finished/{{ item.filename }}"
type="audio/mpeg">
</audio>
</td>
<td>
<a class="button"
href="/recordings/{{ item.filename }}"
href="/finished/{{ item.filename }}"
download>
Download
</a>
@@ -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/<path:filename>")
def finished(filename):
return send_from_directory(FINISHED_DIR, filename, as_attachment=False)
@app.route("/recordings/<path:filename>")
def recording(filename):
path = RECORDINGS_DIR / filename