EggAPI/skills / eggapi-ugc-video-ad
← all skills
ads · performance marketers

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

eggapi-ugc-video-ad/SKILL.mdraw ↗

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_models once at the start. Use the model IDs this skill names when they are listed; otherwise pick the closest model with the same generation_type. unit_price_usd is the price per image, or per second of video; unit_price_by_resolution overrides it for a given resolution.

  • Local files. The generate tools only accept public http(s) URLs. Upload local files first (up to 20 MB per image):

    bash
    curl -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. If EGGAPI_API_KEY is 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_image and generate_video return a task_id at once. Submit every independent task first, then poll get_task for each until done is true: every 5-10 s for images, 10-15 s for videos (usually 1-3 min). If a generate call already returns urls, 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_outputs only 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, with curl -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 cost and 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. A failed task: read error, 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

  1. Presenter: generate_image with google/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.

  2. First frames: for each clip, make its opening frame with google/nano-banana-pro/edit (alternative bytedance/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_video with bytedance/seedance-2.5/image-to-video, which gave the most natural speech in testing. alibaba/wan-3.0/image-to-video costs 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: 720p for the test, 1080p for 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:

bash
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
fi

Join each version and even out the loudness; clips can differ by several decibels and peak at full scale:

bash
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 subtitles filter:

    bash
    ffmpeg -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 as Hiragino Sans GB or Apple SD Gothic Neo on 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 overlay filter:

    bash
    FONT="/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.mp4

    Pick a font file that has the caption's characters: on macOS Hiragino Sans GB.ttc for Chinese, AppleSDGothicNeo.ttc for Korean and Helvetica.ttc for Latin scripts, all in /System/Library/Fonts/; on Linux find one with fc-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.

connected
mcp https://api.eggapi.ai/mcp
models ...
Claude Code · Codex · MCP
█