braidio.timeline#
Timeline breakdown — what a production spends its time on, and in what order.
A rendered production is a walk of beats; this module turns that walk into a
queryable object: for each beat, its kind, source interval (for clips),
rendered duration, and offset on the episode timeline — plus per-kind
totals and a self-contained HTML view. It’s built on the same layout math
(braidio.weave.layout_placed()) the renderer uses, so the offsets match the
actual mix.
Get one straight from the renderer:
out, tl = render_production(script, source=src, return_timeline=True)
tl.totals() # {"clip": 78.6, "book-passage": 346.4, "narration": 616.5}
tl.shares() # same, as fractions
open("anatomy.html", "w").write(tl.to_html("Episode anatomy"))
or build one from raw render data with build_timeline(). This is the
first-class, reusable form of what used to be reconstructed by ad-hoc scripts
(braidio#3): the render now records its timeline instead of anyone probing the
output after the fact.
Functions
|
Assemble a |
|
The record of how a production was rendered, as plain JSON types. |
Classes
|
One beat's place on the timeline. |
|
The ordered beats of a production, with per-kind totals and an HTML view. |
- class braidio.timeline.BeatSpan(index, kind, label='', source_start=None, source_end=None, duration=0.0, start=0.0, placement=None)[source]#
Bases:
objectOne beat’s place on the timeline.
- class braidio.timeline.TimelineBreakdown(beats=<factory>, title='', settings=None)[source]#
Bases:
objectThe ordered beats of a production, with per-kind totals and an HTML view.
settingsis the render record: the settings that actually produced these timings (seerender_settings()), orNonefor a breakdown built by hand. It exists because a render stopped being reproducible from the script alone the momentWeaveConfig.segmentation_unitbecame a real switch (braidio#63): the same script now renders to different durations under different pacing knobs, so a persisted breakdown whose settings are unknown cannot be re-derived, and any downstream data keyed to its timings (panel cues, captions) silently stops matching a later re-render. Recording the settings next to the timings closes that hole on the no-graph fast path, where the nw/lacing provenance layer is not in play at all.It is always plain JSON types, so a consumer that already persists the breakdown gets the record for free.
- classmethod from_dict(d)[source]#
Rebuild a breakdown from
to_dict()output (the persisted form).totalsanddurationare derived, so they are recomputed rather than read back — the beats are the record.- Return type:
>>> tl = build_timeline(kinds=["narration", "clip"], durations=[4.0, 2.0], ... labels=["a", "b"], source_spans=[None, (1.0, 3.0)]) >>> TimelineBreakdown.from_dict(tl.to_dict()) == tl True
- braidio.timeline.build_timeline(*, kinds, durations, placements=None, labels=None, source_spans=None, clip_edge_overlap_s=0.5, narration_crossfade_s=0.12, title='', settings=None)[source]#
Assemble a
TimelineBreakdownfrom per-beat render data (pure).kindsare the aggregation labels (any string;"clip"is the only one the layout treats specially — as an overlapping segment).placementsis the per-beat"sequential"/"under"used by the weave; offsets are computed with the samelayout_placed()the renderer uses.settingsis the optional render record (render_settings()) — what produced these durations. It stays optional so building a timeline by hand (tests, a hand-cut episode, the captions/video doctests) needs nothing extra; a real render always passes one.- Return type:
- braidio.timeline.render_settings(*, config, crossfade_s, clip_edge_overlap_s, target_lufs, duck_db, delivery=None, profile=None, normalize=True)[source]#
The record of how a production was rendered, as plain JSON types.
Why it exists: since braidio#63 the pacing knobs are real, so two renders of one script can differ by minutes — and until this, nothing on disk said which settings produced which file. This is the cheap, no-graph answer: the renderer hands it to the
TimelineBreakdownit already returns, so a consumer that persists the breakdown persists the settings with it.The whole resolved
WeaveConfiggoes in under"weave"rather than a hand-picked subset — adding a knob then keeps the record correct for free, where a curated list would quietly drift."resolved"carries the values the render actually used, which are not always the config’s: a caller may pass no config at all ("weave"is thenNone, and the pacing knobs were never consulted — each beat was one TTS call), the concat path resolves its crossfade from either the config or thecrossfade_sargument, andnormalizeis a render argument rather than a weave choice.>>> from braidio.delivery import V2_TUNED >>> from braidio.weave_config import WeaveConfig >>> rec = render_settings( ... config=WeaveConfig(segmentation_unit="sentence", gap_turn_s=0.28), ... crossfade_s=0.12, clip_edge_overlap_s=0.5, ... target_lufs=-16.0, duck_db=-15.0, ... delivery=V2_TUNED, profile="published", ... ) >>> rec["weave"]["gap_turn_s"], rec["resolved"]["paces_narration"] (0.28, True) >>> rec["delivery"]["model_id"], rec["profile"] ('eleven_multilingual_v2', 'published')