launch-kit: launch and update videos for your own product
这一页还没有中文版,下面是英文原文。
Use when a maker wants to announce a product or a new version: a short demo video of the real product, a
looping GIF for the README, one short clip per feature for X / LinkedIn / Shorts / TikTok / 小红书, Product Hunt
gallery stills and an OG image, post copy per platform in English and 中文, and a posting schedule. It is not for
a talking-head promo of something you made (that is promo-recut) or a concept explainer (explainer).
Inputs → Outputs. A product (name, one-liner, site), release notes (a CHANGELOG section, a git range of
Conventional Commits, or pasted notes), the product itself to record (a URL or an Electron app, plus a shot list),
or screenshots / recordings you already have, and the brand (logo, icon, colours, fonts) → kit/:
| Folder | What |
|---|---|
demo/ |
demo-<lang>-16x9 / 9x16 / 1x1.mp4, 30–60 s: title card, one scene per feature (kinetic caption + the product window, the camera punching in on each click and typed field), logo end card, a soft music bed; a .cover.jpg per video |
readme/ |
loop-<lang>.mp4 + .gif, 15 s, seamless (the last frame crossfades into the first) |
clips/ |
<feature>-<lang>-1x1 / 9x16.mp4, 10–20 s each, one feature + end card |
stills/ |
ph-gallery-1-hero.png, ph-gallery-N-<feature>.png (1270×760), og-1200x630.png |
copy/ |
COPY.md (Product Hunt fields, the launch post and every clip post, international platforms first), <platform>.<lang>.md |
schedule/ |
SCHEDULE.md + schedule.json: launch day = the demo everywhere, then one feature clip per weekday |
firstpass.json |
vstudio.firstpass on every video (+ <video>.firstpass.md) |
Everything content-specific lives in ONE file, launch.config.yaml; start from
$VSTUDIO/workflows/launch-kit/examples/launch.config.example.yaml, which documents every key.
Pipeline ($VSTUDIO = repo root; LK="python3 $VSTUDIO/workflows/launch-kit/scripts/launch_kit.py")
Section titled “Pipeline ($VSTUDIO = repo root; LK="python3 $VSTUDIO/workflows/launch-kit/scripts/launch_kit.py")”- Draft the features from the release.
Terminal window $LK features launch.config.yaml # release.changelog | release.git {repo, range} | release.notesvstudio.launch.featureskeeps feat / fix / perf / breaking commits (chores, ci, docs, tests, refactors never become features) or the bullets of a Keep-a-Changelog section (“Mac app:” style intro bullets contribute their nested bullets). Pick 3–6. For each, write ONE on-screen line (≤ ~50 characters in 16:9; the caption is fitted and wrapped by measuring the real font, and the build fails on a line that cannot fit) and mark the word to highlight with 【】. Captions say what the viewer gets, not what the code does. - Record the product. Write
shots.yaml(one shot per feature: the actions to perform, 5–12 s each) and runTerminal window $LK capture launch.config.yaml # = node $VSTUDIO/workflows/launch-kit/scripts/capture.mjs --shots shots.yaml --out capture ...capture.mjsdrives the product with Playwright (a web URL in Chrome, or an Electron app with a throwaway profile), at 2× device scale, records the page with CDP screencast frames assembled at a constant 30 fps (not Playwright’s low-bitrate recorder), draws a smooth cursor with a click ripple, and types at a human pace. It writescapture/<shot>.mp4, a still per shot, andcapture/shots.jsonwith the focus boxes: where and when each click / typed field happened. An Electron app gets its throwaway profile throughcapture.env/capture.env_json(--env-json);examples/reelfold/is a full example (Reelfold recording itself:seed.pybuilds a temp profile with real engine data on a synthetic talk,shots.yamldrives Home → plan → project → transcript → chat → publish). The camera uses them for the punch-ins. Seed the product with sample data that is safe to show (no real names, emails, handles or other people). Already have screenshots? List them undercapture.imageswith a focus box; the camera then makes a slow move to it. - Build + render.
One HyperFrames project per cut × language × canvas (
Terminal window $LK build launch.config.yaml && (cd kit/build/demo-en-16x9 && npx hyperframes lint)$LK render launch.config.yaml [--only demo|loop|clip|<feature>] [--quality draft]kit/build/<name>/index.html,plan.json,cues.json). Layout per canvas keeps every caption inside the safe area of every platform that canvas is posted to (vstudio.platformsafe boxes: 16:9 X / LinkedIn / YouTube / B站, 9:16 TikTok / Shorts / Reels / 小红书 / 抖音, 1:1). The look is the brand on top of avstudio.themepreset (editorial by default: paper, ink, one accent, a marker sweep under the highlighted word); fonts are the brand’s Latin fonts, Chinese uses Noto Serif / Sans SC subset to the characters on screen. Delivery: BT.709 tags + faststart; loudness to −14 LUFS only when there is audio. Render one draft first and look at frames mid-scene and mid-transition before the full set. - Stills, copy, schedule, check.
Copy comes from the config’s
Terminal window $LK stills launch.config.yaml # PH gallery + OG from the shot stills, rendered at 2x then resampled$LK copy launch.config.yaml # vstudio.publish.platform_post per platform x language -> copy/COPY.md$LK schedule launch.config.yaml [--apply]$LK check launch.config.yaml # firstpass, format "launch"postblock (andfeatures[].post), laid out per platform by the shared post engine (title fields, hashtag caps, X weighted 280, 小红书 / B站 conventions) and checked withvstudio.platform.check_text; without apostblock the routed model writes it (llm taskcopy), and with no model routed the step fails instead of inventing text.--applyadds the schedule to the publish calendar (vstudio.project.pubcal) as planned posts for the accounts inschedule.accounts. Nothing is ever posted.$LK all launch.config.yamlruns steps 3–4.
Defaults and rules
Section titled “Defaults and rules”- No voice by default; a license-safe music bed.
music: auto(default) takes a built-in bed from the shared library (vstudio.music: generated in code, free for any use; moodtechfor a product video). A mood name picks another built-in bed,noneturns it off, and your own track needsmusic.file+music.license. A TTS voice-over (voiceover.enabled, onevoline per feature) is labelled “AI voice” on screen; say so in the post too. With audio the delivery is loudness-normalised to −14 LUFS. - Real product, real data you may show. Numbers in the gallery
factsmust be ones you can stand behind; sample data getsgallery.note(“Sample data”). - International platforms first, Chinese after, everywhere platforms are listed (copy, schedule, captions).
- Red is not a brand accent here.
brand.warningsflags a reddish accent and low ink/paper contrast. - Demo length: scenes are 4.5–9 s; the total is squeezed into
demo.max_seconds(never under 4 s per scene) or padded by holding each shot’s last frame up todemo.min_seconds.
Speed ramps (what timing does)
Section titled “Speed ramps (what timing does)”Real recordings wait: a plan being made, a render finishing. ffmpeg freezedetect (a very low noise threshold, so
typing still counts as motion) finds the frozen stretches of each shot; they play at 4× after a 0.35 s lead-in at
normal speed, the actions play at the feature’s rate. When the scene is still shorter than the ramped take, every
segment speeds up evenly. Each segment is its own back-to-back clip in the composition. Detection is cached next to
the shot (<shot>.mp4.idle.json); ramp: false on a feature turns it off.
Camera (what story.camera does)
Section titled “Camera (what story.camera does)”The shot is cover-fitted into the product window. Each focus event becomes a punch-in (zoom ≤ the point where the acted-on element fills ~60 % of the window, ≤ 2.6×) that starts 0.55 s before the action, holds ≥ 1.5 s, and pulls back when the next action is far off. On a narrow window (9:16) the camera follows the action instead of showing the whole screen. Events closer than 0.6 s share one move. Translations are clamped so the shot always covers the window.
Hard-won lessons
Section titled “Hard-won lessons”- Record at 2× and extract video frames as PNG in the render (
--video-frame-format png): UI text turns to mush with JPEG frame extraction or Playwright’s default recorder. - HyperFrames: never put a CSS
transformon an element GSAP moves (gsap_css_transform_conflict); the camera’s first pose is atl.setat 0. Overlay start states areopacity: 0in CSS. - A loop needs its last frame to be its first: the last scene crossfades into a still of the first scene’s opening frame, and the first scene has no entrance animation.
- Fit captions by measuring the actual font (PIL on the woff2), not by character counts: Newsreader and Noto Serif SC differ by 40 % in width at the same size.