UGC talking video ads
Several scripts with different hooks, one consistent AI presenter who speaks the lines with generated voice, and vertical cuts with burned-in captions, ready for TikTok, Reels and Douyin.
Make three 20-second UGC ads for our protein bar, each with a different hook
when the agent picks itMake UGC-style talking video ads with EggAPI - several scripts with different hooks, one consistent AI presenter, clips where the presenter speaks the lines with generated voice, then vertical cuts with burned-in captions via ffmpeg. Use when the user wants a UGC ad, talking-head or testimonial-style video, creator-style product review, spokesperson video or short-form video ad for TikTok, Reels, Shorts, Douyin or Kuaishou (UGC 广告、口播视频、达人带货视频、真人出镜广告、种草视频).
Make short vertical ads where one presenter talks to the camera about a product, in several versions that differ only in the opening hook, so the user can test which hook wins. Reply in the user's language. Write model prompts in English, but write the spoken lines and captions in the ad's language.
EggAPI basics
This skill uses the EggAPI MCP server, usually added under the name eggapi: list_models, generate_image, generate_video and get_task (in Claude Code they show up as mcp__eggapi__*). If these tools are missing, stop and send the user to https://eggapi.ai/connect to add the server.
-
Models. Call
list_modelsonce at the start. Use the model IDs this skill names when they are listed; otherwise pick the closest model with the samegeneration_type.unit_price_usdis the price per image, or per second of video;unit_price_by_resolutionoverrides it for a givenresolution. -
Local files. The generate tools only accept public
http(s)URLs. Upload local files first (up to 20 MB per image):bashcurl -sS https://api.eggapi.ai/v1/uploads \ -H "Authorization: Bearer $EGGAPI_API_KEY" \ -F "files[]=@./photo-1.jpg" -F "files[]=@./photo-2.jpg"
The URLs are in
data.files[].url. IfEGGAPI_API_KEYis empty in the shell, ask the user to export it; never ask for the key in the chat. Uploaded files can be opened by anyone who has the URL, so say so before uploading anything private. -
Tasks are async.
generate_imageandgenerate_videoreturn atask_idat once. Submit every independent task first, then pollget_taskfor each untildoneis true: every 5-10 s for images, 10-15 s for videos (usually 1-3 min). If a generate call already returnsurls, no polling is needed. -
Always pass
aspect_ratio(the default is 16:9) and, for video,duration(4-15 s). Supported ratios:1:1,3:4,4:3,9:16,16:9,21:9. -
Edit models return one image per call. For more variants, call again;
num_outputsonly applies to text-to-image models. -
Output URLs work as inputs. Pass a finished image's URL straight into the next edit or video call; no re-upload needed.
-
Save everything. Download each output as soon as its task completes, into
eggapi-output/<project>/under the current directory, withcurl -sSL -o <file> <url>: some output links expire after 24 hours, and some file hosts reject other HTTP clients. Keep the extension from the URL and use descriptive file names. -
Cost. Show the plan with an estimated cost before generating and wait for a go-ahead, unless the user already said to go ahead. At the end, report each task's
costand the total. -
Errors.
insufficient balance: ask the user to top up at https://eggapi.ai/dashboard.temporarily unavailable: switch to another model of the same type. Afailedtask: readerror, adjust the prompt if it points at the content, and retry once.
Ground rules
- The presenter is AI-generated. Do not script made-up personal results ("I lost 10 kg in a month") or fake reviews; keep claims to facts about the product that the user can back up. Remind the user to switch on the platform's label for AI-generated content when they upload.
- Never imitate a real creator, celebrity or other identifiable person. A user's own photo of a presenter needs that person's permission.
1. Brief
Ask only for what is missing:
- Product, product photos, and 3-5 facts or benefits the user can stand behind.
- Audience and platform. Default format
9:16. - Length: default 20 seconds; 15-30 seconds works for most placements.
- Language and the presenter's tone (excited, calm, expert, funny).
- Presenter: a description (age range, style, setting), or the user's own photo.
- Setting: bedroom, kitchen, car, bathroom mirror, gym, office.
- Hooks: how many versions to test (default 3).
2. Write the scripts and confirm
Use one body and several hooks, so every version shares most of the footage and only the hook is rendered per version:
- Hook (one clip, 4-6 s): a question, a bold claim the product backs up, a relatable problem, or a surprising demo. One line per hook.
- Body (one or two clips, up to 15 s each): the problem, the product and how it solves it, one or two facts, then the call to action.
Spoken pace is about 2.5 words per second in English and about 4 characters per second in Chinese, so a 12-second clip holds about 30 English words. Set each clip's duration about one second longer than its lines take to say: the models leave a short pause before and after the speech, which is trimmed in step 5. Write lines as short phrases separated by commas; each phrase becomes one caption.
Show a table: clip, duration, spoken line, what we see (selfie talking, product close-up, demo). Estimate the cost: total seconds of all clips x the video price per second at the chosen resolution, plus the presenter images. Wait for the go-ahead.
3. Presenter frames
-
Presenter:
generate_imagewithgoogle/nano-banana-pro/text-to-image,aspect_ratio: "9:16",resolution: "2k",num_outputs: 3:Smartphone selfie photo of {presenter}, holding the phone at arm's length, looking into the camera, natural window light, {setting}, casual and authentic, slightly imperfect framing, no beauty filter.
Let the user pick one. A user's own photo replaces this step.
-
First frames: for each clip, make its opening frame with
google/nano-banana-pro/edit(alternativebytedance/seedream-v5.0-pro/edit), passing the presenter image and the product photos:The person from the first image, exactly the same face, hair and clothes, {holding the product up to the camera / pointing at the product on the table}. The product looks exactly as in the other images: same shape, colors, logo and label text. Same selfie framing, same {setting}, same light.
4. Render the clips
Render the first hook as a test before anything else:
-
generate_videowithbytedance/seedance-2.5/image-to-video, which gave the most natural speech in testing.alibaba/wan-3.0/image-to-videocosts half as much per second or less; offer it when the budget is tight, and render its test clip too. Use the same model for every clip. -
image: the clip's first frame.aspect_ratio:9:16.duration: the clip's length.resolution:720pfor the test,1080pfor the final. -
parameters: {"generate_audio": true}. -
Prompt the delivery and the exact line:
The woman looks into the camera and starts talking right away, saying in {language}, in a {tone} voice: "{line}". Handheld selfie video with slight natural shake, she gestures with the product while talking. No background music. No on-screen text.
Check the test with the user: is the speech clear, in the right language, in sync with the lips, and is the voice right? Voices can differ from clip to clip, so keep each version's talking to as few clips as possible. If speech is not good enough, offer the fallback: render the clips without lines (generate_audio: false, prompt the gestures and the demo only) and let the user record a voiceover.
Once the test is approved, submit the other hooks and the body clips together. Product close-ups or demos without speech can be made from a product photo in the same way, with the motion only in the prompt.
5. Captions and assembly
Download the clips as hook-a.mp4, hook-b.mp4, body-1.mp4 and so on. The steps below need ffmpeg.
Trim the silence at the start of each clip, so a hook speaks in its first second. This prints how far to cut, keeping 0.15 s before the first sound, or nothing when the clip already starts with sound:
cd eggapi-output/<campaign-slug>
lead=$(ffmpeg -hide_banner -nostats -i hook-a.mp4 -af silencedetect=noise=-35dB:d=0.3 -vn -f null - 2>&1 \
| awk '/silence_start/ {s=$NF} /silence_end/ {if (s+0 < 0.05 && $5 > 0.2) printf "%.2f", $5 - 0.15; exit}')
if [ -n "$lead" ]; then
ffmpeg -ss "$lead" -i hook-a.mp4 -c:v libx264 -crf 18 -pix_fmt yuv420p -c:a aac hook-a-trim.mp4
else
cp hook-a.mp4 hook-a-trim.mp4
fiJoin each version and even out the loudness; clips can differ by several decibels and peak at full scale:
printf "file '%s'\n" hook-a-trim.mp4 body-1-trim.mp4 > version-a.txt ffmpeg -f concat -safe 0 -i version-a.txt -af loudnorm=I=-14:TP=-1.5:LRA=11 -ar 48000 \ -c:v libx264 -crf 18 -pix_fmt yuv420p -c:a aac version-a-raw.mp4
Time the captions. One caption per phrase, without closing punctuation, so each fits on one line. Run the silencedetect command above on the joined video to see where each clip's speech starts and ends, then split that span across the clip's phrases in proportion to their length. Write the timings to version-a.srt.
Then burn in the captions. Many ffmpeg builds, including Homebrew's, ship without the subtitles filter, so check first with ffmpeg -hide_banner -filters | grep -w subtitles.
-
With the
subtitlesfilter:bashffmpeg -i version-a-raw.mp4 -vf "subtitles=version-a.srt:force_style='Fontsize=13,Bold=1,Outline=2,Alignment=2,MarginV=80'" -c:a copy version-a.mp4
For Chinese, Japanese or Korean add
FontName=with a font that has those characters, such asHiragino Sans GBorApple SD Gothic Neoon macOS. -
Without it, using ImageMagick: render each caption as an image about 80% of the frame wide, with the font size at about a 18th of the frame width, then overlay each one during its time range with ffmpeg's built-in
overlayfilter:bashFONT="/System/Library/Fonts/Hiragino Sans GB.ttc" W=$(ffprobe -v error -select_streams v -show_entries stream=width -of csv=p=0 version-a-raw.mp4) magick -background none -fill white -font "$FONT" -pointsize $((W / 18)) -size $((W * 8 / 10))x -gravity center caption:"{phrase 1}" \ -extent '%[fx:w+28]x%[fx:h+20]' -background '#000000A0' -flatten cap-1.png # ...one image per phrase, then: ffmpeg -i version-a-raw.mp4 -i cap-1.png -i cap-2.png -filter_complex \ "[0:v][1:v]overlay=(W-w)/2:H*0.62:enable='between(t,0,3)'[v1];[v1][2:v]overlay=(W-w)/2:H*0.62:enable='between(t,3,7)'[v]" \ -map "[v]" -map 0:a -c:v libx264 -crf 18 -pix_fmt yuv420p -c:a copy version-a.mp4Pick a font file that has the caption's characters: on macOS
Hiragino Sans GB.ttcfor Chinese,AppleSDGothicNeo.ttcfor Korean andHelvetica.ttcfor Latin scripts, all in/System/Library/Fonts/; on Linux find one withfc-list :lang=zh.
Either way the captions sit above the bottom quarter of the vertical frame, where the app's buttons cover it. Grab a frame to confirm: ffmpeg -ss 2 -i version-a.mp4 -frames:v 1 check.png.
If neither works, deliver the uncaptioned cut with its .srt file, which TikTok, CapCut and most editors can import. Without ffmpeg, deliver the clips and the .srt files, and tell the user how to install it (brew install ffmpeg on macOS, sudo apt install ffmpeg on Debian or Ubuntu).
6. Deliver
Save to eggapi-output/<campaign-slug>/: the presenter image, the first frames, every clip, version-a.mp4, version-b.mp4 and so on, their .srt files, and manifest.csv with the columns file,version,hook,duration,model,cost. Finish with the list of versions and the total cost, and remind the user to label the ads as AI-generated where the platform asks.
Edit your copy of this file to change the steps, defaults or models; the agent follows your version.