Add pipeline code

This commit is contained in:
Grok Bot
2026-10-17 15:27:14 -05:00
parent 3cd5a4c658
commit 5f0e2a9768
21 changed files with 1612 additions and 0 deletions
+106
View File
@@ -0,0 +1,106 @@
"""Deterministic ACX checker (R13). Measures each MP3 and prints a pass/fail table. Exit code 1 if anything fails.
Checks per file: MP3 CBR bitrate, sample rate, channels, RMS, peak, noise floor, room tone head and tail, duration.
Checks per set: all files mono or all stereo; each script has an MP3; chapter scripts start with a spoken header."""
import glob
import json
import math
import os
import subprocess
import audiomeasure as am
def packet_sizes(path):
out = subprocess.run(["ffprobe", "-v", "error", "-select_streams", "a:0", "-show_entries", "packet=size", "-of", "default=nw=1:nk=1", path],
capture_output=True, text=True, check=True).stdout.split()
return [int(x) for x in out]
def measure(cfg, path):
"""All numbers for one file, as a dict."""
a = cfg["acx"]
info = am.probe(path)
w = am.windows(path)
rms = [r for r, _ in w]
win_s = 0.05
loud = [i for i, r in enumerate(rms) if r > a["speech_window_db"]]
sizes = packet_sizes(path)
blocks = [am.db_mean_power(rms[i:i + 10]) for i in range(0, len(rms) - 9, 10)]
return {
"codec": info["codec_name"],
"sample_rate": int(info["sample_rate"]),
"channels": int(info["channels"]),
"bitrate_kbps": round(int(info["bit_rate"]) / 1000),
"cbr": max(sizes) - min(sizes) <= 1,
"duration_s": float(info["duration"]),
"rms_db": am.db_mean_power(rms),
"peak_db": max(p for _, p in w),
"noise_floor_db": min(blocks) if blocks else float("nan"),
"head_s": loud[0] * win_s if loud else float("nan"),
"tail_s": (len(rms) - 1 - loud[-1]) * win_s if loud else float("nan"),
}
def judge(cfg, name, m):
"""List of (check name, value text, passed)."""
a = cfg["acx"]
retail = os.path.basename(name).startswith("retail-sample")
dur_ok = a["retail_min_s"] <= m["duration_s"] <= a["retail_max_s"] if retail else m["duration_s"] < a["max_minutes"] * 60
ok = lambda v, lo, hi: (not math.isnan(v)) and lo <= v <= hi
return [
("format", "%s %s %dk %d Hz" % (m["codec"], "CBR" if m["cbr"] else "VBR", m["bitrate_kbps"], m["sample_rate"]),
m["codec"] == "mp3" and m["cbr"] and m["bitrate_kbps"] >= a["min_bitrate_kbps"] and m["sample_rate"] == a["sample_rate"]),
("channels", str(m["channels"]), m["channels"] in (1, 2)),
("rms dB", "%.1f" % m["rms_db"], ok(m["rms_db"], a["rms_min_db"], a["rms_max_db"])),
("peak dB", "%.1f" % m["peak_db"], m["peak_db"] <= a["peak_max_db"]),
("noise floor dB", "%.1f" % m["noise_floor_db"], m["noise_floor_db"] <= a["noise_floor_max_db"]),
("head s", "%.2f" % m["head_s"], ok(m["head_s"], a["head_min_s"], a["head_max_s"])),
("tail s", "%.2f" % m["tail_s"], ok(m["tail_s"], a["tail_min_s"], a["tail_max_s"])),
("length", "%.1f s" % m["duration_s"], dur_ok),
]
def set_checks(cfg, folder, measured):
"""Checks across files; folder is an audio/ folder next to script/ (or None)."""
rows = []
chans = {m["channels"] for m in measured.values()}
rows.append(("all files mono or all stereo", ",".join(map(str, sorted(chans))), len(chans) <= 1))
scripts = os.path.join(os.path.dirname(folder), "script") if folder else None
if scripts and os.path.isdir(scripts):
for sp in sorted(glob.glob(os.path.join(scripts, "*.json"))):
stem = os.path.splitext(os.path.basename(sp))[0]
rows.append(("mp3 exists for script " + stem, "yes" if stem in {os.path.splitext(os.path.basename(n))[0] for n in measured} else "no",
stem in {os.path.splitext(os.path.basename(n))[0] for n in measured}))
if stem.startswith("chapter-"):
with open(sp, encoding="utf-8") as f:
first = json.load(f)[0]["kind"]
rows.append(("spoken section header in " + stem, first, first == "header"))
return rows
def run(cfg, paths):
files, folder = [], None
for p in paths:
if os.path.isdir(p):
folder = os.path.abspath(p)
files += sorted(glob.glob(os.path.join(p, "*.mp3")))
else:
files.append(p)
if not files:
print("no MP3 files found")
return 1
measured, failed = {}, False
for f in files:
m = measure(cfg, f)
measured[f] = m
print("\n%s" % f)
print(" %-16s %-26s %s" % ("check", "value", "result"))
for name, value, passed in judge(cfg, f, m):
failed |= not passed
print(" %-16s %-26s %s" % (name, value, "PASS" if passed else "FAIL"))
print("\nSet checks")
for name, value, passed in set_checks(cfg, folder, measured):
failed |= not passed
print(" %-40s %-10s %s" % (name, value, "PASS" if passed else "FAIL"))
print("\nOVERALL: %s" % ("FAIL" if failed else "PASS"))
return 1 if failed else 0