Documentation
README
render-chatgpt-chat
The free renderer for the chatgpt-chat video ad format β the "I just asked ChatGPTβ¦" creative, where someone asks ChatGPT a question and the streamed assistant answer is the punchline (the brand surfacing as the natural response). Deterministic Playwright + ffmpeg assembly; no generative video of the UI, so the bubble text and streamed answer stay pixel-crisp.
This is the ChatGPT sibling of render-imessage-chat. Reach for this one when
ChatGPT is the more credible host for the answer; reach for iMessage when the
punchline is a peer's reaction in a DM. The template recipe (DB) supplies the
per-brand thread + timeline + end_card config and gates the paid music call
(music bed β create-music-elevenlabs) to its own capability.
What it renders
One continuous take β never scene-by-scene (every reload flickers):
- User types in the composer with the iOS keyboard up (
composer-type). - Send-tap is ONE beat β the user bubble pops, the keyboard slides down, and
the header right-cluster swaps (
personPlus/dottedCircleβedit/more) all on the samet. Never sequence them across frames. - One gray loading dot holds ~500ms (never three β three reads as iMessage typing, wrong app), silently (no SFX on the dot).
- The assistant answer streams in word-by-word (
stream-words, ~7 wps) with a soft opacity ramp; the conversation auto-scrolls to keep it in view. - Crossfade to a designed end card (wordmark + β proof row + trust trio + CTA pill) and mux a ducked music bed β master MP4.
The chat records at the ChatGPT-native ~9:19.5 (default 750Γ1624) to match a real iPhone screen recording. Never stretch the chat to a different aspect ratio β the end card is scaled-to-fit + padded to the chat's dimensions in stitch, so the chat is never touched.
Run
cd scripts && npm install # once β installs Playwright
npx playwright install chromium # once
node record-chat.js --config config.json --out-dir <work> # β master-chat.mp4 + .sfx.json
node render-end-card.js --config config.json --out-dir <work> # β scene-end-endcard.mp4
bash stitch.sh --chat <work>/master-chat.mp4 --end <work>/scene-end-endcard.mp4 \
--sfx <work>/master-chat.sfx.json --out <work>/master-final.mp4 \
--pad-color "#ffffff" [--music <work>/music-bed.mp3] [--also-1x1]
record-chat.jsβ readsconfig.json(thread+timeline+ geometry), renders the bundledcreate-chatgpt-mockupHTML once with every message pending, walks the timeline onrequestAnimationFrameinside the page, records it as one continuous MP4, and emits the deterministic SFX cue list.render-end-card.jsβ fillsend-card.template.htmlfromconfig.end_card(wordmark/logo_svg, stars, proof, trust trio, CTA, colors) β still MP4. This is the SAME generic end card asrender-imessage-chat(copied verbatim).stitch.shβ normalizes the end card to the chat's dimensions, crossfades chat β end card, layers the subliminal ChatGPT SFX, optionally ducks a music bed under it, and optionally derives a 1:1 crop. All FREE ffmpeg. Pass--pad-color=end_card.bg(default#ffffff, ChatGPT light mode) so the pad under the end card is seamless.
The chat body: bundled create-chatgpt-mockup
The ChatGPT chat HTML comes from create-chatgpt-mockup (its generate.js +
templates/ produce the light-mode ChatGPT iOS HTML β status bar, header, message
rows, streaming word-spans, composer, and the inline iOS keyboard). Those files are
bundled into scripts/mockup/ so this capability renders the chat body
standalone β no sibling fetch of create-chatgpt-mockup is required. record-chat.js
does require('./mockup/generate.js').
The keyboard is inlined by the mockup (renderKeyboard) β no separate keyboard atom.
Timeline events (consumed by record-chat.js)
| Kind | Meaning |
|---|---|
composer-type |
{ text, dur_sec } β type into the composer. SFX = one key-tap per word. |
composer-clear |
Wipe the composer instantly (fire at send-tap). |
keyboard-show / keyboard-hide |
Slide the iOS keyboard up / down. |
send-tap |
Pulse the send button. SFX = send-tap. |
pop |
{ target: <msg-id> } β reveal a message row. |
header-swap |
{ value: "alt" } β swap the header right-cluster. |
loading-dot-show / loading-dot-hide |
{ target: <dot-id> } β the single gray dot. |
send-state |
{ value: "streaming"|"active" } β composer send-button state. |
stream-words |
{ target, dur_sec, wps } β reveal the assistant answer word-by-word. SFX = stream-tick every 12 words + response-done at the end. |
scroll-to |
{ target, dur_ms } β smooth-scroll a row into view. |
See scripts/config.example.json for the canonical thread + timeline (the "one
beat" send-tap and the streamed list answer are both wired there).
Contract
- FREE assembly: Playwright record + ffmpeg composite/mux + the bundled SFX. No AI-rendered text β the bubbles, the streamed answer, and the end-card copy are all real HTML/PIL, never invented by a model.
- The recipe (DB) supplies the per-brand config: the
thread(light-mode ChatGPT, assistant message setstream: true), thetimeline, theend_card(prefer a reallogo_svgwordmark), and an optional music bed. - SFX are subliminal by design (ChatGPT has no native chime): key-tap -28dB,
send-tap -20dB, stream-tick -32dB, response-done -22dB, and never a cue on the
loading dot. Set
"sfx": falsein the config to ship the chat silent.
Gaps / routing notes
- Music bed is an input, not generated here β the recipe gates it to
create-music-elevenlabs(paid, proxy-routed, billed to the Ads agent) and passes the file intostitch.sh --music. - Bundled SFX are synthesized stand-ins. The original four wavs
(
key-tap/send-tap/stream-tick/response-done) were lost from Git LFS (the objects 404 on the server), soassets/sfx/*.wavare freshly synthesized subliminal clicks/ticks. They work as-is; swap in real wavs (same filenames) for tuned SFX. - Portability: everything runs from the fetched
/tmp/gooseworks-scripts/render-chatgpt-chat/scripts/β¦β the chatgpt-mockup generator + templates are bundled underscripts/mockup/, and the generic end card is bundled underscripts/. No/Users/β¦or repo-relative paths, and no required sibling fetch. - Requires ffmpeg/ffprobe on PATH and Playwright Chromium (
npx playwright install chromium).
Self-QC (per project rule β always /watch the master)
- Keyboard is up the whole time the user types, and slides down only on the send-tap beat (never visible while the answer streams).
- Send-tap is one beat: user bubble + keyboard-down + header-swap on the same frame.
- Exactly one gray loading dot for ~500ms (not three), and no SFX on the dot.
- The answer streams word-by-word, left-to-right / top-to-bottom, not all-at-once.
- No OpenAI spiral logo above any assistant title (the spiral is empty-state only).
- No micro-flicker / scene cuts; the end-card pad color matches
end_card.bg.