saas-demo-maker
Overview
This skill captures screenshots from any web or local app and renders a polished,
silent SaaS demo video (MP4). It is modeled on the proven Castify pipeline at
~/SaaS/castify/generate_castify_demo.py and generalizes it for any Aperone project.
When invoked, you MUST produce an MP4 by the end of the session. Do not stop at "here is a plan" — execute it.
When to Use
以下のいずれかに該当すれば必ず起動する(部分一致・言い換えも含む)。
日本語トリガー
- デモ動画 / デモMP4 / デモ動画を作って / デモ動画を生成して
- プロモ動画 / 紹介動画 / サービス紹介動画
- スクショ撮って動画にして / スクリーンショットを動画にして
- 動画を作って(SaaSやアプリの文脈)
- 既存のスクショから動画を作って
- 画面録画(静止画合成で代替する旨を伝えてから対応)
英語トリガー
- demo video / demo MP4 / product demo
- promotional video / promo video / intro video
- screenshot to video / capture and render
- make a video from screenshots
/saas-demo-maker コマンド直接指定
判断が難しいケース(起動してよい)
- 「動画」「MP4」「スクショ」を含み、アプリ・SaaS・ツールの文脈がある
- ローカルアプリや公開URLへの言及がある
- 「既存の画像から動画にして」という依頼
Inputs
Parse from the user's message:
| Input | Default | Notes |
|---|---|---|
url | — | Full URL or http://localhost:3000 |
product_name | inferred from project | Used in pill and config |
brand_color | #7c3aed | Override VIOLET constant |
screens | 5 | Number of screenshots to capture |
output_path | output/saas_demo.mp4 | Relative to project root |
mode | full | full / capture / render |
locale | ja if Japanese message, else en | Affects step text |
steps | from templates.md preset | User-supplied text wins |
assets_dir | output/screenshots | Source images for render mode |
Modes
full mode
Capture screenshots from a live URL or local app, then render the video.
Trigger examples:
/saas-demo-maker https://castify.aperone.com からサービスカード用デモ動画を作って。5画面、20秒以内、ブランドカラーは#7c3aed、出力はoutput/castify_demo.mp4
/saas-demo-maker http://localhost:3000 のLP、ログイン、ダッシュボード、入力、結果画面を撮って、無音ループ用のSaaSデモMP4を作って
capture mode
Screenshot capture only — no video rendering.
Trigger examples:
/saas-demo-maker スクショだけ撮って。LP、ログイン、ダッシュボード、入力、結果の5枚
render mode
Render video from existing screenshots — no capture step.
Trigger examples:
/saas-demo-maker assetsの既存スクショ5枚から動画だけ作って。プロダクト名はAperone Academy、ブランドカラーは#7c3aed
/saas-demo-maker assetsの5枚から動画だけ作って。プロダクト名はCastify、出力はoutput/castify_demo.mp4
Workflow
Step 0 — Pre-flight
- Read
demo_config.jsonif it exists in the current project. - Check for existing screenshots in
output/screenshots/orassets/. - Detect mode from user message (default:
full). - Confirm
output/directory exists or create it.
Step 1 — Generate config
Write demo_config.json to project root:
- Use values parsed from user message.
- Choose step texts from
templates.mdpreset matchingproduct_name, or generic template for the screen count, or user-supplied text. - Write the file now, before any script generation.
Step 2 — Write scripts (full or capture mode)
Write capture_screenshots.py to project root.
Write generate_saas_demo.py to project root.
Write README_demo_video.md to project root (first time only; skip if exists).
For render mode, skip capture_screenshots.py.
Step 3 — Install dependencies (if missing)
Check and install as needed (see reference.md for commands):
ffmpeg—which ffmpeg || brew install ffmpegpillow—python3 -c "import PIL" || python3 -m pip install pillow- For TypeScript capture: check
node_modules/playwrightexists
Step 4 — Capture (full or capture mode)
Run the capture script:
python3 capture_screenshots.py
Or if the project has an existing Playwright TypeScript capture script (e.g.,
scripts/demo-screenshots.ts), prefer that:
npx tsx scripts/demo-screenshots.ts
Check that output/screenshots/ contains at least 2 PNG files after capture.
If capture fails, read the error, fix capture_screenshots.py, and retry.
Step 5 — Render (full or render mode)
Run:
python3 generate_saas_demo.py
Watch for errors. If the script crashes, read the traceback, fix the script, and re-run. Do not ask the user — self-heal up to 3 attempts.
Step 6 — Quality check
Run ffprobe to verify:
ffprobe -v error -select_streams v:0 \
-show_entries stream=width,height,duration,codec_name,pix_fmt \
-of default output/saas_demo.mp4
Verify all 10 items from the quality checklist in reference.md.
If any item fails, fix and re-render.
Step 7 — Report
Output the final response (see "Final response format" below).
Screenshot Capture Spec
Resolution
1280 × 720 (Chromium viewport)
Browser
Playwright Chromium, headless.
Wait strategy
await page.goto(url, wait_until="networkidle")
await page.wait_for_timeout(800) # extra settle time
Candidate local URLs (try in order)
http://localhost:3000
http://localhost:5173
http://localhost:8000
http://localhost:8080
Output directory
output/screenshots/ relative to project root.
Standard filenames
01-landing.png
02-login.png
03-dashboard.png
04-input.png
05-result.png
06-options.png (if 6+ screens)
07-processing.png (if 7 screens)
Local app startup
- Read
package.jsonto find the dev script (dev,start, etc.). - Start the dev server in the background.
- Wait until the candidate URL responds (max 30 s).
- Capture screenshots.
- Stop the dev server.
Auth handling
- If
.envcontains test credentials and a dev-bypass flag (e.g.,DEV_BYPASS_AUTH=true), set it in the environment before launching. - For login screens: fill a demo email (
demo@<product>.io) without submitting — capture the filled state as the login screen. - Do not attempt real auth bypass or brute-force login.
- If a page redirects to sign-in and bypass is unavailable, use the login screen as one of the demo screens (it looks credible).
Dev-mode artifacts
Clerk development mode banner, Next.js dev indicator, debug overlays — do NOT try to remove them in the browser. Instead, cover them in the render step with a gradient overlay or a dark panel at the bottom of the card.
Render Spec
Architecture
Generate all frames as PNG files in output/frames/, then encode with ffmpeg.
Do NOT use moviepy for encoding — use ffmpeg subprocess directly.
Image sizing
card = ImageOps.fit(image.convert("RGBA"), (card_w, card_h),
method=Image.Resampling.LANCZOS)
This is the ONLY allowed image sizing method. The screenshot must be completely static inside each scene — no zoom, no pan, no Ken Burns.
Ken Burns — PROHIBITED
Do not apply any per-frame scale factor or position offset to the screenshot. The screenshot pixel content must not change between frame 0 and frame 98 of a scene. Only overlays (caption, pill, progress bar, glow) may animate.
Caption animation (allowed)
The caption panel may fade in and translate up 8 px over the first 20 % of a
scene (scene_t < 0.2). This is the only allowed animation on the image layer.
Timing
SCENE_SECONDS = 3.3 # fixed — do not change
TRANSITION_SECS = 0.4 # fixed — do not change
FPS = 30
SCENE_FRAMES = 99
TRANSITION_FRAMES = 12
Transition
Horizontal slide: outgoing frame exits left, incoming frame enters from right.
Use ease_in_out_cubic. Add a narrow seam-softening veil at the cut boundary.
See reference.md for the full algorithm.
ffmpeg command
Use the exact command from reference.md. Key flags:
-c:v libx264 -pix_fmt yuv420p -movflags +faststart -an -crf 18
Visual Design Rules
Background
#020617 (BG) — fill behind card
Card layout
card_box = (34, 22, WIDTH-34, HEIGHT-22)
radius = 30
Top chrome accent line at card_box[1] + 48.
Card border: 1 px CARD_EDGE.
Overlays (all required)
- Progress bar — segmented, top of card, violet fill for completed steps
- Caption panel — bottom-left, glass dark panel, STEP N / name / body
- Product pill — bottom-right, brand color dot + product name
- Focus glow — violet glow around the key UI element (see reference.md for inference)
- Vignette — subtle dark rounded edge on the full frame
- Bottom gradient — covers dev-mode artifacts in the lower card area when needed
Prohibited design elements
- White text on plain black box (no glass effect)
- Excessive bloom / oversaturated glow
- 3D transforms or perspective warps
- Any distortion of the screenshot UI
- Editing or replacing text visible in the screenshot
Configuration Rules
Write demo_config.json before running any script.
When re-running in render mode, read the existing config first and
merge user overrides rather than overwriting.
Schema:
{
"product_name": "string",
"brand_color": "#7c3aed",
"output_path": "output/saas_demo.mp4",
"screenshots": ["output/screenshots/01-landing.png"],
"steps": [{ "step": 1, "name": "...", "body": "...", "focus": null }],
"fps": 30,
"resolution": [1280, 720],
"mode": "full",
"locale": "ja"
}
Implementation Requirements
capture_screenshots.py responsibilities:
- Launch Playwright Chromium headless
- Set viewport 1280 × 720
- Navigate each target URL, wait for networkidle + 800 ms settle
- Save PNGs to
output/screenshots/with standard filenames - Move mouse off-screen before each screenshot (
page.mouse.move(1400, 800)) - Print success/failure per screenshot
- Exit code 0 on success, 1 on failure
generate_saas_demo.py must contain all functions listed in reference.md.
Key invariants:
SCENE_SECONDS = 3.3— hardcoded, no config overrideTRANSITION_SECONDS = 0.4— hardcoded, no config overridefit_cover()callsImageOps.fit()— no zoom arithmeticrender_scene()does NOT modify scale per frameencode_video()calls ffmpeg subprocess with-anflag
Quality Checklist
After encode_video() succeeds, verify ALL of these before reporting done:
output/saas_demo.mp4exists, size > 100 KB- Duration within ±2 s of (screen_count × 3.3 s)
- Width=1280, Height=720 (confirmed by ffprobe)
- No audio stream
- codec_name = h264
- pix_fmt = yuv420p
- All screenshots appear in correct order
- No glaring dev-mode banner visible (covered by overlay if needed)
- Result/output screen clearly communicates product value
- Caption text is readable on all scenes
Run this check:
ffprobe -v error -select_streams v:0 \
-show_entries stream=width,height,duration,codec_name,pix_fmt \
-of default output/saas_demo.mp4
Failure Recovery
| Failure | Recovery |
|---|---|
ffmpeg not found | brew install ffmpeg, then retry |
PIL not found | python3 -m pip install pillow, then retry |