Post-production for spoken-word recordings: several tracks, each needing its own cleanup, assembled in order, delivered at a specified loudness.
This is the application the rest of the stack exists to make possible. With AuK alone it is a shell script of thirty auk-infer calls, manual chunking around the thirty-second window, and no way to tell whether any of it worked.
{
"name": "episode-12",
"target_lufs": -16.0,
"true_peak_db": -1.0,
"gap_seconds": 0.4,
"fade_out": 0.3,
"tracks": [
{
"path": "raw/cold-open.wav",
"label": "cold open",
"request": "remove the noise and reverb, drop the breaths"
},
{
"path": "raw/host.wav",
"label": "host",
"request": "clean it up and calm the delivery",
"target_lufs": -18.0
},
{
"path": "raw/guest.wav",
"label": "guest",
"request": "clean it up and take the phone-line sound off",
"target_lufs": -18.0,
"lead_in": 0.5
}
],
"output": "episode-12.wav"
}openspeech studio episode.json -o build/[1/3] cold open
[2/3] host
[3/3] guest
episode-12: 3 tracks -> build/episode-12.wav
cold open 18.4s -> 18.4s -24.1 -> -16.0 LUFS [pass]
host 42.0s -> 42.0s -28.4 -> -18.0 LUFS [pass]
guest 36.0s -> 36.0s -31.2 -> -18.0 LUFS [warn]
warning: 2 chunk level corrections hit the 6 dB limit
master -16.00 LUFS, true peak -1.00 dBTP, 97.6s
YAML works too with the yaml extra; JSON needs nothing.
| Field | Default | Meaning |
|---|---|---|
name |
"production" |
Label used in reports |
tracks |
required | At least one track |
output |
"<name>.wav" |
Output filename |
target_lufs |
-16.0 |
Delivery loudness. -16 for podcasts, -23 for EBU R128 broadcast |
true_peak_db |
-1.0 |
True-peak ceiling in dBTP |
gap_seconds |
0.35 |
Silence inserted between consecutive tracks |
fade_in, fade_out |
0.0, 0.25 |
Fades on the assembled episode |
sample_rate |
highest input | Assembly rate |
seed |
none | Set for a reproducible run |
| Field | Default | Meaning |
|---|---|---|
path |
required | Relative to the recipe's directory, or absolute |
request |
none | What to do, in plain language. Omit to pass through untouched |
label |
filename stem | Name in reports |
lead_in |
0.0 |
Silence before this track |
target_lufs |
none | Per-track levelling, applied before assembly |
One quiet guest otherwise forces the master into a choice between an audible host and an inaudible answer. Levelling each track to a common target before assembly makes the final master a light touch rather than a rescue.
Set target_lufs slightly below the episode target (-18 against -16 is a reasonable start) so the master has headroom to work with.
- Per track — run its request through the full pipeline: planning, chunking, quality gates, repair.
- Per track — level to
target_lufsif one is set. - Assemble — reconcile sample rates, insert
lead_inandgap_seconds, concatenate. - Master — fades, then normalise the whole episode to
target_lufswith the true-peak ceiling held.
Loudness is measured on the finished assembly, which is the only place the number means anything: an edit after mastering invalidates it.
from openspeech.apps import load_recipe, run_production
from openspeech.engine import create_engine
result = run_production(load_recipe("episode.json"), create_engine("auto"), output_dir="build")
result.ok # every track processed without error or failure
result.master["output_lufs"] # measured, not requested
result.master["true_peak_dbfs"]
result.master["clipped_frames"]
for track in result.tracks:
print(track.label, track.verdict, track.input_lufs, "->", track.output_lufs)
for step in track.steps:
print(" ", step["op"], step["chunks"], step["verdict"])result.as_dict() is JSON-serialisable, so a production run can be archived alongside the audio and diffed against the next one.
A file that cannot be read is recorded as an error on that track; the remaining tracks still produce an episode, and result.ok is False so a build script can decide what to do. Losing three good tracks because the fourth was misnamed is not a useful behaviour.
Set seed in the recipe and the same recipe against the same inputs produces the same output. Worth doing for anything you might need to rebuild.