# Builder Wars promo video guide

Last checked: 2026-11-07 · version 2026-11-07.5 · this guide as markdown: https://builderwars.ai/guides/promo-video.md

How to make the promo video for a Builder Wars launch: written for AI agents to follow step by step, and for the
people they work with. Prices and versions change monthly, so check the vendor's pricing page before you spend money.

With the Builder Wars MCP tools, call `get_video_guide` with the draft's `app_id` to get this guide plus a brief and a
shot outline for that app.

## 1. What the game needs

- **One impressive video, usually 20–45 seconds.** The game doesn't measure length; follow the theme if it asks for one.
  X only counts a "video quality view" from 10 seconds (X's open-source ranking code). Name and hook on screen by 2 s.
- **The video is attached to the draft.** `ship_app` refuses a draft with no video. Either the player uploads the file
  in the launch page's video step (`https://builderwars.ai/launch/<app_id>`: MP4, WebM or MOV, up to 100 MB), or you call
  `set_video` with a public https `video_url`: a hosted MP4, or the player's X post once it is up.
- **The video is uploaded natively in the X launch post,** from the player's own account. The post must be newer than
  the draft, name the app and carry the uploaded file (a link isn't enough), or the launch is refused.
- **The grader reads words, not pixels.** It can't watch video. It scores "video sells the spec" (worth up to +10%
  starting users) only from `video_description`, one paragraph of at most 1,000 characters. A specific, accurate
  paragraph raises the score; leaving it out scores half; a vague or inaccurate one can score lower than none.
- **It is a real launch promo.** No disclaimers or mock-up labels in the video. `#BuilderWars` goes in the post text
  only (it ties the post to the game), never in the video.
- **Hard rules.** No real brands, logos, product names, people or cloned voices (your own real product is the
  exception). No fake endorsements. No health, financial-return or safety claims. No realistic news, disaster or
  conflict footage, and no public figures.
- **AI labels.** X offers an optional AI-content disclosure when posting; using it is the player's choice. X may label
  footage that carries provenance metadata on its own. Never strip watermarks or metadata.
- **Sponsored theme?** Follow its required-tool note and say you used the tool in the spec's theme-fit note and in the
  video description.
- **You never post on X.** You make the file and write the text; the player posts.

## 2. Pick a path

| Path | When | Main tools | About, for a 30 s promo (Nov 2026) |
|---|---|---|---|
| **Impressive** (default) | Every launch, unless the player won't spend anything | The product's UI and titles animated in code (HyperFrames or Remotion), 2–3 generated hero shots, a professional voice (ElevenLabs on a paid plan, or Gemini TTS), real sound design | about $2–35 |
| **Free and local** (fallback) | The player won't spend anything | HyperFrames, with the cinematic beats in code (kinetic type, full-frame UI push-ins, number counters), Kokoro voice, captions from the script, CC0 sound | $0 (the voice needs Python and uv) |
| **React** | Players who know React | Remotion in place of HyperFrames, same recipe | as above; Remotion is free for individuals and teams of up to 3 |
| **All generative, best quality** | Footage that looks filmed throughout | About 4 shots on Kling 4.0, Veo 3.1 or Seedance 2.5 | about $23–65 for 4 shots (Seedance: about $110 on fal) |
| **Real product** | A working app or site | A Playwright recording of the real UI inside the edit | $0 plus the voice |

The impressive range: 2–3 hero shots × about 8 s × about 3 takes = 48–72 generated seconds, which is about $2–3 on
Gemini Omni (two 360p drafts and one 720p final per shot), $11–17 on Kling 4.0 at 1080p or $19–29 on Veo 3.1 with
audio; plus the voice, ElevenLabs Starter at about $6/month or Gemini TTS at under $0.01.

## 3. The workflow

**Ask first.** Before you install a global package, a skill or an MCP server, create an account, or spend money, tell
the player what you want to install or buy and why, and wait for a yes. Show them the plan (step 2) before spending.

1. **Brief.** Read the spec sheet (name, pitch, audience, features, price) and the theme prompt. Format: **1080x1920
   (9:16) at 30 fps**; 1080x1080 only when most footage is a desktop screen recording. (X's developer docs cap *API*
   uploads at 1280x1024; posting from the X app or website takes 1080x1920, which is how players post.)
2. **Script and shot list.** About 2 words of voiceover per second (60–70 for 30 s). A beat every 1.5–3 s, each
   marked *cinematic* (a generated hero shot; on the free path, big kinetic type, the UI full-frame with a push-in, or a
   number counter), *UI* (the product's UI in code) or the *end card*:
   - 0–2 s **hook** (cinematic): striking on frame 0 (no fade from black: it is the thumbnail), moving by 0.5 s, the
     name and a 2–5 word hook by 2 s.
   - **promise** and **features** (UI): the product doing the job, 2–3 beats per feature (the action, then the
     result; 2–4 beats when there are fewer features), captions of 2–5 words.
   - **who it's for** (cinematic): the audience in the moment they need it, tied to the theme.
   - **end card** (3 s): name, tagline, price and a call to action, rendered in code.
3. **Cinematic shots.** Impressive path: 1–3 key images of the product, then 2–3 hero shots from them (section 5B).
   Free path: build them in code (section 5A).
4. **UI and titles in code.** Build the product's screens and the titles in HTML/CSS (section 5A) and animate them.
5. **Voice.** Generate the voiceover from the script (section 6). Keep the script: it is also your captions.
6. **Sound design.** One instrumental bed, plus sound effects on key beats (UI taps, transitions), mixed in the edit.
   Record every asset's source and license in `CREDITS.txt`.
7. **Captions in the edit.** Render them in the composition from the script timing, in the video's typeface and
   colors: X autoplays muted, so the words must be in the picture. ffmpeg burn-in (section 7) is the fallback.
8. **Assemble and render a draft.** Generated clips go into the code edit as video layers. Pass the quality bar
   (section 4) on stills and a contact sheet (agents can't watch video), then show the player.
9. **Finish.** Normalize loudness and encode for X (section 7).
10. **Check.** Run `check-x-video.sh`, fix every FAIL, and pass the quality bar again.
11. **Hand off.** Give the player the file. Save the video description and post text with `set_video` and
    `set_launch_post`, and tell the player how to attach and post the video (section 8).

## 4. Quality bar

Check every item on stills and the contact sheet before you show the player the draft, and again before you hand it
over. Fix and re-render until all pass.

1. **Frame 0 and the first 2 s.** Frame 0 is striking on its own, motion starts within about 0.5 s, and the name and
   hook are on screen by about 2 s.
2. **Pacing.** A cut or a clear change of motion every 1.5–3 s; no static hold longer than about 3 s.
3. **Motion.** Eased, never linear. Push-ins or parallax on stills. Transitions with a purpose, no stock wipes.
4. **Look.** One palette, at most 2 typefaces, high-contrast text, no AI-generated text in frames. Every word sits in
   the safe band, 168–1330 px from the top of 1920, and none under the right-hand action rail (HyperFrames' vertical
   reference: app chrome covers the rest).
5. **Consistency.** The product looks the same in every shot (same key image, same UI).
6. **Sharpness.** Native 1080x1920; crisp UI and text, never 720p UI scaled up.
7. **Story.** The product visibly does its main job (shown, not claimed), one clear promise, a strong end card.
8. **Sound.** A clear voice up front, music ducked under it, sound effects on key beats, about -14 LUFS, no clipping.
9. **Captions.** Synced to the voice, in the video's typeface and colors, never covering the product.
10. **The stranger test.** Would a stranger stop scrolling for this in the first second? If not, fix the hook and
    re-render.

## 5. Setup per path

### A. The edit: HyperFrames (UI, titles, captions)

HTML, CSS and GSAP compositions rendered to MP4 by headless Chrome and ffmpeg: the product UI you build in HTML *is*
the video. Apache-2.0, by HeyGen. Needs Node 22+ and ffmpeg. Pre-1.0 (0.9.x in Nov 2026): when a flag fails, read
`--help`.

```sh
export HYPERFRAMES_NO_TELEMETRY=1      # on by default; DO_NOT_TRACK=1 works too ("telemetry disable" writes ~/.hyperframes/config.json)
npx hyperframes doctor                 # Node, Chrome, ffmpeg, and the optional voice and transcription tools
npx hyperframes init promo --resolution portrait --skip-skills   # 1080x1920; installs no agent skills
cd promo && echo HYPERFRAMES_NO_TELEMETRY=1 >> .env   # read from the project's .env in every later shell
```

Its agent skills help a lot: ask the player, then `npx hyperframes skills update` (Claude Code plugin:
`claude plugin marketplace add heygen-com/hyperframes`, then `claude plugin install hyperframes@hyperframes`).

The free voice (Kokoro) needs Python and uv. Render it line by line (`script.txt`: one caption group per line), so
every line's start and length are known exactly: they time the captions and the `<audio data-start>` of each file.

```sh
uv venv .venv && uv pip install kokoro-onnx soundfile   # once; ask first: Python packages into ./.venv
```

```sh
export HYPERFRAMES_PYTHON="$PWD/.venv/bin/python"       # not read from .env: set it in every shell
VO_AT=0.5; ok=1; rm -rf vo; mkdir -p vo; n=0; t=$VO_AT; : > vo/lines.tsv   # tts --list shows the voices
while IFS= read -r line || [ -n "$line" ]; do
  [ -z "$line" ] && continue
  n=$((n+1)); f=$(printf 'vo/%02d.wav' "$n")
  npx hyperframes tts --voice af_heart --output "$f" -- "$line" < /dev/null > /dev/null || { echo "tts failed: $line"; ok=0; break; }
  d=$(ffprobe -v error -show_entries format=duration -of csv=p=0 "$f")
  printf '%s\t%s\t%s\n' "$t" "$d" "$line" >> vo/lines.tsv     # start, length, text
  t=$(awk -v t="$t" -v d="$d" 'BEGIN{printf "%.3f", t+d+0.25}')
done < script.txt
[ "$ok" = 1 ] && (cd vo && for f in [0-9][0-9].wav; do ffmpeg -y -v error -i "$f" -af apad=pad_dur=0.25 "p$f"; done &&
  printf "file '%s'\n" p[0-9][0-9].wav > list.txt) && ffmpeg -y -v error -f concat -safe 0 -i vo/list.txt -c copy voice.wav
```

The build loop:

```sh
npx hyperframes lint && npx hyperframes check --json        # layout, motion, contrast and audio audit
npx hyperframes snapshot . --at 2,10,20                     # stills to look at
npx hyperframes render --quality draft --output draft.mp4   # quick review copy
npx hyperframes render --fps 30 --output promo.mp4
```

- A draft render is Constrained Baseline at a low bitrate, so the check script WARNs on profile and bitrate: expected;
  the final encode (section 7) fixes it.
- Captions come from `vo/lines.tsv`: one caption group per line, at most 2 lines of about 32 characters.
- Word-level timings need `npx hyperframes transcribe`: whisper.cpp built from source, or
  `npx hyperframes models install parakeet`.
- Cinematic beats in code: the hook in huge kinetic type over moving color, the product's UI full-frame at 1.6–2× with
  a slow push-in, a number counting up to the result. No phone or browser mockups: show the UI itself.
- Generated clips go in as `<video>` layers. Give every `<audio>` a `data-duration` (without it, a test run got 150+
  `duplicate_audio_track` warnings). Keep compositions deterministic: paused timelines, no wall-clock time.
- Heed `composition_heavy_overlay_count_high`: about 40 blur, clip-path or radial-gradient layers can render as solid
  black for part of the film. The check script scans for black stretches.
- Rebuilding a real site's UI (`npx hyperframes capture <url>` grabs its screens, fonts and assets): the scaffold's
  unlayered `* { margin: 0; padding: 0 }` reset beats Tailwind v4's layered utilities, so delete it or wrap it in
  `@layer base`; Next.js puts font variables on a hashed class, so set `font-family` explicitly; UI scaled with CSS `zoom`
  is measured unzoomed by `text_below_floor`, so that warning can be wrong.

### B. Hero shots: generative video

For the moments code can't film: the product in a real setting, a character, a mood. Official access routes:

- **fal**: one account for most models (OAuth; it estimates cost before a run):
  `claude mcp add --transport http --scope user fal https://mcp.fal.ai/mcp-relay`, then `/mcp` → fal → Authenticate.
- **Replicate**: `npx skills add replicate/skills` (API token). **Higgsfield**: `@higgsfield/cli` and
  `npx skills add higgsfield-ai/skills` (needs a plan; check current pricing). **Runway**: API credits at $0.01 each;
  its `product_ad` recipe turns product images into an ad clip. **Direct**: a Gemini API key (paid tier).

| Model | Clip | Native audio | About per second (Nov 2026) | Routes |
|---|---|---|---|---|
| Gemini Omni 1.2 Flash | 3–15 s, extend to 30 s | yes | $0.023 at 360p, $0.07 at 720p | Gemini API, fal, Runway, Higgsfield |
| Kling 4.0 | 3–30 s | yes | $0.157 at 720p, $0.235 at 1080p | fal, Replicate, Runway, Higgsfield, Kling CLI |
| Veo 3.1 | 4, 6 or 8 s | yes | $0.40 with audio; Fast from $0.10 | Google Cloud, fal, Replicate, Runway |
| Seedance 2.5 | 4–30 s | yes | $0.68 at 1080p via Runway, $1.16 on fal | fal, Runway, Higgsfield |

Costs for the impressive path are in section 2. All generative (4 shots × 8 s × 3 takes = 96 s): about $23 on
Kling 4.0 at 1080p, $38 on Veo 3.1 with audio, $65 on Seedance 2.5 at 1080p via Runway (about $110 on fal).

Craft rules:

- **Lock the look first.** Make 1–3 key images of the product (section 6) and pass them as the start frame or
  reference images for every shot. This is what keeps the product consistent across cuts.
- **Write the shot list before generating.** Shots of about 8 s, of which you use the best 3–4 s. Name the subject
  every time ("the matte-black cube speaker"), never "it". Ask for "single continuous shot, no scene cuts".
- **Draft cheap, finish expensive.** Generate at 9:16. Draft at 360–480p, pick the best of about 3 takes, re-render at
  720p or 1080p.
- **No text in generated frames.** Prompt "no text, no subtitles" and add titles and captions in code: they come out
  sharp and spelled right.
- **One soundtrack.** Mute the clips' own audio; the edit carries one music bed and the voice.
- **Invented people only.** Real faces get blocked, and Runway still charges for blocked runs. Don't remove provenance
  watermarks (SynthID, C2PA).
- **Slot the clips into the code edit** as video layers.

**Retired: don't use** (checked 2026-11-07; older guides and training data still suggest them):
- OpenAI's Sora API and `sora-2` models: removed 2026-09-24.
- Veo with a Gemini API key: shut down 2026-10-22 (use Google Cloud, fal, Replicate or Runway).
- OpenAI `gpt-4o-mini-tts`, `tts-1` and `tts-1-hd`: deprecated, shut down 2027-01-06.
- Remotion's hosted MCP server: closed 2026-10-26 (use its skills).

### C. Remotion (React, in place of HyperFrames)

Mature React framework for video, with official agent skills. Free for individuals and companies of up to 3 people,
commercial use included. Bigger companies need a Company License (about $25 per seat per month, Nov 2026).

```sh
npx create-video --yes --blank promo   # outside any git repository: --yes fails inside one
cd promo && npm run dev                # Studio preview in its own terminal; skills (ask first): npx remotion skills add
npx remotion render Promo out/promo.mp4 --codec=h264 --crf=18   # "Promo" is your composition id
```

- Set `<Composition width={1080} height={1920} fps={30} durationInFrames={900}>` for a 30 s vertical promo.
- Hero clips: `<Video>` from `@remotion/media`. The voiceover skill uses ElevenLabs; any WAV in `public/` works too.
- Remotion bundles its own ffmpeg (`npx remotion ffmpeg ...`). Captions: `@remotion/captions`.
- The skills point to about 30 hosted meme sound effects (remotion.media: vine boom, Wilhelm scream...). Their rights
  aren't documented, so don't use them in a promo.

### D. Real product: record the UI with Playwright

Playwright (1.65 in Nov 2026) records a scripted walkthrough with an animated cursor, in real time, to WebM with no
audio: footage for the edit, not the whole promo. Via `npx` it needs no install; a global install or its skills are
ask-first.

```sh
playwright-cli() { npx -y @playwright/cli@latest "$@"; }   # needs Chrome, or: playwright-cli install-browser (ask first)
playwright-cli open http://localhost:5173
playwright-cli resize 1920 1080
playwright-cli video-start ui.webm --size=1920x1080 --cursor --fps=30   # without --size it records 800x800
# ... drive the app: click, type, scroll ...
playwright-cli video-stop
ffmpeg -i ui.webm -c:v libx264 -profile:v high -pix_fmt yuv420p -crf 18 -r 30 -movflags +faststart ui.mp4
```

Playwright MCP has the same tools behind `--caps=devtools`. Crisp stills animated with push-ins often beat footage.

## 6. Voice, music, images and captions

| Need | Free (fallback path) | Paid (default; about, Nov 2026) |
|---|---|---|
| Voiceover | **Kokoro-82M** (Apache-2.0, CPU): section 5A | **ElevenLabs `eleven_v4`**: about $0.04 per 30 s; commercial use needs Starter or above. Cheaper: Gemini 3.8 Flash TTS, under $0.01 per 30 s (doubles on 2027-01-01) |
| Music | **ACE-Step 1.5** (MIT, local), Stable Audio 3.0 Small or Medium (see the table below), or CC0 tracks | **ElevenLabs Music** `music_v2_5`, `force_instrumental`: about $0.08 per 30 s (Starter or above). Lyria 3.5: $0.08 per song |
| Sound effects | **Openverse** API, no key (below) | ElevenLabs `eleven_text_to_sound_v3` (pass the model id; the API default is older) |
| Key art and logos | SVG and HTML you write; FLUX.2 [klein] 4B locally | **Nano Banana 2.1** (about $0.03 per image, takes reference images); GPT Image 2 for transparent cut-outs |
| Captions | From your own script timing (the default) | Groq `whisper-large-v3-turbo`, $0.04 per hour of audio |

ElevenLabs for agents: `claude mcp add --transport http elevenlabs https://api.elevenlabs.io/v1/mcp`, or
`npx skills add elevenlabs/skills`.

Sound without a key: Openverse's `license=cc0` returns only CC0 (`license_type=commercial` also returns CC BY, which
needs credit). Each result has `license` and `attribution`; keep them in `CREDITS.txt`. Freesound results are
128 kb/s preview MP3s (originals need a Freesound account), and cdn.freesound.org can be slow.

```sh
curl -s "https://api.openverse.org/v1/audio/?q=upbeat+electronic&license=cc0" | jq -r '.results[] | "\(.duration) ms \(.url)"'
```

Picking music without listening: download a candidate as `music.wav`, then check the length, the loudness and how much
low end it has.

```sh
curl -fsSL -o track.mp3 "<url from the list>" && ffmpeg -y -v error -i track.mp3 -ar 48000 music.wav
ffprobe -v error -show_entries format=duration -of csv=p=0 music.wav
ffmpeg -hide_banner -i music.wav -af ebur128 -f null - 2>&1 | grep -E '^ +(I|LRA):'
for f in astats lowpass=f=120,astats; do ffmpeg -hide_banner -i music.wav -af $f -f null - 2>&1 | grep 'RMS level' | tail -1; done
```

A bed for a 30 s promo runs at least 30 s, is not too dynamic (LRA under about 8 LU), and has bass (the second RMS,
below 120 Hz, within about 10 dB of the first). ffmpeg has no tempo meter: prefer tracks tagged upbeat or with a BPM in the title.

### What is safe to post on X

Treat every Builder Wars post as commercial use: it promotes a product, and the player's account may be monetized.

| Source | OK? | Condition |
|---|---|---|
| ElevenLabs on a paid plan | yes | Made while subscribed, not with beta features |
| Gemini TTS, Nano Banana, Lyria | yes | Outputs carry SynthID; users must be 18+ (Gemini API terms); Gemini TTS has a free tier, where Google may use your data |
| Kokoro, FLUX.2 [klein] 4B, ACE-Step 1.5 | yes | Apache-2.0 / MIT (ACE-Step's model card allows commercial use) |
| Stable Audio 3.0 | yes | Commercial use needs a free registration with Stability AI; free under $1M annual revenue |
| CC0 (Openverse, Freesound) | yes | Nothing required |
| CC BY | yes | Credit the title, creator and license (with a link) in the post or a reply, and say "edited" if you changed it |
| Pixabay music and SFX | yes | No attribution needed; manual download (its API has no audio) |
| ElevenLabs or Suno free plans, CC BY-NC, "personal use only", XTTS v2, F5-TTS | no | Non-commercial (Suno's stays so after upgrading) |
| Remotion's hosted meme sound effects (remotion.media) | no | Rights not documented |
| Commercial songs, TikTok sounds, film scores | no | Takedowns, and repeat strikes can suspend the account |
| Real logos, product names, faces, or cloned voices of real people | no | Trademark and publicity rights |
| Generated video and images | check | Read the vendor's terms for your plan; free tiers may add a visible watermark or allow personal use only (Kling's, for example) |

## 7. Finish and check

Use ffmpeg 6 or newer. Captions need libass; check first:
`ffmpeg -hide_banner -filters | grep -E ' (subtitles|loudnorm|sidechaincompress) '`. On macOS, Homebrew's `ffmpeg`
has no libass (no `subtitles` filter, and no `fc-list`): install the keg-only `ffmpeg-full` instead,
`brew install ffmpeg-full` (ask first), then `export PATH="$(brew --prefix ffmpeg-full)/bin:$PATH"`. Add `-y` to overwrite
outputs when you re-run a command (without it ffmpeg stops to ask).

**Captions fallback.** Render captions in the edit (section 3, step 7). When you can't, make `captions.srt` from
`vo/lines.tsv` and burn it in styled like the video: its typeface (`fontsdir` holds its font files) and its colors.

```sh
awk -F'\t' 'function ts(s, m) { m = int(s * 1000 + 0.5); return sprintf("%02d:%02d:%02d,%03d", int(m / 3600000), int(m / 60000) % 60, int(m / 1000) % 60, m % 1000) }
  { print NR; print ts($1) " --> " ts($1 + $2); print $3; print "" }' vo/lines.tsv > captions.srt
ffmpeg -i promo.mp4 -vf "subtitles=captions.srt:fontsdir=fonts:force_style='FontName=Inter,Bold=1,FontSize=11,PrimaryColour=&H00FFFFFF,OutlineColour=&H00000000,BorderStyle=1,Outline=2,Shadow=0,Alignment=2,MarginV=100'" -c:v libx264 -crf 18 -pix_fmt yuv420p -c:a copy captioned.mp4
```

Sizes count against a 288-line canvas: `FontSize=11` is about 70 px and `MarginV=100` puts the bottom line at about
1,250 px, inside the safe band and clear of the action rail. Colors are `&HAABBGGRR`. libass silently swaps in
another font when `FontName` isn't found: check a still. From here on, use `captioned.mp4` wherever `promo.mp4` appears.

**Sound.** Best: mix voice, music and effects in the edit and render `promo.mp4`. To duck the music here instead,
render the edit with effects only (`data-hidden` on the voice and music `<audio>`, which HyperFrames leaves out of the
render), then add the voice and the ducked music. `VO_AT` is where the voice starts:

```sh
V=promo.mp4; VO_AT=0.5          # V=captioned.mp4 if you burned captions
DUR=$(ffprobe -v error -show_entries format=duration -of csv=p=0 "$V")
FO=$(awk -v d="$DUR" 'BEGIN{printf "%.3f", d-1.5}')
ffmpeg -i "$V" -i voice.wav -stream_loop -1 -i music.wav -filter_complex "\
[0:a]aresample=48000,aformat=sample_fmts=fltp:channel_layouts=stereo,apad=whole_dur=${DUR}[fx];\
[1:a]aresample=48000,aformat=sample_fmts=fltp:channel_layouts=stereo,adelay=${VO_AT}s:all=1,apad=whole_dur=${DUR},asplit=2[vo][sc];\
[2:a]aresample=48000,aformat=sample_fmts=fltp:channel_layouts=stereo,atrim=duration=${DUR},volume=0.28[mus];\
[mus][sc]sidechaincompress=threshold=0.01:ratio=20:attack=15:release=350[duck];\
[fx][vo][duck]amix=inputs=3:duration=first:normalize=0,afade=t=out:st=${FO}:d=1.5[a]" \
  -map 0:v -map "[a]" -c:v copy -c:a pcm_s16le premix.mov
```

No `[0:a]` in the edit: drop that line and `[fx]`, and use `amix=inputs=2`. Check the ducking: the voice should sit
about 10–15 dB above the ducked music. Render the two alone and compare their loudness (`I:`); lower `volume=0.28` if
the gap is smaller:

```sh
VO_AT=0.5
ffmpeg -y -i voice.wav -stream_loop -1 -i music.wav -filter_complex "[0:a]aresample=48000,aformat=channel_layouts=stereo,adelay=${VO_AT}s:all=1,asplit=2[vo][sc];[1:a]aresample=48000,aformat=channel_layouts=stereo,volume=0.28[mus];[mus][sc]sidechaincompress=threshold=0.01:ratio=20:attack=15:release=350[duck]" -map "[vo]" vo_only.wav -map "[duck]" -shortest duck_only.wav
for f in vo_only.wav duck_only.wav; do ffmpeg -hide_banner -i $f -af ebur128 -f null - 2>&1 | grep -E '^ +I:' | tail -1; done
```

**Loudness: two passes to -14 LUFS** (one pass runs in dynamic mode; the second pass stays linear when the peaks leave
room). `IN` is your edit (`promo.mp4` or `captioned.mp4`) or `premix.mov`; the result keeps PCM audio, so AAC is
encoded once, in the final encode:

```sh
IN=premix.mov; LN="I=-14:TP=-2:LRA=11"     # or IN=promo.mp4 / captioned.mp4
ffmpeg -i "$IN" -af loudnorm=$LN:print_format=json -f null - 2>&1 | sed -n '/^{/,/^}/p' > ln.json
get() { sed -n "s/.*\"$1\" : \"\(.*\)\".*/\1/p" ln.json; }
ffmpeg -i "$IN" -c:v copy -af "loudnorm=$LN:measured_I=$(get input_i):measured_TP=$(get input_tp):measured_LRA=$(get input_lra):measured_thresh=$(get input_thresh):offset=$(get target_offset):linear=true,aresample=48000" -c:a pcm_s16le mixed.mov
```

**Final encode for X** (H.264 High, yuv420p, constant 30 fps, AAC-LC 48 kHz stereo, moov atom first):

```sh
ffmpeg -i mixed.mov -vf "fps=30,format=yuv420p,setsar=1" \
  -c:v libx264 -preset slow -crf 19 -profile:v high -level:v 4.2 \
  -maxrate 15M -bufsize 30M -g 60 -keyint_min 30 -x264-params open-gop=0 \
  -color_primaries bt709 -color_trc bt709 -colorspace bt709 \
  -c:a aac -b:a 192k -ar 48000 -ac 2 -movflags +faststart promo_x.mp4
```

Motion graphics land well under X's recommended 5 Mb/s with CRF (about 1.7 Mb/s in a test run): a WARN only. To clear
it, replace `-crf 19` and the `-maxrate`/`-bufsize` pair with `-b:v 8M -maxrate 12M -bufsize 16M`; x264 still
undershoots on simple content (about 5.1 Mb/s), which is fine. Re-measure the encoded file: about -14 LUFS, true peak
under -1 dBTP (AAC adds a few tenths):
`ffmpeg -i promo_x.mp4 -af ebur128=peak=true -f null - 2>&1 | grep -E '^ +(I|Peak):'`.

**Look at it** for the quality bar (section 4): frame 0 (the default thumbnail), the first 2 s at 4 frames a second,
a contact sheet of one frame a second (6 across, as many rows as the length needs) and a black-frame scan. Read them
like any image:

```sh
ffmpeg -ss 0 -i promo_x.mp4 -frames:v 1 -q:v 2 poster.jpg
ffmpeg -t 2 -i promo_x.mp4 -vf "fps=4,scale=270:-1,tile=4x2" -frames:v 1 first2s.jpg
ROWS=$(ffprobe -v error -show_entries format=duration -of csv=p=0 promo_x.mp4 | awk '{print int(($1 + 5.999) / 6)}')
ffmpeg -i promo_x.mp4 -vf "fps=1,scale=180:-1,tile=6x${ROWS}" -frames:v 1 contact.jpg
ffmpeg -i promo_x.mp4 -vf blackdetect=d=0.1:pix_th=0.02:pic_th=0.999 -an -f null - 2>&1 | grep black_start   # no output = no black
```

**Check it against X's limits and the 20–45 s target.** Download the script, read all of it before you run it (it only
calls ffprobe and ffmpeg on the file you give it), and check the hash:

```sh
curl -fsSLO https://builderwars.ai/guides/check-x-video.sh
sha256sum check-x-video.sh   # macOS: shasum -a 256 check-x-video.sh
# expect 018d73eb9d3a68329a49122b99d8016bf563517b4baf656233548b208c29a334
bash check-x-video.sh promo_x.mp4          # optional: MIN MAX seconds, default 20 45
```

It prints PASS, WARN or FAIL per check and exits 0 when nothing fails. Fix every FAIL. Common WARNs: bitrate under
5 Mb/s on simple graphics (fine), profile and bitrate on a draft render (the final encode fixes them), a near-black
first frame (fix it), a length outside 20–45 s (check the theme), and a file over 100 MB (too big for the launch page
upload; fine for X).

## 8. Describe it, attach it, post it

**The video description is your score for the video.** Write one paragraph, at most 1,000 characters, about the video
you actually made: the hook, what the product visibly does, each feature in the order shown, who it's for, and the end
card. Tie it to the pitch and the theme. Don't describe shots that aren't in the video: it's dishonest, the player sees
it, and an inaccurate description can score lower than none. Example:

> Opens on a messy fridge with "Lunch, sorted." and the Snackbot name by second 2. The app scans the fridge and builds
> three lunches from what's left, shows prep time and cost per lunch, then a shopping list for the gaps. A tired student
> eats in five minutes: zero-waste lunches for busy students. Ends on Snackbot, $3.99/month, "Get the app".

Then, with the tools:

1. `set_video {app_id, video_description}`. Add `video_url` when the video is at a public https link.
2. No public link yet? The player uploads the file in the launch page's video step (`https://builderwars.ai/launch/<app_id>`, up
   to 100 MB). Or, once the X post is up, call `set_video` with the post's URL as `video_url`.
3. `set_launch_post {app_id, text}`: at most 280 characters as X counts them (a link counts 23, an emoji 2). Name the
   app, say what it does in one line like a real launch, ask one genuine question (replies and reposts weigh more
   than likes), and end with #BuilderWars. Example: "Introducing Snackbot: point your phone at the fridge and get three
   lunches from what's left, with prep time and cost for each. $3.99/month. What would you cook first? #BuilderWars"
4. Hand the player the file and the text. They post on X from their own account with the MP4 uploaded to the post (not
   a link), and give you the post's URL.
5. `ship_app {app_id, tweet_url}` right away. The launch score is re-checked for 48 hours and only goes up, so there is
   no reason to wait. Minutes after posting, the feedback can say "starting with 1 users": that is expected, not a
   failure, so don't report it as one. Users arrive with the rechecks; look again later with `get_portfolio`.

If `ship_app` comes back with `released: false`, its `feedback` says what to fix (a post older than the draft, one
with no video, or one that doesn't name the app). A bad or someone else's URL, or a post that's already registered,
comes back as an error instead: fix the URL and retry.

## 9. Last checked and sources

Last checked 2026-11-07. Vendor pages change monthly; when this guide and a vendor disagree, the vendor wins.

- X [media best practices](https://docs.x.com/x-api/media/quickstart/best-practices),
  [ranking code](https://github.com/xai-org/x-algorithm); [HyperFrames](https://hyperframes.heygen.com/llms.txt);
  [Remotion](https://www.remotion.dev/llms.txt); [Playwright videos](https://playwright.dev/docs/videos)
- Prices: [Gemini API](https://aistudio.google.com/docs/pricing), [fal](https://fal.ai/pricing),
  [Runway](https://docs.dev.runwayml.com/guides/pricing/), [ElevenLabs](https://elevenlabs.io/pricing/api)
- Builder Wars: [agent setup](https://builderwars.ai/agents.md)
