Scriptable cinematic screen recorder for product demos — write a YAML scenario, get a professional recording. Powered by Playwright CDP. The demo above was made with 1 file, 239 lines.
Write a YAML scenario, hit record — Clipwise handles all the cinematic magic.
npx clipwise demo to instantly record the built-in dashboard — no setup needed.smartWait + smartSpeed for API call compression. HEVC 10-bit + AV1 codec support.Install Clipwise, run the demo, or write your own scenario.
npx without installing).npm install -D clipwise
npx clipwise demo
npx clipwise demo --device iphone # iPhone mockup
npx clipwise demo --device android # Android mockup
npx clipwise demo --url https://kwakseongjae.github.io/clipwise/demo/ # Custom URL
npx clipwise init # Creates clipwise.yaml
# Edit clipwise.yaml — set URL to your site
npx clipwise record clipwise.yaml -f mp4 # Record!
import { ClipwiseRecorder, CanvasRenderer, encodeMp4, loadScenario } from "clipwise";
const scenario = await loadScenario("my-scenario.yaml");
const recorder = new ClipwiseRecorder();
const session = await recorder.record(scenario);
const renderer = new CanvasRenderer(scenario.effects, scenario.output, scenario.steps);
const frames = await renderer.composeAll(session.frames);
const mp4 = await encodeMp4(frames, scenario.output);
Install the built-in Claude Code skill and generate scenarios with natural language. One command to install, one slash command to use.
.claude/skills/ directory. Run once per project (or globally).npx clipwise install-skill
/clipwise in any Claude Code session. Describe what you want to record in plain language./clipwise
> Record a demo of my dashboard at http://localhost:3000
— click login, type credentials, navigate to analytics
npx clipwise validate, then records the MP4 — all automatically.After upgrading clipwise, re-run npx clipwise install-skill to get the latest skill.
All effects are optional with sensible defaults. Mix and match for your style.
zoom:
enabled: true
intensity: light # subtle|light|moderate|strong|dramatic
duration: 800
cursor:
enabled: true
speed: "normal"
trail: true
highlight: true
background:
type: gradient
value: "linear-gradient(135deg,
#667eea 0%, #764ba2 100%)"
padding: 48
borderRadius: 14
deviceFrame:
enabled: true
type: browser # or iphone/ipad/android
darkMode: true
showTyping: true to enable.keystroke:
enabled: true
showTyping: true # default: false
position: bottom-center
fontSize: 16
fadeAfter: 1500
speedRamp:
enabled: true
idleSpeed: 3.0
actionSpeed: 0.8
A scenario has 4 sections: metadata, effects, output, and steps.
name: "My Demo"
description: "Optional description"
viewport:
width: 1280 # Browser width (default: 1280)
height: 800 # Browser height (default: 800)
effects:
# See "Effects" section below
output:
format: mp4 # gif | mp4 | png-sequence
width: 1280
height: 800
fps: 30 # 1-60
preset: balanced # social | balanced | archive
steps:
- name: "Step name"
actions:
- action: navigate
url: "https://example.com"
captureDelay: 200 # ms to wait after actions
holdDuration: 800 # ms to hold on result
transition: none # none | fade | slide-left | slide-up | blur
effects: # Per-step effects override (optional)
zoom:
enabled: false # Disable zoom for this step only
| Action | Parameters | Default | Description |
|---|---|---|---|
navigate | url, waitUntil? | networkidle | Navigate to URL |
click | selector, delay?, timeout? | Click an element | |
type | selector, text, delay?, timeout? | delay: 50 | Type text (char-by-char) |
hover | selector, timeout? | Hover over element | |
scroll | y?, x?, selector?, smooth?, timeout? | smooth: true | Scroll by offset |
wait | duration | Wait (ms) | |
screenshot | name?, fullPage? | fullPage: false | Capture marker |
| Action | Parameters | Default | Description |
|---|---|---|---|
waitForSelector | selector, state?, timeout? | visible, 15s | Wait for element state |
waitForNavigation | waitUntil?, timeout? | networkidle, 15s | Wait for page load |
waitForURL | url, timeout? | 15s | Wait for URL match |
waitForFunction | expression, polling?, timeout? | raf, 30s | Wait for JS expression to be truthy |
waitForResponse | url, status?, timeout? | 30s | Wait for network response (URL substring match) |
# Wait for element to appear after API call
- action: waitForSelector
selector: ".result-panel"
state: visible
timeout: 20000
# Wait for AI streaming to complete
- action: waitForFunction
expression: "document.querySelector('.ai-response')?.dataset.done === 'true'"
timeout: 60000
# Wait for specific API response
- action: waitForResponse
url: "/api/chat/completions"
status: 200
timeout: 60000
Copy the complete YAML schema reference. Paste into Claude Code, Cursor, ChatGPT, or any AI assistant to generate scenarios instantly.
This block contains everything an AI needs: all 13 action types with parameters, defaults, effects config, output options, and usage rules.
# Clipwise YAML Schema Reference (v0.7.2)
# Scriptable cinematic screen recorder — YAML in, MP4 out
# Install: npm install -D clipwise
# Record: npx clipwise record scenario.yaml -f mp4
# ─── Top-Level Structure ──────────────────────────────
name: "Scenario Name" # required
description: "Optional" # optional
viewport:
width: 1280 # default: 1280
height: 800 # default: 800
# ─── Output ───────────────────────────────────────────
output:
format: mp4 # mp4 | gif | png-sequence
fps: 30 # 1-60 (default: 30)
preset: balanced # social | balanced | archive (default: balanced)
codec: auto # auto | h264 | hevc | av1 (default: auto)
width: 1280 # default: 1280
height: 800 # default: 800
# ─── Authentication (optional) ───────────────────────
auth:
storageState: ./auth.json # Playwright storageState file path
cookies: # or inline cookies
- name: session_id
value: abc123
domain: .example.com
path: / # default: /
httpOnly: false # default: false
secure: false # default: false
sameSite: Lax # Strict | Lax | None (default: Lax)
# ─── Audio (MP4 only) ────────────────────────────────
audio:
file: "./narration.mp3" # MP3, WAV, etc.
volume: 1.0 # 0.0 - 2.0 (default: 1.0)
fadeIn: 0 # seconds (default: 0)
fadeOut: 0 # seconds (default: 0)
# ─── Steps ────────────────────────────────────────────
steps:
- name: "Step Name" # optional
captureDelay: 200 # ms after actions (default: 300)
holdDuration: 800 # ms to hold result (default: 1500)
transition: none # none | fade | slide-left | slide-up | blur
effects: # per-step override (optional, inherits global)
zoom:
enabled: false # disable zoom for this step only
actions:
# --- Basic Actions (7) ---
- action: navigate
url: "https://example.com"
waitUntil: networkidle # load | domcontentloaded | networkidle (default)
- action: click
selector: "#btn" # CSS selector (Unicode/CJK OK)
delay: 0 # optional click delay (ms)
timeout: 15000 # optional element wait timeout (ms)
- action: type
selector: "#input"
text: "hello"
delay: 50 # per-char delay ms (default: 50)
timeout: 15000 # optional element wait timeout (ms)
- action: hover
selector: ".menu"
timeout: 15000 # optional
- action: scroll
y: 400 # vertical px (default: 0)
x: 0 # horizontal px (default: 0)
selector: ".container" # optional target element
smooth: true # default: true
timeout: 15000 # optional
- action: wait
duration: 1000 # ms
- action: screenshot
name: "result" # optional label
fullPage: false # default: false
# --- Async Wait Actions (5 + smartWait) ---
- action: waitForSelector
selector: ".result"
state: visible # visible (default) | attached | hidden
timeout: 15000 # default: 15000
captureWhileWaiting: false # true = capture frames during wait (default: false)
displaySpeed: 8 # speed multiplier for captured wait frames (1-32)
- action: waitForNavigation
waitUntil: networkidle # load | domcontentloaded | networkidle (default)
timeout: 15000 # default: 15000
captureWhileWaiting: false
displaySpeed: 8
- action: waitForURL
url: "https://example.com/dashboard"
timeout: 15000 # default: 15000
captureWhileWaiting: false
displaySpeed: 8
- action: waitForFunction
expression: "document.querySelector('.done') !== null"
polling: raf # raf (default) or ms number (e.g. 500)
timeout: 30000 # default: 30000
captureWhileWaiting: false
displaySpeed: 8
- action: waitForResponse
url: "/api/chat" # URL substring match
status: 200 # optional HTTP status filter (100-599)
timeout: 30000 # default: 30000
captureWhileWaiting: false
displaySpeed: 8
# --- Smart Wait (records + auto-compresses) ---
- action: smartWait
until: networkIdle # networkIdle | selector | domStable
selector: ".result" # required when until=selector
timeout: 30000 # default: 30000
displaySpeed: 8 # speed multiplier (1-32, default: 8)
# ─── Effects ──────────────────────────────────────────
effects:
zoom:
enabled: true # default: true
intensity: light # subtle(1.15x)|light(1.25x)|moderate(1.35x)|strong(1.5x)|dramatic(1.8x)
# scale: 1.25 # numeric override (1-5); ignored when intensity is set
duration: 800 # ms (default: 800)
easing: ease-in-out # ease-in-out | ease-in | ease-out | linear | spring
autoZoom:
followCursor: true # viewport pans to follow cursor position
transitionDuration: 300 # ms (default: 300)
padding: 200 # px (default: 200)
# Smart camera: zoom auto-suppressed during scroll actions
cursor:
enabled: true # default: true
size: 20 # px (default: 20)
color: "#000000" # default: #000000
speed: normal # fast (~72ms) | normal (~144ms) | slow (~288ms)
clickEffect: true # default: true
clickColor: "rgba(59,130,246,0.3)"
trail: true # default: true
trailLength: 6 # default: 6
highlight: true # default: true
highlightRadius: 35 # default: 35
background:
type: gradient # gradient | solid | image (default: gradient)
value: "linear-gradient(135deg, #667eea 0%, #764ba2 100%)"
padding: 48 # px (default: 48)
borderRadius: 14 # px (default: 14)
shadow: true # default: true
deviceFrame:
enabled: false # default: false
type: browser # browser | iphone | ipad | android | none
darkMode: false # default: false
keystroke:
enabled: false # default: false
showTyping: false # show typed text (default: false — shortcuts only)
position: bottom-center # bottom-center | bottom-left | bottom-right
fontSize: 16 # default: 16
fadeAfter: 1500 # ms (default: 1500)
speedRamp:
enabled: false # default: false
idleSpeed: 2.0 # default: 2.0
actionSpeed: 0.8 # default: 0.8
smartSpeed:
enabled: false # default: false
waitSpeed: 8 # speed for smartWait frames (1-32, default: 8)
idleSpeed: 4 # speed for idle frames (1-16, default: 4)
transitionDuration: 300 # ease duration between speed changes (ms)
minSegmentDuration: 500 # don't speed up segments shorter than this (ms)
watermark:
enabled: false # default: false
text: ""
position: bottom-right # top-left | top-right | bottom-left | bottom-right
opacity: 0.5 # 0-1 (default: 0.5)
# ─── Rules ────────────────────────────────────────────
# - Selectors: CSS selectors (#id, .class, [data-*], [aria-label="..."])
# - Unicode/CJK characters in selectors are supported
# - Click/type elements must be visible — scroll first if needed
# - Focus input with click before type action
# - Prefer async wait actions over fixed wait for reliability
# - waitForFunction: use for AI streaming, dynamic content, DOM changes
# - waitForResponse: set up before triggering action (auto-handled)
# - Timing: captureDelay 50-100ms, holdDuration 500-800ms for snappy demos
# - Per-step effects: override any effect per step; unset props inherit global
# - Smart camera: zoom suppressed during scroll; followCursor pans to cursor
# - Transitions: fade/blur for cinematic cuts; slide-left/up for sequences
# - Audio: only works with MP4 format; file must exist at specified path
# - Auth: use storageState for login-required pages (generate with playwright codegen)
# - captureWhileWaiting: set true on async waits to record loading animations
# - Spring zoom: easing: spring for natural Screen Studio-like camera motion
# - Codec: av1 = smallest files (slow encode); hevc = 10-bit color; auto = h264
# - type action: auto-dispatches input/change events for React/Vue compatibility