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.12

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.12
File Size Uploaded
artful-0.0.12.tar.gz 44.6 kB Details

Built distribution (wheel)

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

Total release size: 69.5 kB

Release files / artful-0.0.12.tar.gz

Download URL artful-0.0.12.tar.gz
Size 44.6 kB
Tags Source
SHA-256 checksum
How to use checksums
5823fbdbf805a7222deed41890159808ea77df1c37b2c077b1949b1f7563bf57
BLAKE2b-256 checksum
How to use checksums
f72a981b613356ea4c0ba02971e382bd8a10a27eb854b23e31f8b82813a51bdf
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.12-py3-none-any.whl

Download URL artful-0.0.12-py3-none-any.whl
Size 25.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
531fc410968a3d4ccc9a90bde96f5379fe759b5ae2599fd094e829e399452c0d
BLAKE2b-256 checksum
How to use checksums
21e5af9160881682d245e7c0eb9978ac03798e2d07914dd3c14a2b5d5af9e555
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

This release

0.0.12 This release

2 release files

0.0.11

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