Files
Aime/app/lab/recode-cbr.sh
schakal00783andClaude Opus 5 d9554c590c fix(audio): Wiedergabe-MP3 als CBR ohne Xing-Tabelle -- Wortmarkierung war 28 s voraus
Die Markierung im Transkript stand auf einem Wort, gesprochen wurde eins vier
Zeilen weiter oben (gemessen an #85: 24 s Vorlauf).

Nicht die Ursache -- jeweils per Gegenprobe ausgeschlossen: die Wortzeiten
(Ausschnitte bei 600/1240/3400 s einzeln transkribiert, enthalten exakt die
Wörter der DB), MP3- vs WAV-Zeitachse (identisch), ffmpeg-Seek (trifft),
ontimeupdate-Logik (korrekt).

Ursache war der Sprung im Browser: in einer VBR-Datei gibt es keine feste
Byte-pro-Sekunde-Regel, also nutzt er die Xing-Tabelle -- 100 Stützstellen,
bei 63 Minuten ein 38-s-Raster. Er landet auf der Stützstelle davor, meldet
über currentTime aber die angefragte Zeit. Nachgerechnet: Ziel 20:40 spielt
ab 20:11,5 (-28,5 s).

Fix: -b:a 64k -ar 48000 -ac 1 -write_xing 0 statt -qscale:a 3. Byte-Position
proportional zur Zeit. Die Tabelle muss auch bei CBR weg, sie rastert auf
1/256 der Dateigröße (~15 s pro Stunde, Restfehler bis -6,4 s gemessen). Ohne
sie rechnet der Decoder linear: Restfehler 0,02 s an neun Stichpunkten.

lab/recode-cbr.sh schreibt den Altbestand einmalig um (Quelle WAV, sonst die
MP3 selbst), lab/tocerr.py ist das Messwerkzeug dahinter.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-02 23:40:29 +02:00

68 lines
3.0 KiB
Bash

#!/bin/bash
# Altbestand von VBR- auf CBR-MP3 umstellen.
#
# Warum: der Browser springt in einer VBR-Datei über die Xing-Tabelle (100
# Stützstellen — bei einer Stunde Audio ein 36-Sekunden-Raster) und landet
# systematisch zu früh, meldet aber die angefragte Zeit. Genau daher lief die
# Wortmarkierung im Transkript dem Ton voraus. Bei CBR ist die Byte-Position
# proportional zur Zeit, jeder Sprung sitzt. Die Xing-Tabelle muss dabei WEG
# (-write_xing 0): sie speichert Byte-Positionen nur in 1/256 der Dateigröße,
# das sind bei einer Stunde Audio ~15 s Rundung. Ohne sie rechnet der Decoder
# linear — bei CBR nachgemessen auf 0,02 s genau.
#
# Quelle ist die WAV, wo es sie noch gibt (gleiche Filterkette wie beim
# Aufnehmen). Für die alten Aufnahmen ohne WAV bleibt nur die MP3 selbst —
# dann OHNE Filter, die stecken dort ja schon drin.
#
# `-f mp3` ist Pflicht: die Zieldatei heisst *.mp3.tmp, und aus dieser Endung
# leitet ffmpeg kein Format ab ("Unable to choose an output format").
#
# Sicherheit: erst in eine .tmp schreiben, Dauer gegen das Original prüfen
# (±2 s), dann erst ersetzen. Ein Abbruch lässt die alte Datei unangetastet.
set -u
cd /opt/callrec/recordings || exit 1
MAXTEMP=88000 # °C*1000 — darüber wird pausiert (kein aktiver Lüfter)
ok=0; skip=0; fail=0
dur() { ffprobe -v error -show_entries format=duration -of default=nw=1:nk=1 "$1" 2>/dev/null; }
for m in rec_*.mp3; do
[ -f "$m" ] || continue
# schon fertig = 64 kbit konstant UND ohne Xing-Tabelle (die ist selbst bei
# CBR nur auf 1/256 der Dateigröße genau, bei einer Stunde also ~15 s).
if ffprobe -v error -show_entries stream=bit_rate -of default=nw=1:nk=1 "$m" 2>/dev/null | grep -q '^64000$' && ! head -c 2048 "$m" | grep -qa Xing; then
skip=$((skip+1)); continue
fi
# Thermik: das Board hat keinen Lüfter, also vor jeder Datei kurz nachsehen.
while :; do
t=$(cat /sys/class/thermal/thermal_zone0/temp 2>/dev/null || echo 0)
[ "$t" -lt "$MAXTEMP" ] && break
echo " … $((t/1000)) °C, warte 30 s"; sleep 30
done
w="${m%.mp3}.wav"
d0=$(dur "$m")
if [ -f "$w" ]; then
nice -n 10 ffmpeg -v error -y -i "$w" \
-af 'highpass=f=80,loudnorm=I=-16:TP=-1.5:LRA=11' \
-codec:a libmp3lame -b:a 64k -ar 48000 -ac 1 -write_xing 0 -f mp3 "$m.tmp" </dev/null
else
nice -n 10 ffmpeg -v error -y -i "$m" \
-codec:a libmp3lame -b:a 64k -ar 48000 -ac 1 -write_xing 0 -f mp3 "$m.tmp" </dev/null
fi
rc=$?
d1=$(dur "$m.tmp")
if [ $rc -ne 0 ] || [ -z "$d1" ]; then
echo "FEHLER $m (ffmpeg rc=$rc)"; rm -f "$m.tmp"; fail=$((fail+1)); continue
fi
diff=$(awk -v a="$d0" -v b="$d1" 'BEGIN{d=a-b; if(d<0)d=-d; print (d>2)?"1":"0"}')
if [ "$diff" = "1" ]; then
echo "FEHLER $m: Dauer weicht ab ($d0 -> $d1)"; rm -f "$m.tmp"; fail=$((fail+1)); continue
fi
chown callrec:callrec "$m.tmp"
mv -f "$m.tmp" "$m"
ok=$((ok+1))
echo "ok $m ($d0 s, Quelle: $([ -f "$w" ] && echo wav || echo mp3))"
done
echo "fertig: $ok neu kodiert, $skip schon CBR, $fail fehlgeschlagen"