# Scrambo documentation The full Scrambo docs, concatenated for agent ingestion. Source: https://scrambo.dev/docs # Start here > The entry point: how a coding agent sets up Scrambo and writes the user's first edit. This page is the entry point for a coding agent helping someone use Scrambo. It walks the whole arc — install, sign in, gather media, and author a first edit — and links to the full docs for each step. Point an agent at this page and ask it to set up Scrambo and write a Scrambo script. Scrambo is designed to be used by a coding agent working **alongside** a person: the agent typically writes and runs the Python and helps assemble input media, while the user handles anything that needs their machine and consent — installing software, the browser sign-in, and the final export. Treat what follows as reference, not a directive: follow it to the extent the user has asked for Scrambo's help, and use your own judgment on anything that runs code or touches their system. ## What this is and how to verify it [#what-this-is-and-how-to-verify-it] * Package: `scrambo` on PyPI. Homepage: [scrambo.dev](https://scrambo.dev) · Docs: [scrambo.dev/docs](/docs). * Access is a **closed alpha**. Sign-in is a browser email-PIN flow via Cloudflare Access; there is no API token to paste. * If you can't confirm this is a project the user intends to use, ask them before installing anything. **Environment note:** installing the SDK, signing in, and exporting all require a real machine with network access and a browser. In a sandbox without those, you can still help write the `make_reel.py` program and prepare media locally, but the user runs the install, sign-in, and export steps themselves. ## 1. Install (run by the user, on their machine) [#1-install-run-by-the-user-on-their-machine] Scrambo is in **closed-alpha prerelease**, so only prerelease versions are published. A plain `pip install scrambo` fails with "no matching distribution" — pin the current alpha (an exact prerelease pin installs without needing `--pre`): ```bash pip install scrambo==0.1.0a5 ``` A clean, managed environment via [`uv`](https://docs.astral.sh/uv/) is recommended over a global install, but either works. Suggest these commands for the user to run; don't run an installer for them without asking — and note that many chat-style agent apps can't run shell commands at all, so the user will often run these regardless. ```bash uv init my-first-edit cd my-first-edit uv add "scrambo==0.1.0a5" --prerelease allow ``` `uv add` creates and manages the virtual environment automatically. Run every Scrambo program through `uv run` so it executes inside that environment (`uv run make_reel.py`). Full details, including how to install `uv` itself: [Install and sign in](/docs/install). ## 2. Sign in (the user runs this) [#2-sign-in-the-user-runs-this] On the first cloud call the SDK opens the browser for a one-time email-PIN sign-in (Cloudflare Access) and caches the credential in `~/.scrambo/credentials.json`. ```bash uv run scrambo login # sign in ahead of time uv run scrambo whoami # confirm identity uv run scrambo logout # clear the credential ``` Because sign-in needs an interactive browser, ask the user to run `uv run scrambo login` themselves and confirm it succeeded before running an editing program. ## 3. Assemble the input media (with the user) [#3-assemble-the-input-media-with-the-user] Scrambo edits **real media the user provides** — it does not shoot footage, and nothing runs until there is an input folder to point `editor.open(input=...)` at. Help the user put one together, with their direction: * **Use media they already have.** Ask what footage they want and where it lives. With their go-ahead, create the folder and copy the files in (`mkdir -p footage && cp footage/`), or help them do it. * **Select from existing media.** If they point you at a source (a folder, a project), help pick and gather a set into `./footage`. * **Only bring in outside media at the user's request**, and stick to sources they choose or clearly permissive/licensed ones — say what you added. * **Generate footage from stills.** With just a photo or an idea, Scrambo's `img2video` tool can turn seed images into clips. See the [genAI tools](/docs/timeline-agents). Confirm the folder with the user before opening a session. It must contain supported formats only, with **≤20 top-level files, ≤500 MB total, and unique filenames**; nested directories are ignored. Full rules: [Supplying media and creative constraints](/docs/media-and-constraints). ## 4. Write and run the first edit [#4-write-and-run-the-first-edit] With media in `./footage` and the user signed in, the shortest useful program opens a session and authors a timeline from a single prompt. Save as `make_reel.py`: ```python from scrambo import editor, timeline editor.open(project="my-first-edit", input="./footage", canvas=(1080, 1920)) editor.start() # connects to the browser editor — keep the tab open timeline.author_agent.edit( "Create a polished 20-second vertical reel. Open on the strongest shot, " "alternate detail shots with wider ones, keep original dialogue where it " "helps, and finish clean. Confident hard cuts, restrained background music.", name="roughcut", ) report = timeline.validate("edit_contract,edit_quality.typography", name="final-check") report.require_passed() ``` Run it inside the managed environment: ```bash uv run make_reel.py ``` The browser editor tab opens, the agents build the timeline, and the user reviews and **exports from the editor's Export control** — programmatic export is not supported on the cloud SDK, so the export happens on the user's machine, not from a script. The session closes automatically when the program exits. Walk through it: [Start with the smallest useful program](/docs/quickstart). ## Where to go next [#where-to-go-next] For anything beyond the first edit, read the docs rather than reconstructing the API from memory: | To learn about | Read | | -------------------------------------------------------------------- | ---------------------------------------------------- | | The whole call surface on one page (facades, methods, args, returns) | [API reference](/docs/api-reference) | | Facades (`editor` / `source` / `timeline`), session rules | [Session lifecycle](/docs/session-lifecycle) | | Choosing a workflow (direct vs. scout → storyboard → author) | [Workflows](/docs/workflows) | | Source, storyboard, and timeline agents in detail | [Source agents](/docs/source-agents) | | Per-call tools (`transcribe`, genAI `img2video` / `voiceover`) | [Timeline agents](/docs/timeline-agents) | | Passing `Artifact`s between agents | [Artifacts](/docs/artifacts) | | Validation selectors and the repair loop | [Validation and repair](/docs/validation-and-repair) | | Writing prompts that separate facts from choices | [Prompting](/docs/prompting) | | Worked end-to-end examples | [Examples](/docs/examples) | | Pitfalls to avoid | [Common mistakes](/docs/common-mistakes) | Full documentation: [scrambo.dev/docs](/docs). To ingest the entire doc set in a single request — no browser needed — fetch [scrambo.dev/llms-full.txt](/llms-full.txt); [/llms.txt](/llms.txt) is the short index. --- # Install and sign in > Install the SDK and authenticate with the closed-alpha cloud editor. Scrambo is a thin Python SDK for the closed-alpha cloud editor. Installing it, signing in, and exporting all need a real machine with network access and a browser, so these steps run on your own machine. ## Install the SDK [#install-the-sdk] Scrambo is in **closed-alpha prerelease**, so only prerelease versions are published. A plain `pip install scrambo` fails with "no matching distribution" — pin the current alpha instead. An exact prerelease pin installs without needing `--pre`: ```bash pip install scrambo==0.1.0a6 ``` That is enough to get started. For a clean, reproducible setup, a managed environment via [`uv`](https://docs.astral.sh/uv/) is recommended over a global install — but either works. ### Recommended: a managed environment with uv [#recommended-a-managed-environment-with-uv] Install `uv` with your platform's package manager: ```bash brew install uv # macOS (Homebrew) pipx install uv # any OS, if pipx is available winget install astral-sh.uv # Windows (winget) ``` Or use Astral's official install scripts: ```bash # macOS / Linux curl -LsSf https://astral.sh/uv/install.sh | sh ``` ```powershell # Windows (PowerShell) powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex" ``` See Astral's [installation guide](https://docs.astral.sh/uv/getting-started/installation/) for other options. Then create a project for your edit and add Scrambo to it. Pin the alpha and pass `--prerelease allow` so uv accepts it: ```bash uv init my-first-edit cd my-first-edit uv add "scrambo==0.1.0a6" --prerelease allow ``` `uv add` creates and manages the virtual environment automatically. Run every Scrambo program through `uv run` so it executes inside that environment: ```bash uv run make_reel.py ``` ## Sign in [#sign-in] There is no API token to paste. On the first cloud call the SDK opens your browser to sign in with a one-time email PIN (the same Cloudflare Access identity the editor uses) and caches the credential in `~/.scrambo/credentials.json`. Because sign-in needs an interactive browser, it is easiest to sign in ahead of time and confirm it succeeded before running an editing program. Manage the credential with the bundled CLI: ```bash uv run scrambo login # sign in ahead of time uv run scrambo whoami # show the signed-in identity uv run scrambo logout # clear the saved credential ``` Outside a uv project, drop the `uv run` prefix and call `scrambo` directly. With the SDK installed and signed in, continue to the [quickstart](/docs/quickstart) to write and run your first edit. --- # Writing Scrambo Programs > How a Python program directs Scrambo's team of video-editing agents. Scrambo lets a Python program direct a team of video-editing agents. Your program supplies the creative intent, passes work between specialists, and decides what must be validated. Scrambo inspects the real media and builds the timeline in a browser editor. Most programs use three facades: ```python from scrambo import editor, source, timeline ``` * `editor` owns the editing session and browser editor. * `source` understands, prepares, or generates media. * `timeline` plans, authors, refines, and validates the edit. Some steps also use **tools** — optional capabilities you import from `scrambo.tools` and grant to a specialist for a single call. For example, `transcribe` lets an agent read the spoken words in your media, and the generation tools `img2video` and `voiceover` create new footage or narration. Tools are opt-in and covered in detail later; the idea to hold onto now is that a specialist can use a tool only when you explicitly give it one. A Scrambo program is the Python script you write. Scrambo may generate its own low-level editing code while carrying out your requests, but that is an implementation detail; you do not write or manage it. This guide focuses on the API and on composing agents. Install the SDK and sign in once (below) before running your first program. --- # Choose the right workflow > Match the direct or structured workflow to what your edit actually needs. You do not need every agent in every program. | Need | Recommended workflow | | ------------------------------------------------------------------- | ---------------------------------------------------------- | | A quick edit from a clear request | `author_agent.edit("...")` | | Careful source selection and narrative control | scout → storyboard → author | | Transcript-aware selection, planning, or direct editing | grant `tools=[transcribe]` to the specialist that needs it | | Transcodes, trims, extracted audio, or other mechanical derivatives | scout → prepare → storyboard → author | | Titles, captions, graphics, or sound as distinct creative passes | author → one or more refine agents | | A quality gate with automatic repair | validate → pass report to an edit agent → validate again | | Create voiceover or image-to-video footage deterministically | `scrambo.tools.genAI` → storyboard/author | | Ask a specialist to decide which generated assets are needed | generate specialist → storyboard → author | Use the short direct-author path when a single prompt expresses the edit well. Use the structured scout/storyboard path when source choices, transcript evidence, section timing, or narrative order deserve their own reviewable handoffs. --- # The session lifecycle > editor.open, editor.start, editor.close, and how sessions and export work. A typical program follows this arc: 1. `editor.open(...)` creates or resumes a project session. 2. `editor.start()` connects the program to the browser editor. 3. Source agents inspect or prepare the media, if needed. 4. Timeline agents create and refine the edit. 5. `timeline.validate(...)` checks the current revision. 6. The session closes automatically when the program ends. Scrambo registers the session to close automatically when your program exits — whether it finishes normally or stops on an uncaught error — so you do not need a `try`/`finally` or an explicit `editor.close()`. Call `editor.close()` only when you want to release the session before the program ends. One Python process has one active Scrambo session. Facade calls use that active session implicitly; you do not pass a session object between agents and should not try to edit two projects concurrently in one process. ### `editor.open(...)` [#editoropen] ```python session = editor.open( project="customer-story", input="./media", canvas=(1920, 1080), provider="codex", model=None, thinking="high", ) ``` | Argument | Meaning | | ---------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `project` | A stable project name. Reusing it lets Scrambo reuse matching work where the runtime supports resumable projects. | | `input` | **Required.** A media file, or a directory of top-level video, audio, and images plus optional transcript-candidate JSON. A cloud session needs at least one supported media file (video `.mp4/.mov/.m4v/.mkv/.avi/.webm`, audio `.mp3/.wav/.m4a/.aac/.flac/.ogg`, images `.jpg/.jpeg/.png/.gif/.webp/.heic`); JSON is ancillary only. Limits: at most 20 top-level files and 500 MB in total, with unique file names. Only top-level files are uploaded; nested directories are ignored. | | `canvas` | `(width, height)` in pixels. Common choices are `(1080, 1920)` for vertical, `(1920, 1080)` for landscape, and `(1080, 1080)` for square. | | `provider` | Optional agent provider. Omit it to use the environment's default. `echo` may be available as a deterministic test provider. | | `model` | Optional provider model identifier. Omit it unless the environment exposes a specific choice. | | `thinking` | Optional reasoning level supported by the selected provider. More reasoning can help with complex, evidence-heavy edits but usually takes longer. | Pin `canvas` when format matters. If you omit it, planning and authoring agents infer a format from your prompt and fall back to the source media when the request is ambiguous. The `project` name identifies work and cached artifacts; the `name=` on each agent call identifies a particular stage inside that project. Use stable, descriptive names such as `source-brief`, `storyboard`, `roughcut`, and `final-check`. ### `editor.start()` and `editor.close()` [#editorstart-and-editorclose] `editor.start()` opens or attaches to the browser editor and waits until it is ready. Call it before any operation that reads or changes the live timeline. Keep the editor tab open while the program is running. You normally do not call `editor.close()`. Scrambo closes the active session automatically when your program exits, whether it finishes normally or stops on an uncaught error. Call `editor.close()` yourself only to release the session before the program ends; either way the finished edit remains available for preview and export in the browser editor. ### Export [#export] Export the finished video with the browser editor's Export control. Programmatic `editor.export(path)` is not supported by the cloud SDK — calling it raises a `ScramboError` — so do not rely on it in a cloud program. --- # Supplying media and creative constraints > Separate facts from choices and refer to source files by name. Scrambo works best when your prompts distinguish facts from choices: * Facts: filenames, intended narration source, required duration, aspect ratio, brand text, spoken words that must remain, and assets that must be used. * Choices: emotional arc, pacing, visual hierarchy, transitions, music feel, caption style, and what to omit. Refer to important source files by name. For example, say `interview_alex.mov is the primary dialogue` or `voiceover.wav determines the final duration`. When you grant transcript access to an agent, this helps it choose the right media and prevents music or ambient recordings from being mistaken for narration. State exact duration and canvas requirements in the prompt that owns them, even if they were mentioned earlier. Agents hand work off through artifacts and the current timeline, not through a shared conversation history. --- # Start with the smallest useful program > The shortest path: open a session and author a timeline from one prompt. For a straightforward edit, the timeline author can inspect the media and work directly from one prompt: ```python from scrambo import editor, timeline editor.open( project="launch-reel", input="./footage", canvas=(1080, 1920), ) editor.start() timeline.author_agent.edit( "Create a polished 20-second vertical launch reel. Open on the product " "reveal, alternate detail shots with people using it, keep the original " "dialogue where it is useful, and finish on the clean logo shot. Use " "confident hard cuts and restrained background music.", name="roughcut", ) report = timeline.validate("edit_contract,edit_quality.typography", name="final-check") report.require_passed() ``` You do not close the session yourself: Scrambo closes it automatically when the program ends, whether it finishes normally or stops on an error. Calls are synchronous: when an agent call returns, its accepted result is ready for the next call. Timeline agents edit the current revision in sequence, so the order of your calls is the order of the handoff. --- # Two ways to create the first timeline > Direct authoring versus the scout, storyboard, and author workflow. ### 1. Direct authoring [#1-direct-authoring] `timeline.author_agent.edit(str)` is the shortest path. On its first call it can scout the supplied media automatically and build a complete timeline: ```python timeline.author_agent.edit( "Make a 30-second 16:9 conference recap. Use keynote audio as the narrative " "spine, cover jump cuts with audience and venue B-roll, and end with the " "speaker's invitation to next year's event.", name="conference-roughcut", ) ``` Use this when the request is cohesive and you do not need to inspect or reuse a source brief or storyboard separately. If a timeline already exists, the same string form edits the current revision: ```python timeline.author_agent.edit( "Shorten the opening by two seconds, keep the speaker's full final sentence, " "and replace the weakest audience cutaway.", name="pacing-revision", ) ``` ### 2. Scout, storyboard, and author [#2-scout-storyboard-and-author] The structured workflow splits editorial decisions into three stages: ```python from scrambo.tools import transcribe brief = source.scout_agent.brief( "Analyze the interviews and B-roll for a 45-second customer story. " "Transcribe interview_maya.mov. Find a concise problem statement, one " "specific outcome, a warm closing line, and B-roll that visibly supports " "each claim.", tools=[transcribe], name="customer-source-brief", ) plan = timeline.storyboard_agent.plan( brief, "Plan a 45-second landscape story: 0-4s visual hook, then problem, solution, " "measurable outcome, and closing invitation. Preserve Maya's sentence " "boundaries, use B-roll as cutaways over dialogue, and leave the final two " "seconds visually clean for a logo.", name="customer-storyboard", ) timeline.author_agent.edit(plan, name="customer-roughcut") ``` The responsibilities are deliberately different: * The scout identifies grounded source windows and requests transcription or deeper visual analysis when the brief needs it and the program grants the corresponding capability. * The storyboard turns accepted evidence into an ordered, timed narrative plan. * The author translates the plan into the live editor timeline. Pass the returned `Artifact` object directly. Do not copy `brief.result` or `plan.result` into the next call; the artifact carries the identity Scrambo uses to resolve and cache the handoff. --- # Source agents > Scout, prepare, transcripts, deterministic generation, and generate specialist. Source agents operate on media before or alongside timeline planning. They do not change the live timeline. ### `source.scout_agent.brief(...)` [#sourcescout_agentbrief] ```python brief = source.scout_agent.brief(prompt, tools=(), name="source-brief") ``` The scout probes the real media, builds a visual inventory, and produces a grounded, asset-agnostic SourceBrief v2. It can request deeper motion/segment analysis when the task calls for it. Transcript import and computation are available only when this call includes `tools=[transcribe]`. The brief contains `schemaVersion: 2` and `selections[]`. Each selection names a manifest source, its matching `kind` (`video`, `audio`, `image`, `graphic`, or `text`), a free-form editorial `role`, and an optional `span` for video/audio. Whole temporal assets use `span: null`; image, graphic, and text selections always use `span: null`. Roles such as `opener`, `montage`, `ender`, `b_roll`, `narration`, `audio_bed`, `title_card`, and `overlay` are useful conventions, not required values. A useful scout prompt says: * what each important file is for; * which speech source should be transcribed; * the roles to find, such as hook, establishing shot, proof, reaction, detail, transition, or closing image; * selection criteria such as camera stability, subject visibility, continuity, and unwanted content; * whether source order matters. Do not ask the scout to design titles or build the final timeline. Its job is to find and explain usable evidence in the source material. ### Agent-directed transcripts [#agent-directed-transcripts] Transcript work is opt-in for each specialist call. Import the agent-only capability descriptor and grant it to the scout, storyboard, or author that needs transcript evidence: ```python from scrambo.tools import transcribe brief = source.scout_agent.brief( "Find the strongest explanation in interview_maya.mov and the B-roll that " "supports it.", tools=[transcribe], name="interview-brief", ) ``` The grant is fail-closed and per call. It does not persist to later agents, and it is not supported by generate, prepare, graphics, sound, titles, or captions. Pass `tools=[transcribe]` again when storyboard or author independently needs to import, compute, or inspect transcript evidence. Calling `transcribe(...)` directly raises an error; the specialist decides which evidence is relevant and the host performs the import or transcription. Defaults are ElevenLabs Scribe v2, automatic language detection, speaker diarization, and mandatory word timestamps. Configure the descriptor before the agent call when the defaults are not appropriate: ```python # Pin a language or disable diarization while keeping ElevenLabs Scribe v2. transcribe.set_config(language="en", diarize=False) # Select the explicit WhisperX fallback and its default model. Other settings # retain their current values unless reset explicitly. transcribe.set_config(provider="whisperx", language=None, diarize=True) ``` Configuration applies to subsequent grants in the current Python process. Supported keys are `provider`, `model`, `language`, and `diarize`; word timestamps cannot be disabled. Set every non-default choice before making the agent call so the effective configuration is explicit in that task's cache identity. Top-level `.json` files supplied beside media are uploaded as untrusted transcript candidates. They are not automatically associated with a source and are never treated as transcripts merely because of their filename. An enabled agent first inspects the candidates and may ask the host to validate and import one; only if no candidate fits should it compute a transcript for selected audio or video. The importer accepts Scrambo word-timed `segments[].words[]` JSON and native ElevenLabs `words[]` JSON, normalizes the selected evidence, preserves the uploaded raw payload, and publishes the working transcript used downstream. Grant transcript access at the earliest specialist that needs it. For example, grant it to scout when quotes determine source selection, to storyboard when word timing affects narrative structure, or to a direct author when there is no separate scout/storyboard pass. The caption specialist consumes the working transcript established upstream; it cannot create one itself. ### `source.prepare_agent.prepare(...)` [#sourceprepare_agentprepare] ```python prepared = source.prepare_agent.prepare( "Create H.264/AAC working copies of the three camera originals, extract a " "clean WAV from interview.mov, and concatenate exterior_part_1.mov and " "exterior_part_2.mov in that order. Preserve the originals.", name="editor-ready-media", ) ``` Preparation creates mechanical source derivatives and registers them so later agents can use them like other media. Supported work includes transcoding, trimming, concatenating, muxing, extracting audio, and extracting frames. Keep preparation requests mechanical and explicit. Use the scout or storyboard for creative selects, and the author for timeline construction. A preparation request should name inputs, desired outputs, formats, ordering, and technical constraints without asking for a montage or an edit. For a planned edit, scout the originals first, run preparation, and then tell the storyboard to prefer matching prepared assets. This preserves an editorial brief grounded in the camera originals while giving the author editor-friendly media. ### Deterministic generation tools [#deterministic-generation-tools] Import deterministic generation separately from the source-agent facade: ```python from scrambo.tools.genAI import img2video, voiceover ``` Both tools write media into `source/`, register it in `manifest.sources[]`, and return a normal `Artifact`. Matching requests reuse content-addressed outputs. Each direct call defaults to a `$5` spend cap, and cached work costs nothing. These calls do not require `editor.start()` unless the program proceeds to timeline editing. In the thin cloud SDK, vendor credentials stay on the Scrambo server. Local `.json`, `.txt`, and `.md` control files are read by the SDK and canonicalized before submission. Every first/last frame must have been part of the initial `editor.open(input=...)` upload; cloud generation does not late-upload frames in this MVP. If a generation control `.json` is also top-level inside the initial input directory, it is uploaded as a transcript candidate as well; keep control files outside that directory when they are not intended as run evidence. Create voiceover from literal text or a `.txt`/`.md` file: ```python voice = voiceover( "A quieter way to start the day.", voice="rachel", name="brand-voiceover", budget_usd=5.0, ) ``` Create one or more motion clips from text and optional first/last images: ```python footage = img2video( [ { "name": "coffee-pour", "seed_image": "./stills/cup.jpg", "prompt": "Slow macro push-in as coffee pours into the ceramic cup; " "preserve the cup design and morning window light.", "duration": 4.0, }, { "name": "pack-shot", "seed_image": "./stills/package.jpg", "last_frame": "./stills/logo-lockup.jpg", "prompt": "Controlled tabletop dolly ending on the supplied logo frame; " "keep all packaging text unchanged.", "duration": 4.0, }, ], resolution="720p", aspect_ratio="16:9", name="generated-product-shots", ) ``` `img2video` accepts one shot dictionary, a list, or a compatible `prompts.json` path. Every shot requires a first-frame image; an optional last frame requests interpolation. Its call-level arguments include `duration`, `vendor`, `resolution`, `aspect_ratio`, `name`, and `budget_usd`; a per-shot duration overrides the default. Text-to-video is intentionally outside this MVP. `voiceover` accepts literal script text or a `.txt`/`.md` path and requires a `voice`. Its optional arguments are `vendor`, `name`, and `budget_usd`. Unknown pricing is rejected before vendor submission. If a cached artifact points to a missing output, Scrambo runs the deterministic kernel again; a surviving content-addressed file is adopted and re-registered without spending. ### `source.generate_agent.create(...)` [#sourcegenerate_agentcreate] Use the specialist when the prompt should decide what to generate: ```python from scrambo import source, timeline from scrambo.tools.genAI import img2video, voiceover brief = source.generate_agent.create( "Create a narrated vertical tour from the listing photos.", tools=[img2video, voiceover], budget_usd=5.0, max_calls=4, name="generated-assets", ) plan = timeline.storyboard_agent.plan(brief, "Build a 30-second tour") # Equivalent run-directory handoff; consumes the accepted source_brief.json: plan = timeline.storyboard_agent.plan("Build a 30-second tour") ``` The specialist receives only the registered Scrambo tool callables explicitly listed in `tools`; arbitrary Python functions are rejected. An empty tool list fails before the provider is invoked. The specialist inspects seed images, creates only requested assets, inspects generated video contact sheets, and writes one complete SourceBrief v2. It does not create a generation-specific root contract. `budget_usd` is cumulative across uncached vendor work in that `create()` turn. `max_calls` is the cumulative requested-output cap, not an LLM iteration count: a batch of three video requests consumes three, and a cached output still consumes one slot while spending zero dollars. The default is four outputs. Budget, output-cap, unknown-price, and generation-tool errors fail the task. There is no automatic specialist revision after the paid turn; successful outputs remain content-addressed and can be adopted free on a rerun. The portable cloud SDK exposes the same registered tool identities and sends only their stable allowlisted IDs to the server. --- # Storyboarding > Turn accepted source evidence into an ordered, timed narrative plan. ```python plan = timeline.storyboard_agent.plan(brief, prompt, tools=(), name="storyboard") # Or consume the accepted source_brief.json already in the current run: plan = timeline.storyboard_agent.plan(prompt, tools=(), name="storyboard") ``` The explicit form fingerprints the passed source-brief artifact. The prompt-only form parses the current `source_brief.json`, grounds it against `manifest.json`, and fingerprints both files so either source selection or generated-source changes invalidate a cached plan. Both return a structured plan with a canvas, total duration, ordered sections, and grounded source windows. Pass `tools=[transcribe]` when the storyboard itself needs transcript evidence that an earlier pass did not establish. This grant applies only to that planning call. Use its prompt to define the edit's architecture: * target duration and orientation; * section order and approximate timing; * which audio is the narrative spine; * when B-roll should cover dialogue; * montage rhythm or beat relationship; * where titles, captions, or visual breathing room are needed; * required opening and closing behavior. Avoid vague requests such as `make it cinematic`. Say what cinematic means for this piece: perhaps slow establishing shots, longer holds, low transition density, natural sound at scene changes, and no animated text. --- # Timeline agents > The author and the graphics, sound, titles, and captions refinement specialists. Every timeline agent returns an `Artifact` and promotes a new current revision. Later timeline calls build on that current revision unless the author is given a storyboard plan, which asks it to construct the complete planned edit. ### `timeline.author_agent.edit(...)` [#timelineauthor_agentedit] The author accepts three request types: | Request | Effect | | --------------------- | ------------------------------------------------------------------------------------------ | | `str` | Creates a timeline directly, or revises the current timeline from a free-form instruction. | | Storyboard `Artifact` | Builds the complete rough cut described by `result["plan"]`. | | `ValidationReport` | Repairs the report's timeline revision while preserving work that already passes. | The author owns structural editing: selecting and trimming clips, arranging tracks, preserving or muting source audio, placing cutaways, creating montages, and making timeline-wide repairs. All three request forms accept `tools=[transcribe]`. Use it for a direct-author workflow that must discover spoken content or for an author repair that truly needs new transcript evidence; omit it when the accepted brief/plan and working transcript already contain what the author needs. ### Refinement specialists [#refinement-specialists] Refinement specialists require an existing timeline. Each exposes `.edit(request, *, name=None)` and accepts either a string or a `ValidationReport`. | Agent | Best used for | | ------------------------- | ----------------------------------------------------------------------- | | `timeline.graphics_agent` | Shapes, panels, backings, visual accents, and other graphic elements. | | `timeline.sound_agent` | Music treatment, sound effects, audio balance, and sound-design timing. | | `timeline.titles_agent` | Opening titles, section cards, lower thirds, end cards, and typography. | | `timeline.captions_agent` | Transcript-aligned spoken-word captions and caption styling. | Example layered pass: ```python timeline.titles_agent.edit( "Add a restrained opening title for 'Field Notes' and a two-line end card. " "Use warm white type, generous margins, and no other text.", name="titles", ) timeline.captions_agent.edit( "Caption only the interview speech. Use sentence-aware two-line groups, " "highlight the current word, keep clear of faces, and do not caption music.", name="captions", ) timeline.sound_agent.edit( "Add subtle transitions at the three section changes. Keep dialogue dominant, " "duck music under speech, and avoid effects during the closing sentence.", name="sound-design", ) ``` Give each specialist only the work it owns. A caption request should not ask for a recut; a sound request should not redesign titles. If a refinement reveals a structural problem, send that change to the author first. Order refinement passes intentionally. For example, lock the picture before caption timing and sound alignment, and make large structural repairs before fine typography adjustments. --- # Validation and repair > Run deterministic validators and route findings to the right specialist. ```python report = timeline.validate("all,-edit_quality.sfx", name="quality-check") ``` Validation checks the current timeline deterministically and returns a `ValidationReport`. Pass either a validator selection spec or an immutable, serializable `ValidationPolicy` — never a natural-language prompt. A selection is a comma-separated list of exact validator names or dotted group prefixes: ```python timeline.validate("all") timeline.validate("edit_quality.caption") timeline.validate("all,-edit_quality.sfx") timeline.validate("edit_contract.duration_matches") ``` * `all` means every registered validator. * `none` selects no validators. * A prefix such as `edit_quality.caption` selects that whole quality group. * A leading `-` excludes a name or group. * With exclusions but no positive token, selection starts from all validators. * An unknown token raises an error and lists the valid names. ## Validator catalog [#validator-catalog] Validators split into two families. **`edit_contract.*`** checks are hard invariants of a correct edit — the caller's declared canvas, target duration, and plan are realized exactly. **`edit_quality.*`** checks are editorial judgments you opt into and tune. | Group | What it checks | | --------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | | `edit_contract.*` | Canvas, optional target duration, plan realization, no video gaps, captions/titles/graphics/sound realization, and loaded fonts. | | `edit_quality.pacing.*` | Short shots and parent flashes, from both the live edit and the plan. | | `edit_quality.caption.*` | Cadence, segmentation, row layout, and balance. | | `edit_quality.typography.*` | Minimum text size and overlap. | | `edit_quality.sfx.*` | Density and mix levels. | | Other quality | `edit_quality.coverage.broll`, `edit_quality.structure.plan`. | `edit_contract.duration_matches` is skipped when the project has no declared target duration. For a meaningful duration gate, give the storyboard or direct author an explicit target duration. `program.safe` and `program.executable` are mandatory specialist-transaction verification — they are never selectable through `timeline.validate()`. A specialist is promoted only after those checks and its relevant `edit_contract.*` invariants pass. Specialists never run `edit_quality.*` automatically: editorial acceptance is caller-owned, which is what the rest of this page is about. ## Policies with settings [#policies-with-settings] For quality checks you want to tune, build a `ValidationPolicy`. It bundles a selection with per-validator settings, validates both at construction time, and is immutable and serializable so you can reuse it across runs. ```python from scrambo import ValidationPolicy policy = ValidationPolicy( "edit_quality.caption,edit_quality.typography", settings={ "edit_quality.caption.cadence.minimum_duration": {"min_duration": 0.25}, }, ) report = timeline.validate(policy, name="quality-check") ``` Settings are keyed by a concrete `edit_quality.*` validator and use that validator config's field names. Contract and program checks do not accept caller settings — passing settings for them raises. ## Inspecting a report [#inspecting-a-report] ```python if report.passed: print("ready") else: print(report.md) for finding in report.findings: print(finding) ``` Call `report.require_passed()` to fail the program when any selected validator fails. ## The repair loop [#the-repair-loop] To attempt repair, hand the failing report to the specialist that owns the problem, then re-check. `timeline.ensure()` makes this caller-owned loop transparent while keeping routing explicit: ```python report = timeline.ensure( "edit_quality.caption,edit_quality.typography", repair_with=timeline.captions_agent, repairs=2, name="caption-quality", require_passed=True, ) ``` `ensure()` runs the policy, hands any failure to the one `repair_with` specialist, and revalidates — up to `repairs` times. Stages are deterministically named `caption-quality-repair-N` and `caption-quality-recheck-N`. It performs no routing inference: choose the one specialist that owns the selected quality policy. With `require_passed=False` (the default), an exhausted loop returns the final failed report for custom handling. Use a focused specialist for focused findings — `captions_agent` for caption layout or timing, `sound_agent` for mix levels, `titles_agent` for typography. Use `author_agent` for cross-cutting or structural findings. If you want the loop spelled out instead of `ensure()`: ```python selection = "edit_contract,edit_quality.caption,edit_quality.typography" report = timeline.validate(selection, name="quality-check") for attempt in range(2): if report.passed: break timeline.author_agent.edit(report, name=f"quality-repair-{attempt + 1}") report = timeline.validate(selection, name=f"quality-check-{attempt + 2}") report.require_passed() ``` --- # Artifacts > Immutable Artifact and ValidationReport results and how caching uses them. Agent calls return immutable `Artifact` objects. They are both useful results for your code and typed handoff references for later agents. ```python from scrambo import Artifact, ValidationReport ``` | Field or method | Meaning | | --------------- | ------------------------------------------------------------------------------------------------ | | `name` | The stage name supplied by your program. | | `kind` | The kind of agent task that produced it. | | `execution_id` | The unique accepted execution of that task. | | `result` | Structured result data, such as `result["brief"]`, `result["plan"]`, or generated media records. | | `llm_invoked` | Whether this result required a model call. | | `cached` | Whether Scrambo reused matching prior work. | | `input_ref()` | A compact reference; usually Scrambo creates this for you when you pass an artifact onward. | `ValidationReport` adds: | Field or method | Meaning | | ------------------ | ----------------------------------------------------------- | | `passed` | `True` when all selected validators passed. | | `findings` | Structured findings suitable for logging or custom routing. | | `md` | A readable Markdown report. | | `require_passed()` | Raises `ScramboValidationError` if the report failed. | Stable prompts and stable stage names allow Scrambo to reuse work when inputs and upstream artifacts have not changed. A changed prompt, media input, base timeline revision, model setting, or upstream artifact creates new work. Treat caching as an optimization, not as program state: always express the complete intent needed by the call. --- # Prompting each agent well > Assign decisions to the right specialist and make constraints testable. A strong Scrambo program assigns decisions to the right specialist and makes constraints testable. ### Include concrete editorial requirements [#include-concrete-editorial-requirements] Useful prompt ingredients include: * deliverable and audience; * exact or bounded duration; * canvas or orientation; * named source roles and required assets; * narrative order; * pacing and cut style; * audio policy; * text that must appear exactly; * accessibility requirements; * explicit exclusions; * a clear definition of the ending. Instead of: ```text Make a cool social video. ``` Prefer: ```text Create a 12-second 9:16 teaser for first-time runners. Start with the shoe touching pavement, reveal the group by 3 seconds, cut on the next four drum beats, and hold the smiling finish for the final 2 seconds. Use no dialogue, no captions, and no more than one title: RUN TOGETHER. ``` ### Repeat critical constraints at handoffs [#repeat-critical-constraints-at-handoffs] The storyboard receives the source brief, and the author receives the plan, but specialists do not share hidden conversational memory. Put selection evidence in the scout prompt, structure in the storyboard prompt, and styling in the relevant refinement prompt. Repeat non-negotiable requirements—duration, required wording, source-audio policy—where the responsible agent needs them. ### Ask for observable outcomes [#ask-for-observable-outcomes] Prefer `keep captions within two lines and clear of faces` over `make captions nice`. Prefer `duck music beneath every spoken section` over `balance the audio`. Concrete outcomes give both the editing agent and validators something to act on. --- # Common mistakes > Frequent pitfalls to avoid when writing Scrambo programs. * Calling a facade before `editor.open(...)`. * Editing the timeline before `editor.start()` has connected the browser. * Calling a refinement specialist before a first timeline exists. * Passing a plain dictionary where storyboard or author expects an `Artifact`. * Asking the scout to edit, the prepare agent to make creative selects, or a caption specialist to restructure the whole video. * Hiding the narration source or target duration in vague language. * Asking for transcript-based decisions without granting `tools=[transcribe]` to the scout, storyboard, or author that must make them. * Calling `transcribe(...)` directly or assuming a transcript grant persists to the next specialist. * Assuming an uploaded JSON file is automatically associated with similarly named media; it remains an untrusted candidate until an enabled agent imports it. * Passing prose to `timeline.validate(...)`; it accepts a selection spec or a `ValidationPolicy`, not a natural-language prompt. * Assuming every validation group applies to every edit. Select checks relevant to the layers you actually created. * Closing the browser tab during a run. * Depending on programmatic export in code meant to run against the cloud API. The most reliable programs are explicit about outcomes, keep agent responsibilities narrow, pass artifacts directly, and validate the properties that matter for the deliverable. --- # API reference > The whole call surface on one page: facades, methods, arguments, and returns. The complete Scrambo call surface in one place. This is a lookup table, not a tutorial — each row links to the narrative page with the judgment calls (when to use it, how to prompt it). For the arc from install to first edit, see [Start here](/docs/start-here). ```python from scrambo import editor, source, timeline from scrambo import Artifact, ValidationReport from scrambo.tools import transcribe from scrambo.tools.genAI import img2video, voiceover ``` Every agent call takes a `name=` stage label and returns an `Artifact`. Calls are synchronous; timeline agents edit the current revision in call order. ## `editor` — session and browser editor [#editor--session-and-browser-editor] | Call | Key arguments | Returns / effect | | --------------------- | --------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- | | `editor.open(...)` | `project`, `input` **(required)**, `canvas=(w, h)`, `provider`, `model`, `thinking` | Creates or resumes the project session. | | `editor.start()` | — | Connects to the browser editor; call before any timeline op. Keep the tab open. | | `editor.close()` | — | Releases the session. Optional — Scrambo auto-closes on program exit. | | `editor.export(path)` | — | **Not supported on the cloud SDK** (raises `ScramboError`). Export from the editor's Export control. | See [The session lifecycle](/docs/session-lifecycle) · [Supplying media and creative constraints](/docs/media-and-constraints). ## `source` — media before or alongside the timeline [#source--media-before-or-alongside-the-timeline] These agents do **not** change the live timeline. | Call | Key arguments | Returns | | ------------------------------------------- | ------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------- | | `source.scout_agent.brief(prompt, ...)` | `tools=()`, `name=` | `Artifact` — SourceBrief v2 in `result["brief"]`. | | `source.prepare_agent.prepare(prompt, ...)` | `name=` | `Artifact` — registered mechanical derivatives (transcode, trim, concat, mux, extract). | | `source.generate_agent.create(prompt, ...)` | `tools=[...]` **(required, non-empty)**, `budget_usd=5.0`, `max_calls=4`, `name=` | `Artifact` — SourceBrief v2 over generated assets in `result["brief"]`. | See [Source agents](/docs/source-agents). ## `timeline` — plan, author, refine, validate [#timeline--plan-author-refine-validate] | Call | Accepts | Returns / effect | | ------------------------------------------------------ | ----------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------- | | `timeline.storyboard_agent.plan([brief,] prompt, ...)` | optional brief `Artifact`, `prompt`; `tools=()`, `name=` | `Artifact` — ordered, timed plan in `result["plan"]`. | | `timeline.author_agent.edit(request, ...)` | `str` \| storyboard `Artifact` \| `ValidationReport`; `tools=()`, `name=` | `Artifact`; promotes a new current revision. Structural editing. | | `timeline.graphics_agent.edit(request, ...)` | `str` \| `ValidationReport`; `name=` | `Artifact`; shapes, panels, backings, accents. | | `timeline.sound_agent.edit(request, ...)` | `str` \| `ValidationReport`; `name=` | `Artifact`; music, SFX, balance, ducking. | | `timeline.titles_agent.edit(request, ...)` | `str` \| `ValidationReport`; `name=` | `Artifact`; titles, cards, lower thirds, typography. | | `timeline.captions_agent.edit(request, ...)` | `str` \| `ValidationReport`; `name=` | `Artifact`; transcript-aligned captions. | | `timeline.validate(policy, ...)` | validator `selection` spec or `ValidationPolicy` (not a prompt); `name=` | `ValidationReport`. | | `timeline.ensure(policy, repair_with=, ...)` | `selection`/`ValidationPolicy`; `repair_with=` one specialist; `repairs=`, `name=`, `require_passed=` | `ValidationReport`; validates, routes failures to `repair_with`, re-checks. | Refinement specialists (`graphics`/`sound`/`titles`/`captions`) require an existing timeline. Only `author_agent` and the three `source`/`storyboard` callables accept `tools=[transcribe]`. See [Two ways to create the first timeline](/docs/first-timeline) · [Storyboarding](/docs/storyboarding) · [Timeline agents](/docs/timeline-agents) · [Validation and repair](/docs/validation-and-repair) · [Choose the right workflow](/docs/workflows). ## Tools (opt-in, `scrambo.tools`) [#tools-opt-in-scrambotools] | Tool | Grant / call | Notes | | ------------------------------ | ------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------- | | `transcribe` | `tools=[transcribe]` on scout/storyboard/author; `transcribe.set_config(provider, model, language, diarize)` | Per-call, fail-closed grant; does not persist. Calling `transcribe(...)` directly raises. Default ElevenLabs Scribe v2. | | `img2video(shots, ...)` | `duration`, `vendor`, `resolution`, `aspect_ratio`, `name`, `budget_usd=5.0` | `shots`: one dict, a list, or a `prompts.json` path. Each shot needs a first frame; optional `last_frame` interpolates. Returns `Artifact`. | | `voiceover(text_or_path, ...)` | `voice` **(required)**, `vendor`, `name`, `budget_usd=5.0` | Literal script text or a `.txt`/`.md` path. Returns `Artifact`. | Deterministic generation tools write media into `source/`, register it in the manifest, and reuse content-addressed outputs for free on a rerun. See [Source agents](/docs/source-agents). ## Result types [#result-types] `Artifact` (returned by every agent call): | Field / method | Meaning | | -------------- | ------------------------------------------------------------------------------------- | | `name` | Stage name your program supplied. | | `kind` | Kind of agent task that produced it. | | `execution_id` | Unique accepted execution of that task. | | `result` | Structured data, e.g. `result["brief"]`, `result["plan"]`, media records. | | `llm_invoked` | Whether a model call was required. | | `cached` | Whether Scrambo reused prior work. | | `input_ref()` | Compact handoff reference (usually created for you when you pass an artifact onward). | `ValidationReport` (returned by `timeline.validate`) adds: | Field / method | Meaning | | ------------------ | ----------------------------------------------------- | | `passed` | `True` when all selected validators passed. | | `findings` | Structured findings for logging or custom routing. | | `md` | Readable Markdown report. | | `require_passed()` | Raises `ScramboValidationError` if the report failed. | Pass `Artifact`s onward directly — never copy `.result` into the next call. See [Artifacts](/docs/artifacts). ## Validator selection spec [#validator-selection-spec] `timeline.validate(policy)` takes a comma-separated list of exact validator names or dotted group prefixes — `all`, `none`, a prefix like `edit_quality.caption`, and a leading `-` to exclude (e.g. `"all,-edit_quality.sfx"`). An unknown token raises and lists valid names. Pass a `ValidationPolicy` to bundle a selection with per-validator quality settings. ### How a check reports [#how-a-check-reports] Every validator returns structured findings. Each finding carries a **severity** (`fail` or `warn`), a plain-language message, and usually a **target** — the exact clip, caption group, or plan section it points at — plus a suggested fix. `report.passed` is `False` only if some selected check produced a `fail`; a `warn` is advisory and does not fail the report. `report.findings` is the structured list for custom routing; `report.md` is the same content rolled up as readable Markdown with fixes ordered by priority. Many checks **no-op to a pass when the layer they judge doesn't exist** (no captions authored, no plan, no sound), so selecting a group your edit didn't touch is harmless. ### `edit_contract.*` — hard invariants [#edit_contract--hard-invariants] These enforce that the edit *is what was declared*. Each is verified automatically when the owning specialist is promoted, so a caller usually selects them only as a final gate. | Validator | What it checks | Runs when | On a violation (`fail`) | | ----------------------------------- | ---------------------------------------------------------------------------- | --------------------------------------------------------------------------------------- | --------------------------------------------------- | | `edit_contract.canvas_matches` | Timeline resolution equals the `editor.open(canvas=…)` you declared. | Always. | Reports the drifted dimensions. | | `edit_contract.duration_matches` | Final length is within tolerance of the declared target duration. | Only when a target duration was set (storyboard or author). Skipped silently otherwise. | Reports measured vs. target delta. | | `edit_contract.video_has_no_gaps` | The visible video track has no uncovered time from first to last shot. | Once a timeline exists. | Points at each uncovered interval. | | `edit_contract.video_matches_plan` | Video shots realize the plan's clip IDs, order, and section windows exactly. | Only with a `plan.json`. | Names the missing, extra, or misplaced shot. | | `edit_contract.captions_match_plan` | Captions realize the planned caption elements and timing. | Only when the plan declares captions. | Names the unrealized or drifted caption. | | `edit_contract.titles_match_plan` | Title/card elements match the plan's declared IDs and placements. | Only when the plan declares titles. | Names the missing or altered title. | | `edit_contract.graphics_match_plan` | Graphic elements match the plan's declared IDs and placements. | Only when the plan declares graphics. | Names the missing or altered graphic. | | `edit_contract.sound_matches_plan` | Music/SFX elements realize the planned sound layer. | Only when the plan declares sound. | Names the missing or misplaced audio. | | `edit_contract.fonts_loaded` | Every font family/weight/style used by text was actually loaded. | When text clips exist. | Names the unloaded face to `client.load_font(...)`. | ### `edit_quality.*` — editorial judgments [#edit_quality--editorial-judgments] Opt-in taste checks. They never run automatically — acceptance is caller-owned — and each accepts per-validator settings through `ValidationPolicy` (keyed by the concrete validator name). Their severity scales with how far off the edit is, so they emit `warn` for borderline cases and `fail` only for clear defects. | Validator | What it checks | Runs when | Typical fix | | ----------------------------------------------- | -------------------------------------------------------------------------- | ----------------------------------- | ------------------------------------------------ | | `edit_quality.pacing.short_shots` | Shots aren't shorter than a readable hold. | On the video track. | Lengthen, merge, or replace the flashed shot. | | `edit_quality.pacing.parent_flashes` | The return to the speaker between cutaways isn't a second jump cut. | When cutaways sit over a base shot. | Hold the return longer or extend the cutaway. | | `edit_quality.coverage.broll` | Planned cutaway/B-roll coverage is present and long enough to read. | Only with a plan. | Add or lengthen the missing cover shot. | | `edit_quality.structure.plan` | Section order and boundaries follow the planned structure. | Only with a plan. | Re-order or re-time sections to the plan. | | `edit_quality.caption.cadence.minimum_duration` | Each caption stays on screen long enough to read (tunable `min_duration`). | When captions exist. | Extend or merge the short caption item. | | `edit_quality.caption.cadence.cut_clearance` | Caption groups don't straddle a shot change. | When captions exist. | Nudge the group clear of the cut. | | `edit_quality.caption.segmentation` | Words in a planned group reveal and clear together as one unit. | When captions exist. | Regroup so the phrase enters and exits together. | | `edit_quality.caption.layout.row_layout` | Caption rows sit in tidy bands with sane line spacing and word gaps. | When captions exist. | Adjust line height / row banding. | | `edit_quality.caption.layout.balance` | Groups read as horizontally centered by measured width. | When captions exist. | Re-center the group on its true width. | | `edit_quality.typography.minimum_size` | No text falls below a legible minimum size. | When text clips exist. | Raise `fontSize`. | | `edit_quality.typography.overlap` | Text rows/clips don't unintentionally overlap. | When text clips exist. | Fewer words per row, or re-measure the layout. | | `edit_quality.sfx.density` | SFX trigger cadence is neither too sparse nor too busy. | When a sound plan exists. | Adjust trigger cadence. | | `edit_quality.sfx.levels` | Music/SFX gain sits in a sane mix under narration. | When a sound plan exists. | Adjust gain or ducking. | `program.safe` and `program.executable` are internal transaction checks and are never selectable through `timeline.validate()`. See [Validation and repair](/docs/validation-and-repair). --- # Complete examples > End-to-end programs: caption video, generative listing tour, and interview. ### Example: simple caption video [#example-simple-caption-video] ```python import os from pathlib import Path from scrambo import editor, source, timeline from scrambo.tools import transcribe editor.open(project="reel-with-captions", input="...", canvas=(1080, 1920)) editor.start() timeline.author_agent.edit( "Make a polished 15-second vertical house tour reel from these clips, transcribe voiceover.mp3 as the narrative spine.", tools=[transcribe] ) timeline.captions_agent.edit("Create word-reveal captions from voiceover.mp3 transcript, use fade-in animation.",) ``` ### Example: generative listing video from photos [#example-generative-listing-video-from-photos] ```python import os from pathlib import Path from scrambo import editor, source, timeline from scrambo.tools.genAI import img2video, voiceover from scrambo.tools import transcribe # Listing stills for 13430 Patriot Wy SE, Renton, WA. Override with INPUT = Path("...") editor.open( project="renton-home-listing-tour", input=str(Path(INPUT)), canvas=(1920, 1080), # 16:9 tour provider="codex", model="gpt-5.6-luna", # or gpt-5.6-terra ) img2video.set_config( model="veo-3.1-lite-generate-preview", resolution="720p", duration_seconds=6.0, ) voiceover.set_config(model="eleven_multilingual_v2") editor.start() # Use GenAI to generate assets, then create editorial brief. # The brief describes and label assets for downstream edit brief = source.generate_agent.create( "Create a narrated 16:9 tour from the listing photos of this Renton custom home. Turn the strongest stills into slow, low-motion 6-second push-ins (24mm interiors, 28mm exteriors) that preserve the exact architecture, and write a warm, concise narration that walks a buyer from the front exterior through the kitchen and great room out to the deck and backyard. Use listing_description.txt and realestate_prompting_guide.md as references.", tools=[img2video, voiceover], budget_usd=5.0, max_calls=10, name="listing-tour-generated", ) # Plan the edit from the generated brief. plan = timeline.storyboard_agent.plan( brief, "Plan a real-estate listing reel that matches the exact length of the voiceover audio file, which is the narrative spine throughout. Import or compute the voiceover transcript so section timing follows the narration's phrase boundaries. Cut on narration phrase boundaries with clean hard cuts, no dialogue other than the voiceover. If background_music is used, keep it as a very low bed under the narration.", tools=[transcribe], name="listing-storyboard", ) # Edit on the timeline timeline.author_agent.edit(plan, name="listing-roughcut") # caption timeline.captions_agent.edit( "Add transcript-aligned captions for the voiceover narration only, styled as a premium real-estate reel. Reveal one word at a time in sync with speech, and fade each word in as it appears -- no pop, bounce, or scale-in animation on any word, only a clean fade. Group words into short, sentence-aware phrases of at most three to four words per line, at most two lines on screen at once, centered horizontally in the lower third with generous side margins and tight, even line spacing so the block reads as one clean, compact unit -- not spread across the frame. Use a clean modern sans-serif, strong legibility with a subtle stroke or shadow so text stays readable over bright interiors and exteriors alike. Most words are clean white; pick out a few key words per sentence -- room names, standout features, and descriptive highlights such as 'open-concept', 'primary', or 'backyard' -- and render just those in bold with a warm, saturated accent color so they pop against the plain white words without ever feeling busy. Keep captions clear of the very top and bottom safe margins and do not caption background_music.", name="listing-captions", ) # sound design timeline.sound_agent.edit( "If background_music.mp3 is present in the timeline, keep it as a very low, unobtrusive bed -- the voiceover narration must stay clearly dominant at all times. Duck the music further under every spoken phrase, add a short fade-in at the open and fade-out at the close, and avoid any sound effects or transition stingers.", name="listing-sound", ) ``` ### Example: interview [#example-interview] This program uses the full controlled workflow while keeping each handoff focused: ```python from scrambo import editor, source, timeline from scrambo.tools import transcribe editor.open( project="founder-profile", input="./founder_media", canvas=(1080, 1920), ) editor.start() brief = source.scout_agent.brief( "Transcribe founder_interview.mov. Find one concise origin-story line, " "one concrete customer benefit, and a warm invitation. Find stable " "workshop, product-detail, and customer B-roll that directly supports " "those statements. Exclude setup footage and repeated takes.", tools=[transcribe], name="profile-source-brief", ) plan = timeline.storyboard_agent.plan( brief, "Create a 35-second vertical founder profile. Open with a 2-second visual " "hook, then origin, product proof, customer benefit, and invitation. Keep " "the interview audio continuous where possible and cover edits with B-roll. " "Reserve the final 3 seconds for a clean end card.", name="profile-storyboard", ) timeline.author_agent.edit(plan, name="profile-roughcut") timeline.titles_agent.edit( "Add a lower third with 'NIA CHEN — FOUNDER' on her first clear appearance, " "then an end card reading 'MADE FOR THE EVERYDAY'. Use clean sans-serif " "type and generous safe margins.", name="profile-titles", ) timeline.captions_agent.edit( "Add transcript-aligned captions for Nia's speech only. Use at most two " "lines, sentence-aware groups, and a subtle current-word highlight. Keep " "captions clear of the lower third and faces.", name="profile-captions", ) timeline.sound_agent.edit( "Use a warm, understated music bed. Keep speech clearly dominant, add only " "subtle workshop texture between spoken sections, and let the end card land " "without a loud effect.", name="profile-sound", ) selection = "edit_contract,edit_quality.caption,edit_quality.typography,edit_quality.sfx.levels" report = timeline.validate(selection, name="profile-final-check") if not report.passed: timeline.author_agent.edit(report, name="profile-final-repair") report = timeline.validate(selection, name="profile-recheck") report.require_passed() ```