Communitygithub.com

product-feature-video

Use this skill when creating a feature/demo/launch video for a real, running SaaS product or open-source app — one built from actual screen recordings of the app plus voiceover, music, and light code-built b-roll, not a synthetic motion-graphics trailer. Trigger on

product-feature-video 是什么?

product-feature-video is a Claude Code agent skill that use this skill when creating a feature/demo/launch video for a real, running SaaS product or open-source app — one built from actual screen recordings of the app plus voiceover, music, and light code-built b-roll, not a synthetic motion-graphics trailer. Trigger on.

兼容平台Claude Code~Codex CLI~CursorGemini CLI
npx skills add https://github.com/akmishra56/product-video-skill/tree/main/skills/product-feature-video

在你喜欢的 AI 中提问

打开一个已预加载此 Agent Skill 的新对话。

文档

Product Feature Video Skill

Plan and build a demo/feature video for a real, running product — captured on real screens via Playwright, narrated with real TTS, scored with a real (open-source, license-clean) music bed, and assembled in Remotion. This is a different discipline from a synthetic motion-graphics product trailer: the footage already exists and can't be redrawn to fit a plan, so the plan has to fit the footage.

The key difference from a synthetic motion-graphics trailer

Plenty of Remotion video workflows assume you control every pixel and can re-time anything — because the whole video is built from scratch: mockups, generated backgrounds, invented UI. That assumption doesn't hold here. A real screen recording runs on its own clock; a real click happens on one specific frame, not whenever a storyboard wants it to; some moments (a native OS window, a browser's own toolbar, an interaction that completes in half a second) can't be re-shot to fit a plan at all. This skill is built around that constraint from the ground up — motion-design craft (easing, transitions, motion density) still matters and is covered in Phase 8, but the pipeline's real backbone is what footage-driven video specifically requires: a prototype-fallback audit for the un-capturable moments, stability-verified targeting for zooms and click emphasis on real footage, and timing driven by actual asset durations rather than a plan.

Core principle: real footage first, prototype second, illustration last

For every scene, prefer in this order:

  1. Real footage — an actual Playwright recording of the actual running app.
  2. A reviewed HTML prototype, captured the same way, for anything real footage structurally cannot show (see Phase 2).
  3. An honest, clearly-illustrated placeholder — only when neither of the above is achievable yet, and only if it doesn't pretend to be real.

Never fake option 1 with option 3 dressed up to look real. A viewer trusts a demo video precisely because it's real; one fabricated-looking moment undermines all the genuine ones.


Phase 0: Brief

Ask once, together, before anything else:

  1. Product & repo — name, one-line description, URL/local path to the running app or repo.
  2. Audience & tone — who's watching, and what should they feel (impressed / reassured / excited)?
  3. Duration — 30s teaser, 60-90s walkthrough, or a fuller 2-3 min tour? Real footage runs long fast; be honest about scope.
  4. The one moment that must land — if the viewer remembers exactly one thing, what is it? This becomes the climax/hero beat.
  5. Login/test account — does capturing the app require an account? Can a disposable demo account be created, or does one already exist?
  6. Publishing context — landing page, YouTube, social feed (autoplay-muted matters a lot for caption/on-screen-text decisions).

If the product is a browser extension, also ask whether Manifest V3 is in play (changes the Playwright launch strategy — see Phase 6).


Phase 1: Recon — extract the real design system, don't invent one

Before any aesthetic decision, read the actual product:

  • Pull the real color tokens, font stack, and spacing scale from the app's own CSS/theme file. Use them as-is, even if the accent color happens to resemble a "generic AI SaaS" cliché (indigo/purple on near-black is extremely common in real product palettes too — it's only a red flag when it's an invented default, not when it's the actual product). Authenticity beats novelty here.
  • Note the product's actual UI copy, terminology, and information architecture — the video's captions and voiceover should use the app's own words, not marketing paraphrase.
  • Run the app once, end to end, as a user would. Identify: what's the single best "hero" interaction? What's genuinely hard to demonstrate live (see Phase 2)?

Save the extracted tokens (colors, fonts) into a tokens.ts/tokens.css the whole video project imports from one place — every scene, real or code-built, should visibly belong to the same product.


Phase 2: Prototype-Fallback Audit (do this before the storyboard)

Walk the planned feature list and flag anything Playwright structurally cannot capture:

  • OS-level browser chrome — the extension toolbar icon, chrome://extensions, right-click context menus, native file pickers, native OS dialogs. Playwright's recordVideo only captures page content, never the browser's own UI.
  • Native desktop shells (Tauri, Electron once packaged, Qt, etc.) — these aren't a web page Playwright can attach to at all.
  • Interactions too fast or too small to reliably showcase — a toggle flip that completes in under half a second of real footage is a poor target for a crop-zoom or a click-emphasis ring; both need a stable multi-frame window to land on (see Phase 8's ZoomCue/ClickRing pattern and its stability rule).

For each flagged moment, decide: real footage with a workaround (e.g., seed state via an API call instead of a real click, per Phase 6), a reviewed HTML prototype (Phase 7), or an honestly-labeled illustration (last resort, and say so in a code comment).

Doing this audit before the storyboard, not after, prevents the storyboard from committing to timing and coverage that the actual capture step can't deliver — this was the single biggest source of rework across TabNemo's own v1-v3.


Phase 3: Storyboard & Scene Plan

Narrative arc

Same shape works whether the footage is real or synthetic:

ActPurposeShare of runtime
HookCold open — the problem, or a striking moment, before the product name appears5-8%
RevealBrand/product name lands8-10%
Showcase3-5 real features, one beat each45-55%
ClimaxThe hero moment identified in Phase 0 — give it the most visual care8-12%
CloseLogo, tagline, CTA8-10%

Per-scene spec

For every scene, decide up front and write down:

  • Source: real clip / prototype capture / code-built b-roll (per Phase 2's audit).
  • Narration: exact text, or null. Narration length is a real constraint — TTS audio duration becomes the scene's minimum length (see Phase 4).
  • Real footage runs on its own clock — a scene's durationInFrames is max(real clip length, narration length), probed from the actual rendered assets, never assumed from a plan. Where narration outlasts the clip, hold the clip's last frame rather than looping or cutting the line off.
  • Camera plan: any punch-in/zoom or click-emphasis, with real coordinates — decided from actual stills, not guessed (Phase 8).

Review checkpoint

Show the scene list + narration text before recording a single clip or generating a note of audio. A plan built on wrong assumptions about available footage is expensive to unwind once TTS and Playwright scripts exist.


Phase 4: Voiceover — script and generation

Write two documents, not one:

  1. A review script — one block per scene, with its planned timecode, so a human can read the whole narration arc and approve pacing before generation.
  2. A TTS input script — the same lines, split one-per-scene-file, pacing carried by punctuation only (or SSML <break>/<emphasis> tags if the engine supports them), so each scene becomes its own audio clip placeable at its own offset.

Generation tooling

  • ttsmp3.com — free, no signup, Amazon-Polly-backed. No public API, so generation is one clip at a time through the browser UI — budget a single focused session to do them all, and name output files by scene (scene-03.mp3, not tts_export_1.mp3).
  • For a pipeline that needs to run unattended (CI, an agent with no browser-in-the-loop), prefer an actually scriptable open-source/local option instead: Piper (fully offline, MIT-licensed, fast, decent voices) or edge-tts (wraps Microsoft Edge's read-aloud voices, free, no key, scriptable, but depends on an external service staying available). Gemini TTS and ElevenLabs are strong non-open alternatives if API cost is acceptable. Whichever engine, if it doesn't guarantee voice consistency across separate calls, generate all segments in a single request with pause markers between them, then split on detected silence — that one trick fixes the "different voice per line" problem regardless of engine.

After generation — audio is the source of truth

Probe every generated file's real duration (don't trust an estimate). Propagate real durations back into the scene plan's timing. This one step prevented every timing bug in TabNemo's build — narration is what the viewer's ear measures a scene against, so it, not the plan, sets the clock.


Phase 5: Background Score — get a catchy one without a licensing landmine

A flat pad-and-drone loop reads as elevator hold music, not a "catchy" bed — the difference is almost always a repeating melodic hook, not just harmonic pads. Three real options, in order of "safest license" to "most reliably catchy":

Option A — Procedural synthesis (zero license risk, needs real effort to not sound flat)

Generate a short, seamlessly-loopable stereo WAV in plain Node/Python — no models, no dependencies, no licensing question at all, because you wrote every sample. The concrete recipe that worked:

  • A short (8-16 bar) chord loop using simple oscillators (sine/triangle) with slow attack/release envelopes — this alone is the "elevator music" trap; it is NOT the hook.
  • The actual hook: a Karplus-Strong plucked-string melody riding on top, in a fixed repeating rhythmic pattern, one octave above the pad — this is what makes a synthesized loop memorable rather than ambient wallpaper. Don't skip this layer.
  • A soft kick + hat pulse underneath for forward motion, quiet enough to sit under narration.
  • Normalize to leave headroom, loop-length chosen so the whole progression completes on a bar boundary (no audible seam).

This is completely safe to ship commercially, but the melodic-hook layer is what separates "catchy" from "generic" — budget real iteration on it, and get a listen-and-confirm from the user before wiring it into a full render (regenerating a 15-20s loop is cheap; re-rendering the whole video to audition a different one is not).

Option B — Open-weight generative music models (more "produced," check the license every time)

  • Stability AI's Stable Audio Open — open weights, text-prompted, genuinely produces full arrangements with real melodic content. License terms are more permissive than most generative-audio models but do carry revenue-based conditions — re-read the current license before any commercial use, since these terms move.
  • Meta's MusicGen (via the audiocraft library) — excellent quality, but its music-model weights are typically licensed CC-BY-NC (non-commercial) — do not use generated output in a monetized product's launch video without separately clearing that.
  • Either can run locally (GPU recommended) or via a hosted inference endpoint (Replicate, Hugging Face Spaces) if local compute isn't available.

Option C (recommended default) — use B for the idea, ship A for the asset

Prompt an open-weight model locally for inspiration only ("catchy synth-pop hook, ~110bpm, warm optimistic tech product feel"), listen to a few generations, and re-implement the melodic idea you like as a procedural pluck pattern (Option A). This gets a genuinely catchier result than starting from a blank page, while keeping the shipped asset license-clean because you wrote the final samples yourself.

Whichever option: duck the bed under narration via a volume callback, not a static level (Remotion's <Audio volume={(f) => ...}> — see Phase 8), and let it swell during any silent cutaway beats instead of leaving true silence there.


Phase 6: Real Footage Capture (Playwright)

Launch strategy for an app with login (and optionally a browser extension)

A two-phase launch avoids fighting Playwright's persistent-context/extension-loading constraints:

  1. Phase A — headless, no extension. Launch normally, log in (or sign up) through the real UI, let cookies land in a real cookie jar / persistent user-data dir.
  2. Phase B — headed (or still headless, if the target supports it), same user-data dir, --load-extension=<path> for Manifest V3 extensions. launchPersistentContext is required for MV3 — a fresh browser.newContext() won't load the extension.

Seeding state without a slow, flaky UI path

For state that doesn't need to be shown being created (e.g., pre-existing history items so a search demo has real results), seed it via context.request — it shares cookies with the browser context, so authenticated API calls work without a second login flow. One real bug to avoid: don't set Content-Type: application/json unconditionally in a request helper — a bodyless POST with that header set can trip a strict "empty JSON body" error on some backends. Only set it when a body is actually present.

Recording

context.recordVideo on a fixed {width, height} matching the final composition's canvas avoids any resize/letterbox surprises later. Capture generously (a few seconds of pre-roll/post-roll around the actual action) — trimming in the edit is free, re-shooting is not.

Defensive scripting for onboarding/first-run states

A fresh account's first UI visit often has an onboarding overlay that isn't present on subsequent runs — dismiss it defensively (try a "Skip"/"Not now" button with a short timeout that's allowed to fail silently) regardless of which flow led there, since it can appear on more paths than expected.

MFA / TOTP flows

If the product has authenticator-app-based MFA, don't fake the code — inline the real RFC 6238 TOTP algorithm (it's ~30 lines, no dependency needed) and drive the actual flow with a real generated code. A viewer who knows what MFA setup looks like will notice a fake "123456."

Real text-selection demos

Driving a real window.getSelection()/Range selection (not a synthetic highlight overlay) proves the feature actually works on arbitrary page content. Watch for pages whose first paragraph is empty or filled with hidden template markup (Wikipedia's IPA-pronunciation templates are a known trap) — filter candidate text by minimum length and known marker strings before selecting it.

Before trusting any specific frame number

Real footage seek precision is not perfectly deterministic near a fast state change — probing the same frame twice can occasionally decode differently right at a transition boundary. Before pinning a zoom or click-ring to a specific frame, probe several frames across the transition and confirm the window is stable (tens of frames wide, not single-digit). If a transition genuinely only holds for ~15-20 frames, that moment is not a safe target for frame-precise treatment — see Phase 8's stability rule, and consider a Phase 7 prototype instead.


Phase 7: Prototype Capture Workflow (the fallback from Phase 2)

For any scene flagged in Phase 2:

  1. Build the HTML prototype using the tokens extracted in Phase 1 — same fonts (link Google Fonts directly, or inline @font-face), same colors, same border-radius/spacing scale as the real app. Recreate only what the scene needs (e.g., a native-looking title bar wrapping the real UI's actual layout for a "desktop app" scene; a single oversized control for a "close-up" scene).
  2. Review it before capturing anything. If using Claude, publish it as an Artifact so a human can see and approve the look in a browser — this is a fast, cheap checkpoint before spending a capture+render cycle on it. (Any equivalent quick-preview method works if not using Claude.)
  3. Capture from the local file, not a hosted preview link. A published/hosted preview may require an authenticated session a headless browser doesn't have — point Playwright at the local file directly (file://...) instead. Same technique as Phase 6: page.screenshot() for a static mockup, or context.recordVideo through a short scripted CSS/JS animation (e.g., an auto-triggered setTimeout state change) for a mockup that needs to show a transition.
  4. Integrate it honestly. In code, comment why this is a prototype and not real footage, and consider whether it fully replaces the illustrated version of that scene or sits alongside the real footage as a supplementary inset (a bordered, rounded picture-in-picture panel reads clearly as "an inset," not as a claim that it's the real screen).

Phase 8: Remotion Assembly

The two duration-math bugs to check for, every time

  1. Transition overlap. TransitionSeries overlaps adjacent scenes by the transition's own duration — totalDuration = sum(scene durations) - sum(transition durations). Get this wrong and the video either cuts off early or ends on blank frames.
  2. Frame numbering after transitions shift. Any code that reasons about "what's the global frame right now" for something outside the TransitionSeries itself (a progress bar, chapter labels, music-ducking cues keyed to absolute frame ranges) must use the actual post-transition frame numbers, not the naive sum of each scene's own duration. Every transition boundary shifts everything after it earlier by that transition's duration. This is exactly as common and exactly as easy to miss as bug #1 — verify with a still at a few specific frames, not by eye in Studio.

Motion — what transfers from the synthetic-trailer skills

  • Never linear easing — Bézier curves or spring physics only; even a synthetic-feeling scene like a title card needs this.
  • Motion density minimum, per code-built scene: at least 3 simultaneous properties on any entrance (opacity + transform + a 3rd — blur is the cheapest to add and reads as a camera-focus pull), plus at least one independent ambient/background layer (a slow-breathing radial glow behind the content works well and costs almost nothing).
  • Vary transitions by what they're bridgingfade() for a real mood/tone shift, slide() (with a spring damping: 200 to avoid overshoot-peek of the next scene) for forward progression within the same feature/chapter. One fade at every cut reads as default, not deliberate.
  • A held frame is not a wasted frame — Remotion's <Video> naturally freezes on its last frame once the source runs out; use this deliberately when narration outruns a clip rather than looping or fading early.

Two footage-specific patterns worth reusing directly

ZoomCue / crop-zoom on real footage — punch into the one control being demonstrated instead of showing the full 1:1 recording:

function zoomTransform(frame, cues) {
  const RAMP = 16;
  for (const cue of cues) {
    if (frame < cue.from - RAMP || frame > cue.to + RAMP) continue;
    const targetScale = Math.min(cue.maxScale ?? 1.9, Math.min(compW / cue.w, compH / cue.h));
    const originX = cue.x + cue.w / 2, originY = cue.y + cue.h / 2;
    let t = frame < cue.from
      ? interpolate(frame, [cue.from - RAMP, cue.from], [0, 1], { extrapolateLeft: "clamp" })
      : frame <= cue.to ? 1
      : interpolate(frame, [cue.to, cue.to + RAMP], [1, 0], { extrapolateRight: "clamp" });
    if (t > 0) return { scale: 1 + (targetScale - 1) * t, originX, originY };
  }
  return { scale: 1, originX: compW / 2, originY: compH / 2 };
}
// apply as: transform: `scale(${scale})`, transformOrigin: `${originX}px ${originY}px`

ClickRing at a real click's exact pixel+frame — solves what a zoom can't (a click too fast to hold a crop on):

function ClickRing({ atFrame, x, y }) {
  const frame = useCurrentFrame();
  const local = frame - atFrame;
  if (local < -2 || local > 26) return null;
  const ringSize = 16 + interpolate(local, [0, 22], [0, 1], { extrapolateLeft: "clamp", extrapolateRight: "clamp" }) * 70;
  const ringOpacity = interpolate(local, [0, 4, 22], [0, 0.9, 0], { extrapolateLeft: "clamp", extrapolateRight: "clamp" });
  // render an expanding ring + a brief white flash centered on (x, y), both pointerEvents:"none"
}

The stability rule for both: only place a ZoomCue or ClickRing where probing several nearby frames confirms the target state holds for tens of frames, not single-digits. Where a real transition is too fast to be a safe target (confirmed by probing, not assumed), don't force it — either skip the treatment for that beat, or fall back to a Phase 7 prototype recreation shown as a supplementary inset rather than a replacement.

Music ducking

<Loop durationInFrames={loopLengthInFrames}>
  <Audio src={staticFile("bg-music-loop.wav")} volume={(frame) => duckedVolume(frame)} />
</Loop>

duckedVolume should return a lower value during any window narration is playing (with a short ramp at each edge, not a hard cut) and a higher value during silent cutaways and the cold open/outro tails — computed from the actual post-transition frame numbers (bug #2 above).


Phase 9: Post-Processing

  • ffmpeg is the one universal tool worth having in the pipeline regardless of everything else: use its loudnorm filter to normalize narration and music to a consistent loudness target before final mixing (hand-tuned volume constants drift take-to-take; a normalization pass generalizes), and it's the standard fallback for trimming, format conversion, or producing a second, more compressed delivery format alongside Remotion's master render.
  • @remotion/media-parser (already a Remotion dependency) covers duration/dimension probing for every asset without needing ffmpeg for that specific job — use it to get real clip and narration durations before locking scene timing (Phase 3/4).
  • For a quick visual sanity check before committing to a full render: remotion still <Composition> out/frame.png --frame=<n> on a handful of representative frames is far cheaper than rendering the whole thing to discover a layout bug.

Phase 10: Pre-Render Review

  • Every scene's durationInFrames is max(real asset length, narration length), probed from actual files
  • Total durationInFrames accounts for transition overlap (bug #1)
  • Any absolute-frame-keyed logic (progress bar, music ducking, chapter labels) uses post-transition frame numbers (bug #2)
  • Every ZoomCue/ClickRing was verified stable against multiple probed stills before being wired in
  • No silent stretch longer than a couple seconds without either narration, music presence, or an intentional Punchline/beat
  • Transitions vary by what they bridge, not uniform throughout
  • Every code-built scene has ≥3 motion properties on its entrance and an ambient layer
  • Every prototype-sourced scene is commented as such, with the reason it isn't real footage
  • Narration audio and music are loudness-normalized relative to each other
  • First 3 seconds are compelling even muted (captions/on-screen text cover any load-bearing narration)

Phase 11: Handoff — Export Individual Clips for Manual Editing

Not every user wants the fully-composited render as the end of the road. Some will want to open the pieces in Remotion Studio, DaVinci Resolve, Premiere, or any other NLE and cut it themselves. Always produce that path alongside the final MP4, not instead of it — it costs little once the assets already exist, and it's the difference between handing someone a locked video and handing them a real project.

What goes in the export folder

Create an export/ directory (separate from public/, which stays Remotion-internal and can keep whatever technical naming the code needs) containing, per scene, in storyboard order:

  • Every real Playwright-captured clip, trimmed to the scene's actual used range — not the raw, un-trimmed recording.
  • Every prototype-captured clip or still (Phase 7's output), named so it's obvious it's a prototype (e.g. a -prototype suffix), not indistinguishable from real footage.
  • Every code-built scene, rendered out as its own short standalone clip — use remotion render <Composition> out.mp4 --frame-range=<start>-<end> scoped to that scene's frame range, so even the synthetic b-roll (title cards, outro, ambient-glow text beats) becomes a real video file any NLE can import, instead of something that only exists as React code.
  • Every narration file, already named by scene (Phase 4).
  • The music bed — both the final mixed/ducked stem if one was rendered separately, and the raw unlooped source loop (Phase 5), so an editor can retime or extend it rather than being stuck with one fixed length.

Naming convention

Number everything by scene order matching the storyboard, not by internal filename — 01-cold-open.mp4, 02-title-card.mp4, 03-install-and-capture.webm, 03-install-and-capture-narration.mp3, and so on. An editor should be able to sort the folder alphabetically and get the timeline order for free, without cross-referencing any code.

The manifest

Ship a short export/MANIFEST.md (or .json, if the target tool can import it) listing, per scene: its number and name, source type (real / prototype / code-built), the exact narration text and its file, the real duration in seconds, and — for any scene that had a ZoomCue or ClickRing — the target coordinates and frame window, so a manual editor can reproduce or intentionally skip that emphasis rather than losing the information silently.


Appendix: Repository Layout

video/
├── public/
│   ├── clips/        # real Playwright recordings + prototype-captured clips
│   ├── audio/        # narration + the music bed
│   └── images/       # prototype-captured stills
├── src/
│   ├── tokens.ts             # colors/fonts extracted from the real app (Phase 1)
│   ├── musicCues.ts          # duck/rise volume function (Phase 8)
│   ├── TabNemoDemo.tsx        # <- rename per project; the master TransitionSeries
│   └── scenes/
│       ├── LiveClip.tsx      # real-footage wrapper: narration, ZoomCue, ClickRing
│       ├── ClickRing.tsx
│       └── ...                # code-built b-roll scenes (title card, outro, etc.)
├── export/                   # Phase 11 — the manual-editing handoff
│   ├── MANIFEST.md
│   ├── 01-cold-open.mp4
│   ├── 02-title-card.mp4
│   ├── 03-install-and-capture.webm
│   ├── 03-install-and-capture-narration.mp3
│   ├── ...
│   └── music-bed-loop.wav
├── generate-music.mjs        # Phase 5, Option A
├── probe-durations.mjs       # Phase 4/9, via @remotion/media-parser
└── package.json              # scripts: dev (remotion studio), render, still

相关技能