recorder.py aktualisiert
This commit is contained in:
+42
-49
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user