A step‑by‑step operating manual written for an AI agent. Feed this to a capable agent with tool access and it should reproduce the entire pipeline — script → cloned voice → talking mascot → b‑roll → logo sting → final edit — on the first pass, avoiding every mistake we made learning it the hard way.
You are an AI agent with tool access (a Higgsfield connector or equivalent generation API, a shell with ffmpeg/ffprobe/curl, and the ability to view images). Your job: produce one finished promo video for [CLIENT].
ffprobe, volumedetect, a spectrogram). Most of our failures were invisible until we looked../promo/ with subfolders vo/ broll/ leo/ logo/ work/ out/. Absolute paths everywhere.-ar 48000 on any FFmpeg audio step. loudnorm silently resamples to 96 kHz internally and, if you don’t pin the rate, you get click/glitch artifacts downstream. 48 kHz everywhere./assets/logo-mark.png) and upscale it.declined_preset_id to generate your literal prompt.Every one of these cost us time on the Etari build. The rules above exist because of them. Read them so you don’t repeat them.
| What we did wrong | Symptom | Do this instead |
|---|---|---|
Used ElevenLabs / seed_audio for the founder VO | Voice sounded flat/robotic; lost the founder’s character | text2speech_v2 + variant seed_speech with an element (cloned) voice. Nothing else. |
| Used Wan 2.7’s output audio in the cut | Hissy, “glitchy/distorted” voice on the timeline; clean render only proved the source was fine | Use Wan clips video‑only; lay the clean generated VO underneath. |
| Generated a text‑heavy dashboard / website with the model | Gibberish numbers and warped words | Dashboards: 3 big legible KPIs max. Website: real screenshot + FFmpeg push‑in. |
Ran loudnorm without pinning sample rate | Clicks/pops after import; audio silently at 96 kHz | Always end audio filters with -ar 48000. |
| Let Kling auto‑apply a preset | Video came out as an unrelated “3D render in the dark” | Pass declined_preset_id from the recommendation notice. |
| Re‑drew the client logo with a generator | Close but wrong — altered leaf/shape; not brand‑exact | Download the real logo-mark.png from the site; upscale to 4K. |
| Padded a composited logo with a guessed background colour | Visible seam/rectangle around the logo | Sample the exact bg pixel and pad with that hex. |
| Tried to re‑sync a fresh continuous VO read to existing lips | Impossible to line up word‑for‑word; drift everywhere | Either regenerate the mascot to the new audio (Wan), or restore the original synced audio in place. Don’t fight two different takes. |
| Assumed a clip’s duration = its video length | Beats had video = N.0s but audio = N.1s (a 0.1s tail) | Confirm both streams with ffprobe. The tail is silence — safe to truncate. |
| Inferred generation cost from the credit ledger | Estimates off by 3× | Use the get_cost preflight; it’s free and exact. |
| Drove the edit through DaVinci over MCP | App hung on permission dialogs; the “current project” switched away and lost work | Edit in FFmpeg. No app, no state, deterministic. |
You are producing a ~50–60 second promo: a branded mascot (in the founder’s real cloned voice) delivers a tight 6‑beat pitch, and you cut fast (every ~2–3s) between the talking mascot and supporting visuals. You do not generate 2–3s clips. You generate a few longer pieces and cut between them in the edit, with the VO running continuously underneath. Four layers stack on the voice:
| Layer | What | Made with |
|---|---|---|
| BASE | Mascot talking — one clip per script beat, lip‑synced to the founder’s voice | Wan 2.7 (video) + seed_speech VO |
| B‑ROLL | Grounded scenes: results dashboard, the client’s website, “work happening” | Nano Banana Pro keyframe → Kling 3.0; real screenshots via FFmpeg |
| MOTION GFX | Optional: animated numbers, labels, lower‑thirds, captions | FFmpeg drawtext / burned subtitles (or Remotion for fancy) |
| LOGO | A premium logo sting (intro or outro) | Real logo → 4K upscale → Kling 3.0 |
The 6‑beat script skeleton (proven structure): 1) HOOK — a sharp reframe of the problem · 2) PROBLEM — the pain, concretely · 3) SOLUTION — how [CLIENT] is different · 4) PROOF — the dashboard / results · 5) ALT‑OFFER — a secondary path (e.g. DIY) · 6) CTA — the ask + where to go.
| Job | Model / tool | Notes |
|---|---|---|
| Character & b‑roll keyframes | nano_banana_2 (Nano Banana Pro) | ~1.5–2 cr/image. Best at legible text. Restate the design every prompt. |
| Reusable character | Element (workspace reference) | Create once; embed as <<<element_id>>> in prompts for consistency. |
| Voice clone | text2speech_v2, variant seed_speech, voice_type:element | The only acceptable VO path. Outputs 24 kHz mono (fine for speech). |
| Talking mascot (lip‑sync) | wan2_7, 720p | ~1.5 cr/sec. start_image=keyframe, audio_references=VO. Duration = audio length (max 15s/clip). |
| B‑roll motion | kling3_0, 720p | ~10 cr / 5s. Image‑to‑video. Pass declined_preset_id. |
| Upscale (logo) | upscale_image (bytedance), 4K | Flat cost. Requires source width/height. |
| Assemble / audio / captions | FFmpeg + FFprobe | The editor. 100% scriptable. |
| Fetch real assets (logo, screenshots) | Headless browser (Playwright) | Read the DOM to find the true asset URLs; screenshot at device scale. |
Tight, spoken, ~50–55s total. Short sentences. One idea per beat. Write it as 6 blocks; each block becomes one VO generation and one BASE mascot clip. Example (Etari):
1 HOOK "Most businesses don't have an ad problem. They have an agency problem."
2 PROBLEM "You pay an agency, your account lands on a junior's desk, and once a month you get a pretty PDF."
3 SOLUTION "At Etari Digitals, we do it differently. Every account is run by a founder. No juniors, no handoffs. Google Ads and SEO built as one system, so your ad spend stops being a cost and starts being an asset."
4 PROOF "And you're never left guessing. Every client gets a live dashboard: spend, revenue, ROAS, profit, all in one place, in real time. No black box."
5 ALT "Prefer to run it yourself? Our DIY package hands you the exact systems we use internally. Same playbook, your hands on the wheel."
6 CTA "Want to know what's leaking in your account? We'll audit it free, within 48 hours. Check out the website, link in our bio."
Write a canonical design sentence and reuse it verbatim in every prompt. Example: “Anthropomorphic muscular lion, golden‑tan fur, teal‑and‑green mane with a green leaf tuft on top; wears a dark navy athletic polo with thin gold trim and NO text; flat 2D cartoon, bold black line art, cel‑shaded.”
nano_banana_2 using the canonical sentence.element_id.<<<element_id>>> and still restate the design sentence — belt and suspenders keep it on‑model.voice_id (element).generate_audio(
model = "text2speech_v2",
variant = "seed_speech", # NOT elevenlabs, NOT seed_audio, NOT vibe_voice
voice_type = "element",
voice_id = "[FOUNDER_VOICE_ELEMENT_ID]",
prompt = "<the beat's text>"
)
| Variant | Verdict |
|---|---|
seed_speech | USE THIS. Preserves the voiceprint. 24 kHz mono — perfectly fine for speech (telephone is 8 kHz; broadcast ~16–24 kHz). |
elevenlabs | ❌ Flattens the founder’s voice. Rejected. |
seed_audio | ❌ 24 kHz “robotic”. Rejected. |
voice_change (post‑hoc) | ❌ Glitchy. Rejected. |
ffprobe). The beat’s lip‑synced video will be built to that length.For each beat: animate the mascot keyframe to that beat’s VO with Wan 2.7. The mouth is driven by the audio’s phonemes → real lip‑sync.
generate_video(
model = "wan2_7",
resolution = "720p",
duration = <seconds of that beat's VO>, # 2–15
aspect_ratio = "16:9",
medias = [
{ value: "[MASCOT_KEYFRAME_ID]", role: "start_image" },
{ value: "[BEAT_VO_MEDIA_ID]", role: "audio_references" }
],
declined_preset_id = "[the id from the preset notice, if any]",
prompt = "The character speaks to camera, natural mouth movements lip-synced to the audio, subtle head/shoulder motion, staying in the same set. Keep the exact character, framing, lighting. Do not distort."
)
ffmpeg -i beat3_wan.mp4 -lavfi showspectrumpic=s=1000x400:legend=1 spec_wan.png
ffmpeg -i vo/raw_beat3.mp3 -lavfi showspectrumpic=s=1000x400:legend=1 spec_raw.png
# open both; the Wan one has a hazy 12-17kHz band the raw one doesn't.You now have 6 BASE clips (mascot talking, video‑only usable). Optionally generate 1–2 alternate angles (a tighter dead‑on close‑up, a true side profile) using the “same set, change the angle” trick — these give you free cutaways/jump‑cuts in the edit.
Generate a photorealistic, minimal dashboard with only a few big, legible KPIs. Fewer elements = zero gibberish. Blur/redact any client name.
generate_image(
model="nano_banana_2", aspect_ratio="16:9", count=2,
prompt="Photorealistic clean MINIMAL marketing results dashboard on a monitor in a bright modern office.
Only THREE big bold legible KPI cards: 'ROAS 6.4x', 'Revenue EUR 41,900', 'Leads +228%',
and one simple upward green line chart. No tiny cluttered text anywhere.
The account name at the top is hidden behind a solid grey privacy blur bar. Crisp, credible."
)
Then animate the keyframe (subtle push‑in) with Kling — the on‑screen numbers must stay static/legible:
generate_video(model="kling3_0", duration=5, aspect_ratio="16:9",
declined_preset_id="[preset id if notice fires]",
medias=[{value:"[DASH_KEYFRAME_ID]", role:"start_image"}],
prompt="Subtle slow cinematic push-in on the monitor. Dashboard numbers/chart stay perfectly static and legible. Gentle camera drift, soft background office movement.")
# slow 5s push-in on a cropped 16:9 region of the screenshot, 720p
ffmpeg -loop 1 -i site_packages.png -t 5 -r 30 \
-vf "crop=1920:1080:0:400,scale=3840:2160,\
zoompan=z='min(zoom+0.0007,1.11)':d=150:x='iw/2-(iw/zoom/2)':y='ih/2-(ih/zoom/2)':s=1280x720:fps=30,\
format=yuv420p" \
-c:v libx264 -crf 18 -pix_fmt yuv420p broll/web_packages.mp4
Grounded, believable scenes (someone reviewing dashboards, a team at a desk). Nano Banana keyframe → Kling motion, sound:off. Keep it bright and real — not dark/surreal.
/assets/logo-mark.png — and download it. Also capture the wordmark: it’s often live HTML text, so zoom the header element up (e.g. transform:scale(6)) and screenshot at device scale for a razor‑sharp wordmark.upscale_image, provide source w/h).# sample the exact bg colour from a corner, then pad with it (no seam)
read R G B < <(ffmpeg -i logo_4k.png -vf "crop=8:8:2:2,scale=1:1,format=rgb24" -f rawvideo - 2>/dev/null | od -An -tu1 | awk '{print $1,$2,$3}')
HEX=$(printf "0x%02X%02X%02X" "$R" "$G" "$B")
ffmpeg -i logo_4k.png -vf "crop=W:H:X:Y,scale=1560:-1,pad=1920:1080:(ow-iw)/2:(oh-ih)/2:color=$HEX,format=rgb24" logo/logo_frame.png
generate_video(model="kling3_0", duration=5, aspect_ratio="16:9",
declined_preset_id="[preset id — the word 'dark' triggers 'IN THE DARK']",
medias=[{value:"[LOGO_FRAME_ID]", role:"start_image"}],
prompt="Elegant premium logo sting. The logo (emblem + wordmark) stays perfectly sharp, centered, undistorted.
Subtle only: a soft golden light-glint sweeps left to right, a gentle glow pulse, faint drifting particles, a very slow zoom-in.
The logo and text must NOT warp, morph, or change shape.")
Build ONE continuous VO track from the 6 raw beat clips, then master it. This mastered track is the audio bed for the whole film.
# 1) concat the 6 raw VO clips in order (they are the clean source)
printf "file '%s'\n" vo/raw_beat1.wav vo/raw_beat2.wav ... > vo/list.txt
ffmpeg -f concat -safe 0 -i vo/list.txt -c:a pcm_s24le -ar 48000 -ac 2 vo/bed_raw.wav
# 2) master it — the proven chain. ALWAYS end with -ar 48000 (R4).
ffmpeg -i vo/bed_raw.wav \
-af "highpass=f=75,equalizer=f=2800:width_type=q:width=1:g=2,\
equalizer=f=10000:width_type=q:width=1:g=4,deesser,\
acompressor=threshold=-18dB:ratio=2:attack=5:release=50,\
loudnorm=I=-16:TP=-1.5:LRA=11" \
-ar 48000 -ac 2 -c:a pcm_s24le vo/bed_master.wav
ffmpeg -i dirty.wav -af "highpass=f=75,lowpass=f=12000,afftdn=nr=12:nf=-30,deesser" -ar 48000 -ac 2 -c:a pcm_s24le clean.wav
Verify by comparing spectrograms before/after — the >12 kHz haze should be gone and the gaps darker.This is the part DaVinci made miserable. In FFmpeg it is a deterministic script. The model: V1 = the 6 BASE mascot clips concatenated end‑to‑end (video‑only); b‑roll overlays on top at chosen times with crossfades; the mastered VO is the audio. Cut ≈ every 2.5s by choosing when b‑roll covers the mascot.
norm(){ ffmpeg -y -i "$1" -an -r 30 -vf "scale=1280:720:force_original_aspect_ratio=increase,crop=1280:720,format=yuv420p" -c:v libx264 -crf 18 "$2"; }
norm leo/01_beat1.mp4 work/b1.mp4 # ... repeat for all beats and all b-roll
printf "file '%s'\n" work/b1.mp4 work/b2.mp4 work/b3.mp4 work/b4.mp4 work/b5.mp4 work/b6.mp4 > work/base.txt
ffmpeg -f concat -safe 0 -i work/base.txt -c copy work/base.mp4 # this is the mascot, continuous
Decide a cut map: for each b‑roll clip, a start second and a duration on the timeline. Overlay each on top of BASE with a short alpha fade in/out so cuts dissolve. Pattern for one overlay (repeat/chain per clip, or script it):
# overlay web_packages from t=22s for 4s, with 8-frame dissolves
ffmpeg -i work/base.mp4 -i work/web_packages.mp4 -filter_complex "\
[1:v]trim=0:4,setpts=PTS-STARTPTS,fade=t=in:st=0:d=0.27:alpha=1,fade=t=out:st=3.73:d=0.27:alpha=1[ov];\
[0:v][ov]overlay=enable='between(t,22,26)':x=0:y=0[v]" \
-map "[v]" -c:v libx264 -crf 18 -pix_fmt yuv420p work/base_1.mp4
# chain the next overlay onto work/base_1.mp4, and so on.
For many overlays, generate this chain programmatically from a cut‑map array (each entry: file, t_start, dur). Keep the VO’s beat boundaries in mind so b‑roll lands on the right words.
# video = the fully overlaid picture; audio = mastered VO (+ ducked music bed if you have one)
ffmpeg -i work/picture.mp4 -i vo/bed_master.wav -i music.mp3 -filter_complex "\
[2:a]volume=0.12,afade=t=out:st=52:d=3[mus];\
[1:a][mus]amix=inputs=2:duration=first:dropout_transition=0[a]" \
-map 0:v -map "[a]" -c:v copy -c:a aac -b:a 256k -ar 48000 -shortest out/promo_v1.mp4
You already have the exact script per beat and each beat’s start time. Emit an .srt from those, then burn it:
ffmpeg -i out/promo_v1.mp4 -vf "subtitles=captions.srt:force_style='Fontname=Arial,Fontsize=22,Bold=1,PrimaryColour=&Hffffff&,Outline=2,Shadow=0,MarginV=48'" -c:a copy out/promo_captioned.mp4
For word‑by‑word / animated captions, render them in Remotion and overlay the transparent caption layer with FFmpeg.
ffprobe out/promo_captioned.mp4 → confirm 1280×720, 30fps, AAC 48 kHz, ~50–60s.ffmpeg -i out/... -af volumedetect -f null - → mean ≈ −16 LUFS‑ish, peak ≤ −1 dB (no clipping).# safe delivery encode
ffmpeg -i out/promo_captioned.mp4 -c:v libx264 -profile:v high -pix_fmt yuv420p -crf 19 -c:a aac -b:a 256k -ar 48000 -movflags +faststart out/FINAL_[CLIENT]_promo.mp4
On the plan we used, credits cost about $0.05 each (a 1,000‑credit top‑up was $49; auto‑refill runs 18 credits/$1). The entire Etari build — including all the failed experiments while learning — came to ~860 credits ≈ $43. A clean first‑go run following this guide should be well under half that.
| Item | Qty | ~Credits |
|---|---|---|
| Mascot + set keyframes | ~4 | ~8 |
| Voice clone (element) | 1 | ~40 |
| seed_speech VO (6 beats) | 6 | ~2 |
| Wan lip‑sync BASE clips | ~7 | ~85 |
| B‑roll keyframes + Kling motion | ~8 | ~55 |
| Logo: upscale + Kling sting | 2 | ~12 |
| Website / real footage / FFmpeg edit | — | 0 |
| Clean‑run total | ~200 (≈ $10) |
| Symptom | Cause | Fix |
|---|---|---|
| Voice sounds flat / not the founder | Wrong variant | Only text2speech_v2 + seed_speech + element voice. |
| Voice hissy/“distorted” on the timeline | Using Wan’s baked audio | Use Wan video‑only; lay the mastered VO under it. |
| Clicks/pops in exported audio | loudnorm resampled to 96 kHz | End every audio step with -ar 48000. |
| Dashboard/site text is gibberish | Model can’t spell dense text | ≤3 big KPIs; real screenshots for websites. |
| Video came out as a random “3D render / in the dark” | Preset auto‑applied | Pass declined_preset_id. |
| Logo looks “close but wrong” | AI re‑draw | Download the real asset; upscale it. |
| Rectangle/seam around composited logo | Guessed pad colour | Sample the exact bg pixel; pad with that hex. |
| Lips don’t match a new VO read | Two different takes | Regenerate mascot to the new audio (Wan), or keep the original synced audio. Don’t re‑time a different read. |
| Delivered file won’t open on a normal PC | Exotic codec (YUV 422 10‑bit in MP4) | Deliver H.264/AAC MP4. |
| Sync drifts across the film | Mixed fps/res | Normalise everything to 1280×720 / 30 fps first. |
seed_speech → generate 6 VO clips → animate the mascot to each with Wan (keep video only) → make b‑roll (few‑number dashboards, real‑screenshot websites, grounded scenes) → get the real logo, upscale, animate → master ONE continuous VO with the fixed FFmpeg chain (-ar 48000!) → in FFmpeg, concat the mascot beats, overlay b‑roll with crossfades, lay the VO, burn captions → export H.264. Look at and listen to everything before you use it. Decline the presets. That’s the whole film.