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>
68 lines
3.0 KiB
Bash
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"
|