Skip to main content

artful

Storyboard data model and exporters — lacing-native panels along a timeline.

A storyboard is a sequence of panels along a timeline (a song, a video, a podcast clip). Each panel pins an interval of the master asset, optionally points at a project shot, and carries one or more images plus directorial annotations (caption, framing, camera, transition, notes). Panels are persisted as lacing annotations, so a storyboard is queryable, exportable, and round-trippable through every adapter lacing already supports (TextGrid, EAF, JAMS, OTIO, WebVTT, Web Annotation, …) without artful having to reinvent any of it.

artful is not the renderer. It's the panels-along-a-timeline plan that drives a renderer: an LLM authors panels from a script, a human reviews the contact sheet, the same panel data feeds downstream image / video generation.

Install

pip install artful

Requires pydantic>=2.6 and lacing>=0.0.13.

Quick start

from artful import (
    PanelBody,
    PanelImage,
    Storyboard,
    panel_intervals_from_panels,
    save_storyboard,
    load_storyboard,
)
from lacing import MemoryStore

sb = Storyboard(
    title="The Bells — v1",
    asset_id="song-asset-id-abc",
    style="noir, candlelight",
    panels=(
        PanelBody(
            panel_id="p1",
            caption="Thor at the piano",
            framing="medium",
            images=(PanelImage(path="composite.png", role="seed"),),
        ),
        PanelBody(
            panel_id="p2",
            caption="Bells over winter sky",
            framing="wide",
            camera="slow push-in",
        ),
    ),
)

# Pin each panel to a time interval (seconds on the master timeline).
intervals = panel_intervals_from_panels(
    [
        ("p1", 0.0, 4.0),
        ("p2", 4.0, 8.0),
    ]
)

store = MemoryStore()
save_storyboard(sb, store, panel_intervals=intervals)

# ... later, possibly in a different process / backend ...
loaded = load_storyboard(store, asset_id="song-asset-id-abc")

The store can be any lacing.IntervalAnnotationStore (MemoryStore, SQLiteStore, PostgresStore, …); the persistence layer is lacing's.

Authoring with LLMs (Markdown)

Markdown is artful's canonical "give an LLM a storyboard to read or write" format. It round-trips losslessly through to_markdown / from_markdown.

from artful import to_markdown, from_markdown

md = to_markdown(loaded, intervals)
# Show `md` to an LLM, let it edit panels, then…
edited_sb, edited_intervals = from_markdown(edited_md)
save_storyboard(edited_sb, store, panel_intervals=edited_intervals)

The Markdown shape:

# The Bells — v1

- asset_id: `song-asset-id-abc`
- style: noir, candlelight
- aspect: 16:9

## panel p1 [0.00..4.00]s

- framing: medium

Thor at the piano

![seed](composite.png)

## panel p2 [4.00..8.00]s

- framing: wide
- camera: slow push-in

Bells over winter sky

Reviewing as a contact sheet (HTML)

from artful import to_html

with open("storyboard.html", "w") as f:
    f.write(to_html(loaded, intervals))

to_html produces a self-contained HTML page with embedded styles and <img> tags pointing at each panel's path / url. Open it in a browser to review.

Data model

Type What it is
Storyboard Title, asset_id (the timeline), panels, style hint, aspect ratio.
PanelBody One panel: panel_id, optional shot_id, images, caption, framing, camera, transition_in, notes. The interval lives on the lacing annotation's reference, not in the body.
PanelImage One image for a panel — artifact_id (sha-256 lacing artifact ref), url, or path, plus a role — a free string, by convention thumbnail / seed / reference / alternate, not validated — and caption.

All three forbid unknown fields (extra="forbid"); the two body models — PanelBody and PanelImage — are also frozen, so build new versions with model_copy(update={...}). Importing artful registers PanelBody against lacing under the body-schema URI annot://schema/storyboard-panel/v1, so lacing.validate_body(...) works out of the box.

Why lacing-native?

Storyboards are an annotation problem: panels are intervals on a timeline with structured bodies. Rather than invent a new persistence layer, artful encodes each panel as a lacing.Annotation whose body_schema_uri is the panel schema. The benefits:

  • Allen interval algebra for free: overlaps, gaps, adjacencies.
  • Multiple storyboards over the same asset stay separate via the tier field on each annotation.
  • Provenance tracked across edits through lacing's Provenance model.
  • Format adapters — read or write panels as TextGrid tiers, JAMS annotations, OTIO clips, Web Annotation JSON-LD, etc., via lacing.

A small StoryboardMetaBody schema (URI annot://schema/storyboard-meta/v1) carries title / style / aspect as a single timeless annotation alongside the panels.

The shot schedule

Before there are panels there is a shot schedule: an ordered list of shots, each carrying the constraints a downstream planner must respect, and the advisory risk flags raised when those constraints collide with the chosen video model's real limits.

from artful import (
    RiskFlag,
    ShotEntry,
    ShotScheduleBody,
    save_shot_schedule,
    load_shot_schedule,
)

sched = ShotScheduleBody(
    schedule_id="sch-001",
    title="Scene 4 — the pub",
    model_id="fal-ai/bytedance/seedance/v1/pro/image-to-video",  # a reference
    aspect="16:9",
    resolution="720p",
    shots=(
        ShotEntry(
            shot_id="sh-01",
            description="Mairead pushes the door open.",
            characters=("Mairead",),
            shot_size="MS",
            duration_seconds_estimate=6.0,
            max_duration_seconds=8.0,
            max_characters_in_frame=1,
            take_budget=3,
            clump_id="pub-interior",
        ),
        ShotEntry(
            shot_id="sh-02",
            characters=("Mairead", "Declan"),
            duration_seconds_estimate=14.0,
            has_dialogue=True,
            risk_flags=(
                RiskFlag(
                    code="over_clip_cap",
                    message="~14s is over this model's ~10s clip cap.",
                    gotcha_id="seedance-clip-length-cap",
                ),
            ),
        ),
    ),
)

save_shot_schedule(sched, store, asset_id="song-asset-id-abc")
loaded = load_shot_schedule(store, asset_id="song-asset-id-abc")

Three sources of truth, deliberately kept apart:

what who owns it
What a model can do (max_clip_seconds, single_character_recommended, supported_resolutions, …) the model registry downstream — referenced here by model_id, never copied.
What a shot requires (max_duration_seconds, max_characters_in_frame, aspect, resolution, take_budget) ShotEntry.
What happens when the two collide RiskFlag — a cached verdict, stamped with advised_for_model_id so a model change makes it visibly stale via schedule.needs_advice.

Ordering is the tuple order of shots — there is deliberately no order field to disagree with it, and no character_count field to drift from len(characters). Constraint defaults encode the safe choice: allow_last_frame_anchor is False, because anchoring both a first and a last frame contorts the subject mid-clip.

The schedule persists as a single timeless annotation under annot://schema/shot-schedule/v1 — a schedule exists before times are pinned, which is exactly what duration_seconds_estimate is for.

API surface

from artful import (
    # data model
    Storyboard,
    PanelBody,
    PanelImage,
    ModelSheet,
    ShotScheduleBody,
    ShotEntry,
    RiskFlag,
    new_panel_id,
    new_shot_id,
    new_schedule_id,
    # persistence (round-trip with any lacing.IntervalAnnotationStore)
    save_storyboard,
    load_storyboard,
    panel_intervals_from_panels,
    save_shot_schedule,
    load_shot_schedule,
    load_shot_schedules,
    # exports
    to_markdown,
    from_markdown,
    to_html,
    # body-schema URIs
    PANEL_BODY_SCHEMA_URI,
    STORYBOARD_META_BODY_SCHEMA_URI,
    MODEL_SHEET_BODY_SCHEMA_URI,
    SHOT_SCHEDULE_BODY_SCHEMA_URI,
    StoryboardMetaBody,
)

PDF export is deferred to the optional [pdf] extra (reportlab).

License

MIT

Metadata

Release files for artful 0.0.11

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for artful 0.0.11
File Size Uploaded
artful-0.0.11.tar.gz 44.2 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for artful 0.0.11
File Interpreter ABI Platform
artful-0.0.11-py3-none-any.whl Python 3 none any Details

Total release size: 69.1 kB

Release files / artful-0.0.11.tar.gz

Download URL artful-0.0.11.tar.gz
Size 44.2 kB
Tags Source
SHA-256 checksum
How to use checksums
b6e5cca083e74284fb5f94ca3863fe7075c950e01dd916169224fe7ccbbd2b14
BLAKE2b-256 checksum
How to use checksums
5748f70b58d3c7a179421f7c0808c1a63e5bf29464ca7c2948810bdef2988743
Upload date
Uploaded using Trusted Publishing?
What is 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}

Release files / artful-0.0.11-py3-none-any.whl

Download URL artful-0.0.11-py3-none-any.whl
Size 25.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
c85ea88672669df9f54038cabd554e39b79e0dde2237e1d6c7553a5fabb853f5
BLAKE2b-256 checksum
How to use checksums
a8ee8aaa09e2ce9193574868204c5230f9324f3c67781c0e595d0f3b6588dfff
Upload date
Uploaded using Trusted Publishing?
What is 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}

Release history Release notifications | RSS feed

0.0.13

2 release files

0.0.12

2 release files

This release

0.0.11 This release

2 release files

0.0.10

2 release files

0.0.9

2 release files

0.0.8

2 release files

0.0.7

2 release files

0.0.6

2 release files

0.0.5

2 release files

0.0.4

2 release files

0.0.3

2 release files

0.0.2

2 release 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