Skip to main content

braidio

Weave commentary — solo, duo, panel, debate, interview, documentary — with source clips into a production.

braidio braids the strands of a piece that talks about something (a song, a book, a film, an event) into one rendered production. The talk is the spine — a single presenter, two hosts, a panel, a narrator — and source clips and narration bridges are the "illustration" layers woven onto it. Audio today, with visual support to follow (it braids A/V — braid-io, and it sounds like radio).

Two things braidio gives you:

  1. Ready-made format templates under the standard, industry names people already recognize — Deep Dive, Song Exploder-style, Panel, Debate, Documentary VO — each a high-quality bundle of defaults you can render in one call.
  2. Full parametrization underneath, so any style is expressible: a Script of beats, cast via a ConversationCast, tuned by WeaveConfig + Delivery, with per-beat voice/delivery overrides.

braidio was extracted from the Hamilton lyrics-podcast (its first application) into a reusable engine. It is now live infrastructure: released on PyPI, served as a multi-tool MCP connector, and imported in production by reelee. Treat a signature change here as having downstream blast radius.

The model

A production is a Script — an ordered list of beats:

Beat What it is
Narration(text, voice?, voice_settings?, …) one voice reading — a narrator or a solo presenter. voice / voice_settings override per beat, so one timeline can carry a lively presenter and a graver book-narrator.
Dialogue(turns=[(role, text)]) a multi-speaker exchange, synthesized in one pass (so it sounds like people talking to each other, not alternating monologues). A ConversationCast maps roles → voices.
SegmentBeat(reference) a span of source media to cut and weave in (resolved by a pluggable SegmentSource — quote → [start, end]). spotlight=True drops the music bed out under it.
SceneBreak(label?) a structural boundary — "a new section starts here". Renders as a short musical sting (your asset) or a beat of silence; costs nothing.

WeaveConfig holds every editing knob (casting, turns, pacing, clip pre/post-roll, duck, crossfades, loudness). Delivery presets bundle the TTS model + voice settings (e.g. V2_PRESENTER lively vs V2_NARRATOR grave). The renderer loudness-normalizes every part, tucks clips under the speech (speech stays dominant), and masters the result.

Ready-made formats

Pick a standard format and render it — the template supplies the cast, voices, delivery and mix defaults:

from braidio import DEEP_DIVE, render_format

render_format(DEEP_DIVE, script, source=my_source, out_path="episode.mp3")
Preset id Standard name Shape
solo_explainer Solo-Presenter Explainer (video/audio essay, close reading) one presenter + exhibits
deep_dive Two-Host Conversation ("Deep Dive", Switched on Pop, NotebookLM) two hosts; one teaches, one probes
interview Interview (host + guest) Q→A, guest is the center
interview_host_removed Song Exploder-style (host removed) guest monologue; each claim illustrated by its isolated stem; full artifact at the tail
panel Panel / Roundtable (Pop Culture Happy Hour) moderator routes distinct voices
debate Debate (Oxford-style) proposition / opposition / moderator, phased
documentary_vo Documentary Voice-Over ("Voice of God", This American Life) narrator on top; interviews + clips + actuality beneath

Narration bridges and source clips are optional illustration layers usable with any format. The taxonomy, weaving grammar, exemplar recipes and the full template specs are in misc/docs/research/commentary-formats-and-styles.md.

Render support note: the cast, per-role voices, narration deliveries, loudness master, per-clip placementSegmentBeat(placement="before" | "under" | "after"), where under lays the clip concurrently beneath the talk, ducked — a music bed (MusicBed, an app-supplied instrumental laid under the whole production) and the structural music are all applied. Structure is a SceneBreak beat in the Script — "a new section starts here" — which renders as a short sting when you pass render_format(..., sting_asset=…) (or a beat of silence when you don't), and SegmentBeat(spotlight=True) for fade-to-spotlight: the bed drops out before that exhibit and resumes after it. Each format's structure (MusicStructure) sets the defaults — whether a break stings, whether every clip is spotlit — and a beat overrides them. As with the bed, braidio ships no music: the sting is your asset. Both render paths carry this: the graph path takes the same structure= / bed= (or a fmt=, whose declared structure it uses) on weave_project, records the decision as a production-structure/v1 node, and re-renders only the episode when you swap the sting. Calling weave_project again on the same project re-ingests: beats are matched by position, unchanged ones reuse their renders, and a changed config, structure, rights profile or dialogue cast is rewritten under its existing node so everything derived from it reads stale through provenance — the only correct way to change a profile after the first weave. Every beat type takes the graph path, Dialogue included: an exchange is one dialogue-beat/v1 rendered in one Text-to-Dialogue pass, and the cast (cast=, else the format's, else the default) is one dialogue-cast/v1 node every dialogue render derives from — so recasting re-renders the exchanges and nothing else. Design history in braidio#25, braidio#39, braidio#46 and braidio#51.

Parametrize anything

The templates are just good defaults over the primitives — compose directly for full control:

from braidio import Script, Dialogue, Narration, SegmentBeat, WeaveConfig
from braidio.conversation import ConversationCast, JESSICA, CHRIS
from braidio import V2_NARRATOR, render_production

script = Script(
    title="…",
    id_slug="01",
    beats=[
        Dialogue(
            (("A", "First thing that gets me…"), ("B", "right — but isn't that…"))
        ),
        SegmentBeat("the lyric being discussed", label="hook"),
        Narration(
            "Here's how the book tells it —", voice_settings=V2_NARRATOR.voice_settings
        ),  # graver, per-beat
    ],
)
render_production(
    script,
    source=my_source,
    cast=ConversationCast(roles={"A": JESSICA, "B": CHRIS}),
    config=WeaveConfig(),
    out_path="out.mp3",
)

Cost tracking

ElevenLabs TTS is braidio's only spend (everything else is local ffmpeg). braidio.estimate_cost(script) previews a production's cost before you pay for synthesis, and real renders attribute a per-character cost onto their artifacts. Dollars are a rate estimate — set your ElevenLabs plan's rate for exact figures:

Env var Meaning
BRAIDIO_TTS_USD_PER_1K_CHARS USD per 1000 characters. Unset → a conservative default; none → mark spend unpriced (character counts still reported, dollars None).
BRAIDIO_TTS_VOICE Default ElevenLabs voice id for narration.
ELEVENLABS_API_KEY The key synthesis bills when a render is given no api_key=.

Every render entry point — narrate, render_dialogue, render_production, render_format and the graph driver weave_project — takes an explicit api_key= for a caller's own ElevenLabs key; None (the default) resolves from the environment. On the graph path the key reaches the two synthesis transforms through nw's execute(secrets=) seam and is never persisted: not in a node, provenance, a cache key or a usage ledger (thorwhalen/braidio#58). The MCP server reads it from the X-Elevenlabs-Key request header, never from a tool argument.

What it deliberately does not do

braidio is a thin orchestration layer. It delegates:

Concern Owner
Content acquisition (Genius, audiobooks, news…) the consuming app, via SegmentSource adapters
Linked-artifact graph / content-addressed media lacing
Project workflow, provenance, plan/execute, partial re-render nw
Video render + visual support reelee — except the stills case below
Pan/zoom motion over a still burns
Raw audio DSP + TTS (crop, concat, duck, loudnorm, synth) mixing / falaw

braidio orchestrates these; it never reimplements the DSP or the graph.

Video: a commentary episode you can watch

braidio[video] turns a rendered episode into a Ken Burns film over still images — the one video case that stays here rather than going to reelee, because reelee imports braidio and the cuts have to land on braidio's own beat timeline. burns owns the motion; braidio owns only where the picture changes.

import braidio
from braidio.video import plan_spans, assign_stills, render_video

path, timeline = braidio.render_format(
    fmt, script, source=src, out_path="ep.mp3", return_timeline=True
)
panels = assign_stills(plan_spans(timeline), ["a.jpg", "b.jpg", "c.jpg"])
render_video(panels, audio_path="ep.mp3", out_path="ep.mp4")

plan_spans cuts on beat boundaries — splitting a long passage so no photograph stalls on screen, merging a short beat so none flickers. Each Span carries the beat's label and kind, because which picture belongs over a sentence needs to know what the sentence says; assign_stills is the mechanical default for when the images are interchangeable texture. Motion is content-aware by default, so a slow push stays on the subject. braidio fetches no images — supply them, as with SegmentSource.

Subtitles need no ASR: you authored the words and the timeline says when they play.

Path("ep.srt").write_text(braidio.captions_for(script, timeline, max_chars=42))

braidio.captions is part of the core (pure, no extra dependencies); braidio.video's dependencies are imported inside the functions that use them, so import braidio.video works on a bare install and the planners stay usable — braidio.HAS_VIDEO reports whether the render path is available.

Agent skills

braidio ships skills that install with it, so an agent host can use them without cloning the repo:

Skill For
braidio authoring and rendering a commentary production (audio)
braidio-commentary-video the video pipeline above — panels, stills, captions, credits, rights
ln -s "$(python -c 'import braidio; print(braidio.skills_dir())')/braidio" ~/.claude/skills/braidio

Ecosystem

braidio is a production kind on top of nw — a reusable definition of "commentary that weaves talk with extracted media" — the way a music video is another production kind. It sits on lacing (graph) + nw (workflow / provenance) + mixing/falaw (audio/TTS), and will use reelee for video. The optional nw-app layer (braidio.HAS_GRAPH / HAS_NW) records render choices as provenance so a change re-renders only the affected parts.

Install

pip install braidio

ElevenLabs credentials are needed for TTS; ffmpeg must be on PATH for everything else.

License

MIT

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

braidio-0.0.39.tar.gz (320.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

braidio-0.0.39-py3-none-any.whl (170.7 kB view details)

Uploaded Python 3

File details

Details for the file braidio-0.0.39.tar.gz.

File metadata

  • Download URL: braidio-0.0.39.tar.gz
  • Upload date:
  • Size: 320.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.12.13 {"installer":{"name":"uv","version":"0.12.13","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for braidio-0.0.39.tar.gz
Algorithm Hash digest
SHA256 585211cd3e2d77fad4720ce6fba94bdb8ebe50312ae474d8a5251b15cfd66bdf
MD5 b67f61e2143e3f99084a0e94b18b20f1
BLAKE2b-256 b7ef5913d2bdc4ef0ae790e95690464c3e6bbdc4b98c5f7f62f99136d18c6bc2

See more details on using hashes here.

File details

Details for the file braidio-0.0.39-py3-none-any.whl.

File metadata

  • Download URL: braidio-0.0.39-py3-none-any.whl
  • Upload date:
  • Size: 170.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.12.13 {"installer":{"name":"uv","version":"0.12.13","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for braidio-0.0.39-py3-none-any.whl
Algorithm Hash digest
SHA256 d23ed7a8703f7214f0a695343fb2922761d980bb76eb2644ecb9633aff728515
MD5 7ce2b50c8ac0f8ad35e71535f3140713
BLAKE2b-256 133b5442ad22e5ee3b04b6a4e4349aae955c7055d4e04910610b6a18a5b25af7

See more details on using hashes here.

Release history Release notifications | RSS feed

0.0.40

2 files

This release

0.0.39 This release

2 files

0.0.38

2 files

0.0.37

2 files

0.0.36

2 files

0.0.35

2 files

0.0.34

2 files

0.0.33

2 files

0.0.32

2 files

0.0.31

2 files

0.0.30

2 files

0.0.29

2 files

0.0.28

2 files

0.0.27

2 files

0.0.26

2 files

0.0.25

2 files

0.0.24

2 files

0.0.23

2 files

0.0.22

2 files

0.0.21

2 files

0.0.20

2 files

0.0.19

2 files

0.0.18

2 files

0.0.17

2 files

0.0.16

2 files

0.0.15

2 files

0.0.14

2 files

0.0.13

2 files

0.0.12

2 files

0.0.11

2 files

0.0.10

2 files

0.0.9

2 files

0.0.8

2 files

0.0.7

2 files

0.0.6

2 files

0.0.5

2 files

0.0.4

2 files

0.0.3

2 files

0.0.2

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page