Skip to content
FX fx-dub
Open source · zero dependencies

Your dub passed every check and still said the wrong line.

48 kHz, exact duration, textbook −18 LUFS — and a line you never wrote, in a voice that is not your character’s, with a two-second hole in the middle. fx-dub gives you two receipts: one for the container, one for what was actually said.

Install

pip install fx-dub

Check the content

fxdub-dialogue scene.json words.json --only-speaker VOICE

Check the container

fxdub-receipt runs/2026-08-22-final --json receipt.json

Two receipts, because one is not enough

Sample rate and duration cannot see a model inventing dialogue. Both of the takes that made this tool necessary passed every container check.

Content, not just container

Every scripted line present and in order, no invented speech, no cross-character overlap, no mid-line pause that eats the next cue, one voice per character.

The scene script is the contract

Dialogue, cast, clip duration and per-line delivery direction live as data. A director’s phrasing note travels with the number it justifies.

Per-character stems

--only-speaker narrows the contract to one character, so a stem is checked for their lines and silence everywhere else. Checking against the whole scene hides exactly that bug.

Standards, not vibes

EBU R128 loudness, ATSC A/85 dialogue anchoring, JAES ducking-depth research. Every check cites the standard or the measured defect it traces to.

No network, no dependencies

Zero runtime dependencies and no HTTP client — CI fails the build if that ever changes. FLAC and MP4 headers are decoded with the standard library, not by shelling out to ffprobe.

Exit codes CI can act on

0 pass · 1 the audio failed its contract, read the receipt · 2 the tool could not run. Errors carry code, message and hint — never a raw traceback.

Usage

Install

pip install fx-dub

Check what a take actually says

$ fxdub-dialogue docs/scenes/night-street.json words.json --only-speaker VOICE
9/10 checks pass
| PASS | line_present:0:VOICE    | Hey, how’s it going?
| FAIL | no_invented_speech      | 4 unscripted word(s): not bad can’t complain
| PASS | no_overlap              | clean
| PASS | one_voice_per_character | clean

A scene script

{
  "clip_duration_s": 10.062,
  "lines": [
    { "speaker": "VOICE", "text": "Hey, how’s it going?" },
    { "speaker": "MAC",   "text": "Not bad. Can’t complain.",
      "max_gap_s": 0.15,
      "direction": "There’s no pause in between." },
    { "speaker": "VOICE", "text": "Good to hear, good to hear." }
  ]
}

Check the delivered run

$ fxdub-receipt runs/2026-08-22-v28 --bed-gain-db -12
19/19 checks passed.
| PASS | loudness:mix_target       | -18.09 LUFS (target -18.0 +/- 2.0)
| PASS | loudness:dialogue_anchored| VO - bed = +11.17 LU (expect 8-20)
| PASS | dub:frames_intact         | 161 frames

Build a graph without submitting it

from fxdub import vo_graphs

graph = vo_graphs.transcribe(".flac", "run/words")
# API-format dict, ready for your own submit path.
# Nothing is sent, uploaded, or spent from here.