Voice is POST /v1/audio/speech and word timings come from POST /v1/audio/transcriptions over the rendered audio. Music, sound effects, images, and video generate through /v1/audio/music, /v1/audio/foley, /v1/images/generations, and /v1/videos/generations. The shared catalog is a listing under media/<type>/ in our own object store, browsed through /v1/s3 — an empty prefix reports itself as empty rather than reading as a bad query. One client (scripts/lib/api.mjs) holds every route and the one credential: a Hanzo IAM token in $HANZO_API_KEY, read from Hanzo KMS. No credential file, no per-repo .env, no per-provider CLI to install or keep current. $HANZO_BASE_URL points the same code at a local Hanzo Engine. Word timings are passed through, never interpolated: a caption cut on invented boundaries reads as correct and drifts against the voice. When the service returns text without them, the word array is empty and the caller is told. Deletes the provider cascade this replaces, its model ladders, and the account linking in telemetry — the install id stands alone.
4.3 KiB
Background music (BGM)
One music bed per composition, produced by the shared audio engine
(scripts/audio.mjs -> scripts/lib/bgm.mjs). Two routes:
- catalog — the closest name under
media/bgm/in the shared catalog, downloaded toassets/bgm/track.<ext>. Free, instant, identical every run. - compose —
POST /v1/audio/musicwith a mood prompt, written toassets/bgm/track.wav. Used when the catalog cannot answer.
Both are synchronous: when the engine returns, the file is on disk.
Driving it from the request
audio_request.json -> bgm: { mode?, query?, prompt? }:
mode—catalog | compose | none. Omit for auto (catalog, then compose). An explicitcatalogis honored as written: an empty or unmatched shelf is reported and BGM is skipped, never quietly composed instead.query— the mood. It is the catalog query and the fallback prompt seed (a storyboard'smusic:field, falling back tomessage->arc->"calm cinematic underscore").prompt— an explicit full prompt for compose; omit it and the engine infers one. Optionalblob/archetype/arcfeed that inference.
The catalog rung
The catalog is a listing under media/bgm/, browsed through
GET /v1/s3/buckets/media/objects. An object key IS its description — a
file named calm-cinematic-underscore.mp3 answers "calm cinematic underscore" —
so stock it with names written in the words a brief would use.
The catalog is empty until someone stocks it. That is reported, not hidden:
an empty prefix logs the catalog holds no music yet (bgm/) and, under auto,
falls through to compose.
The cue written to audio_meta.json:
{
"path": "assets/bgm/track.mp3",
"volume": 0.12,
"mode": "catalog",
"query": "calm cinematic underscore",
"key": "bgm/calm-cinematic-underscore.mp3",
}
volume comes from bgmDefaultVolume(): BGM_BED_VOLUME (0.12, about -18 dB —
a bed under the voice) under narration, BGM_SILENT_VOLUME (0.9) for a silent
film. Tune those constants in scripts/lib/bgm.mjs, not at call sites. An
explicit volume in audio_meta.json always wins.
The compose rung
POST /v1/audio/music with the inferred (or given) prompt and the total voice
duration. Output goes to assets/bgm/track.wav.
Mood inference (the compose prompt)
inferBgmPrompt() in scripts/lib/bgm.mjs: an explicit prompt wins;
otherwise industry-keyword base -> narrative-archetype shape ->
emotional-arc tiebreaker.
Match in blob / query |
Base prompt | BPM |
|---|---|---|
crypto / nft / web3 / defi / token / blockchain |
atmospheric electronic, deep bass, futuristic synths, restrained percussion | 100 |
finance / fintech / bank / payment / invest / wealth |
calm cinematic, soft strings, subtle piano, restrained percussion | 92 |
creative / agency / design / studio / art / brand |
playful electronic, warm pads, light percussion | 115 |
| (default: SaaS / tech / platform) | uplifting corporate tech, bright modern piano with synth pads | 108 |
Archetype then reshapes the arc — PAS -> "MINOR to MAJOR" build; BAB / future-pacing -> aspirational rising; feature-cascade -> +10 BPM driving; demo-loop -> -8 BPM minimal. The emotional arc breaks remaining ties (tension->relief, excitement, trust/reassurance).
Failure modes
| Failure | Behavior |
|---|---|
| Empty catalog, auto mode | Anomaly logged, composes instead. |
| Empty catalog, explicit catalog | Anomaly logged, bgm: null. Render proceeds without music. |
| No catalog match | Same as above — reported, then composed or skipped. |
| No credential | bgm: null, anomaly logged. |
| The music call fails | bgm: null, anomaly names the HTTP status. |
BGM failure never blocks a render.