Personalized sales video from a pitch deck
You deliver a 1920×1080 video, about 90–110 s, in which an avatar of whoever is presenting
greets the customers by name and walks through the real slides of a pitch deck. It should feel
like the sender recording a quick personal walkthrough, not an ad. First run: 100.6 s, about
$9.90 all in, with an existing avatar and voice. Video steps follow
hn/recipe-slide-deck-presenter.
Part 1: Get to a finished deck
Work out which of three inputs the person brought, and say which one you're in:
- A. A finished deck for this client (PDF, PPTX, Keynote, images or a Canva link). Use it as is. Go to Part 2.
- B. A deck template plus a client-details doc. Fill the template for this client.
- C. A brand guide plus a client-details doc. Build a new deck in that brand.
For a Canva deck, template or brand kit, follow recipe-sales-video-from-canva-deck, which
covers all three in Canva and hands back here at Part 2.
The client-details doc
B and C both run on it. Read it and pull out: the client's name; the customers' names and how they're said; the presenter; what you heard from them (their needs, in their words); the scope (what you'll make, how many, what each one differs by); the reusable parts (a character, a fixed segment); the timeline; the pricing, including any discount for a case study; the date; and any images of theirs. List anything the deck needs that the doc doesn't say and ask for it in one message. Never make up prices, numbers, dates, quotes or claims; a field the doc doesn't cover is a question, not a guess.
B. Fill a template
- Copy the template; never edit the master. Name the copy " x proposal".
- Find every placeholder:
[bracketed]fields, sample names, dates and prices, and stock photos meant to be swapped. Read the speaker notes; a template often lists its fields there. - Fill each field from the doc. Keep the template's lengths: a field that's a short label stays short, so text doesn't wrap or collide. Rewrite one line rather than letting it overflow.
- Swap the photos for the client's own imagery when the doc supplies it. Otherwise leave the template's photos where they fit the client and say which ones you left.
- Drop slides the deal doesn't need, and clear any template notes out of the speaker notes.
- Check: no
[left anywhere, no text overflowing or overlapping, and the client's name spelled the same everywhere.
C. Build from a brand guide
- Take the brand guide's colours, type, logo and tone. If it has a slide template, start from that.
- Outline the standard proposal, one idea per slide: title ("Prepared for "), what we heard, approach, deliverables, timeline, pricing, and the sender's end card. Add or drop slides to fit the doc. Gate: the person approves the outline.
- Build the deck in a 4:3 frame if you can, since 4:3 leaves room for the presenter (Part 2). Use the brand's own type and colours, few words per slide, and one image per slide at most. Make it in whatever deck tool the session has; a deck that can be exported as images is all Part 2 needs.
Gate (B and C): show every finished slide and get the deck approved before any voice or lip sync is paid for. A change to a slide after that costs a re-render of its span.
Part 2: Before the video, ask who presents
Ask who should present. Never assume it's the person you're talking to, and never reuse an avatar from another job without asking. It might be them, a cofounder, or the account exec who owns the deal. Ask once:
- The presenter's name as said in the greeting, and their role if it's mentioned.
- Whether they already have an avatar and voice. Look in this machine's earlier job folders
(
job-notes.md,character/,refs/). If you find them, show them and ask to reuse. - The customers' names and how to say them. Whisper writes unusual names differently; that's fine for the audio, but write lip-sync prompts from the script, never from the transcript.
- Which slides to use. Drop duplicates (an older copy at the end, a slide that restates another as text) and say which ones you dropped.
- House rules, such as no added on-screen text. Then the only words on screen are the slides'.
Part 3: The video
- Make the presenter, when there isn't one yet. Still: use one or two of their own
recent photos (
guide-depict-real-people-and-places); never generate a stand-in. Recompose it into a 3:4 portrait at 2K with nano banana Pro edit (nano-banana-restage-for-new-aspect): waist-up, centred, facing the camera, plain dark backdrop, closed-mouth smile. Fit it to 768×1024. Gate: the presenter approves the still. Voice: clone their own from 1–3 min of clean speech, with their consent. Otherwise cast a library voice (guide-cast-a-voice) and say it won't sound like them. Gate: an audition line. Save both inrefs/and note them injob-notes.md. - Slides as images: 1920 px wide (4:3 → 1920×1440),
source/slideNN.png. - Script, one section per slide, about 2.5–3 words per second. Open with "Hi , hi
! It's from ," then what this is. Say each slide in a spoken
voice to "you". Write numbers as they're spoken ("twenty-five hundred dollars"). Close
warmly. Gate: the script, the presenter and the cost, approved word for word, in chat
(
review_record). In route B, a template's speaker notes can be the first draft. Then, before the voice: the greeting and first slide in two or three presenter layouts, on word times estimated from the script, live on a review page (recipe-slide-deck-presenterstep 1). Gate: the layout mix. - Voice:
elevenlabs/text-to-speech,eleven_v4, one tagged line per section, two takes each (seeds 11, 22), checked with whisper-cli. Gate: a review page. If the person says to use one take throughout, use one seed for every section; the later gates still stand. - Track (
scripts/assemble.py): 0.3 s lead-in, 0.6 s between sections, 0.8 s tail, −16 LUFS. Transcribe it for word timings. - Lip sync, only once the person has seen a layout move (after the script, or
now, before the first run): cut into 5–14 s chunks at sentence pauses (
scripts/plan_chunks.py). H3 Max 768P, still pinned first and last, presenter described from their still, words from the script, one gesture per chunk (a wave on the names and on the sign-off). About $0.094/s. Check each chunk (scripts/check_all_chunks.py), retime late drift (scripts/retime_drift.py), join on a 24 fps grid with 5-frame dissolves (scripts/assemble_head.py). - Cutout:
scripts/matte_head.py, Robust Video Matting on the local GPU, free. - Layout (
slides/build.py→hn4a-compositor), from the layouts picked after the script: slides as-is at 1440×1080 on the left; a 480 px strip on the right, dark with a faint dot grid. Slides change mid-gap with a 0.35 s zoom. Mix the presenter layouts, never the same twice in a row: big cutout on the greeting and sign-off (810×1080 at x≈1170, slow 0.96→1 scale), normal cutout (698×930 at 1300,150), a picture-in-picture box (400×533 in the strip, a frame in the brand colour, offset shadow), and a round bubble (400 px, a face crop measured from their still, a brand-colour ring). Shrink the end card to 1280×960 so a big cutout doesn't cover its tagline. Take a still per layout and check that nothing covers a slide's words. - Render and deliver:
rev1/<Client>_pitch_<Names>.mp4. Frames should equal round(duration × 30). Look at a contact sheet. Send a 1280 px copy (CRF 26) in chat; the full file goes inrev1/. Keep the filled deck's link or file injob-notes.md.
Choices
- Get to a finished, approved deck before anything is paid for. The deck is the script's backbone and the video's picture.
- Real people only: the presenter's own photos and voice, or a voice they picked.
- Real slides, never redrawn; their words stand in for captions.
- One talking head, used three ways: the box and bubble crop the footage, and only the cutout uses the matte.
Dead ends
- Prices or claims filled in from memory or another client's deck: always from the details doc.
- A placeholder longer than the field it replaced: it wrapped and collided. Keep lengths close.
- Lip-sync prompts from the whisper transcript: misspelled names, "$2.99" for "two ninety-nine".
- A big cutout over a full-size end card: it covered the tagline.
- Waiting on a device call over 60 s: it times out. Start it with
wait: false, then follow its id withtasks(wait: true, calls: [<id>], timeout_s: 50)(execsfor a program), again until it has ended.


