# Customize Composition

> Validate the generated content against the composition's real variable schema and stage it for rendering. Use after generate-script and before render-and-return.

- Skill: `heygen-com/customize-composition` (Agent Skill)
- Install (CLI): `npx skillmds@latest add heygen-com/customize-composition`
- Raw SKILL.md: https://api.skillmd.com/api/skills/heygen-com/customize-composition/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: heygen-com (https://skillmd.com/u/heygen-com)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/heygen-com/customize-composition

---


# Customize the composition

Turn the content you wrote into a validated, render-ready bundle. This step
catches mistakes (wrong color format, a value that doesn't match a declared
variable) locally, so the render API never sees a bad payload.

You don't edit the composition's HTML. HyperFrames injects your variable values
at render time — the composition reads them with
`window.__hyperframes.getVariables()`. So "customizing" means producing a clean
`variables.json` and staging the composition directory.

## Steps

1. Make sure your generated content is at `/.agents/workspace/output/proposed.json`
   (from generate-script).
2. Validate it against the chosen composition and write the clean variables:

   ```bash
   python3 /.agents/workspace/scripts/customize.py \
     /.agents/workspace/compositions/<chosen-id> \
     /.agents/workspace/output/proposed.json \
     /.agents/workspace/output/variables.json
   ```

   - On success it writes `/.agents/workspace/output/variables.json` (your values plus
     declared defaults for anything you left out) and exits 0.
   - On failure it exits non-zero and prints exactly what's wrong (unknown
     variable, wrong type, a color that isn't a hex value, a number out of
     range). **Read the message, fix `proposed.json`, and run it again.** Do not
     skip ahead to rendering with an invalid file.
3. Copy the chosen composition into the output area so the render step has a
   self-contained directory:

   ```bash
   rm -rf /.agents/workspace/output/composition
   cp -r /.agents/workspace/compositions/<chosen-id> /.agents/workspace/output/composition
   ```

4. Bake in the voiceover. The renderer reads the `<audio>` `src` from static
   HTML, so substitute the synthesized URL into the staged composition (or strip
   the audio element if there's no narration):

   ```bash
   python3 /.agents/workspace/scripts/apply_voiceover.py \
     /.agents/workspace/output/composition \
     "$(cat /.agents/workspace/output/voiceover_url.txt 2>/dev/null)"
   ```

   **Free-composition case:** the shipped starters already have the voiceover
   `<audio>` slot. But if you **free-authored** the composition's HTML (custom
   video, not a starter), you MUST include the same slot or the video renders
   silent — `apply_voiceover.py` only substitutes a slot that exists. Put this
   inside the composition root, near the other timed elements, with the literal
   placeholder src (apply_voiceover fills it; it's stripped if there's no
   narration). Use a **real, literal `data-duration` in seconds** — your
   composition's own length — e.g. for a 10-second video:

   ```html
   <audio id="voiceover" data-start="0" data-duration="10"
          data-track-index="0" data-volume="1" src="__VOICEOVER_URL__"></audio>
   ```

   - The ONLY placeholder is `src="__VOICEOVER_URL__"` (apply_voiceover
     substitutes it). Every other attribute must be a concrete value — do not
     leave a literal like `<comp-duration>` in the HTML; the renderer parses
     `data-duration` as a number, and a non-number is silently dropped → silent
     video. (You may also omit `data-duration` entirely; it then defaults to the
     audio's own length.)
   - Do NOT use `data-has-audio` or `data-composition-variable` — the renderer
     reads a static `src`; the placeholder + apply_voiceover is the working path.

## Multi-turn edits

On a follow-up ("make the headline punchier", "switch to a dark background"),
you don't need to start over:

- Edit only the changed keys in `/.agents/workspace/output/proposed.json`.
- Re-run `customize.py` to re-validate and rewrite `variables.json`.
- Keep the same staged composition unless the user wants a different starter.

## Output of this step

- `/.agents/workspace/output/variables.json` — validated values.
- `/.agents/workspace/output/composition/` — the staged composition directory.

Then move to **render-and-return**.

