agentleFS
freemafreema/ai-skills/skills/promo-video/SKILL.md

promo-video

Skill5 starsChanged 21 days agoMITView on GitHub

Make short promotional videos as code — product spots, feature demos, launch and game trailers — with HyperFrames (HTML + GSAP rendered to MP4). Covers the brief and facts check, a 15–35 s story shape, a runnable template with one camera and a pure frame(t) driver, ElevenLabs voiceover with word timings, burned-in captions checked against Netflix rules, synthesized or engine-rendered sound effects placed from a cue log, a loudness-checked mix that survives the AAC encode, recorded browser/canvas gameplay, recreated product UI, review frames, and web/social delivery. Use when the user asks for a promo, spot, teaser, trailer, demo or explainer video, or a social video of a product or game.

  • Reads credentials

19 other projects use a version of this.

Adopted by

Most recently taken up by

The file

We split this skill into the rules it gives an agent

12 rules. 0 are common, 5 are distinctive, and 7 are very specific to this project.

  • 0

    Common

    Most of the 16 edited versions we read keep these.

  • 5

    Distinctive

    This author's own choices. Worth a look.

  • 7

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    Brief and facts: Before writing a word of script, check every product name, number and claim against the product's own sources (site, docs, …

    line 57
  2. Very specific:

    Every beat has its own idea: No two beats in a row with the same layout and the same move.

    line 97
  3. Distinctive:

    Picture follows the voice: Every visual change lands on the word that names it, or on the beat in a music-led piece.

    line 101
  4. Very specific:

    Home: every project is $PROMO_VIDEO_HOME/<slug>/ (default ~/promo-videos/<slug>/), one per video.

    line 129
  5. Very specific:

    A local render is not a publication: Never run hyperframes publish, upload, or post without the user's explicit go-ahead for that …

    line 147
  6. Very specific:

    Do not widen either to make a page load.

    line 153
  7. Distinctive:

    No invented facts: No player counts, revenue, ratings, testimonials, endorsements or competitor claims you cannot source.

    line 155
  8. Distinctive:

    Other brands: When a scene borrows another product's look (a chat, an editor), take tokens and layout from real screenshots and source, but …

    line 158
  9. Very specific:

    Every font family needs an @font-face with a vendored file, or lint fails with font_family_without_font_face.

    line 169
  10. Very specific:

    Every <video> needs an id, or the renderer freezes it (media_missing_id).

    line 171
  11. Distinctive:

    Do not measure text during the build: fonts may still be settling.

    line 176
  12. Very specific:

    Do not silence check with data-layout-ignore on content layers: with everything ignored it reports "Timeline did not advance" and checks …

    line 182

“Common” is measured: kept by at least 6 in 10 of the 16 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Promo video as code

A video is a web page that HyperFrames renders frame by frame: an index.html composition, one paused GSAP timeline, and a soundtrack built from stems. Everything is a pure function of time, so a render is reproducible and a one-word change is a re-render, not a re-edit.

The method comes from four finished videos: two 30–33 s product spots with voiceover, a 36 s how-to for one module of a product, and a 30 s music-led game trailer with recorded gameplay. Pick the shape that fits:

ShapeCarried byMix targets (integrated)
Voiceover spot: one story told by a voice, product UI doing the work, captionsvoicevoice −14 LUFS, music bed −36, SFX −26, mix −12
Music-led trailer: supers on a beat grid, footage, no voicemusicmusic −16, SFX −22, mix −14

Both: true peak ≤ −1 dBTP after a test AAC encode, 1920×1080, 60 fps, an end card held about 2 s.

What ships with the skill

PathDoes
scripts/new-project.sh <slug>creates ~/promo-videos/<slug> (or $PROMO_VIDEO_HOME) from the template and scripts, vendors GSAP 3.14.2 and Inter, starts README.md and facts.md; refuses inside a git repository
assets/index.html, assets/scene.jsthe template: an 8 s working composition, hook → product does one task (typing, cursor, click, result) → end card, with one camera, frame(t), a cue log and a caption layer
assets/script.jsonexample voiceover script (say, group, chunks, gap_after, burn)
scripts/probe.mjsserves the project, reports errors, --cues exports the SFX cue log, --shots saves review frames (videos seeked)
scripts/tts.mjsElevenLabs take with character timestamps; --line re-takes one line; --dry-run
scripts/voice-test.mjsthe same script lines read by 3–4 candidate voices, for picking by ear; prints the credits first
scripts/build_vo.pysplits the take into lines, re-spaces them, masters the voice, writes word timings
scripts/build_captions.pycaption chunks + .srt/.vtt; fails the build on Netflix rule breaks
scripts/sfx.py17 deterministic synthesized UI sounds (pop, click, tick, whoosh, ding, …)
scripts/build_mix.pyvoice + back-timed music + SFX from cues → audio/mix.wav; fails if the AAC true peak is above −1 dBTP
scripts/record-canvas.mjsrecords a live canvas + its WebAudio from a scripted input plan
scripts/cut_clips.pytrims, fixes the frame rate, crops and scales recordings into composition clips
scripts/deliver.shversioned web encodes (H.264 MP4, VP9 WebM, poster)
scripts/audiolib.py, scripts/browser.mjsshared helpers (ffmpeg/numpy audio, headless Chrome lookup)

Requirements: Node 22+, Python 3 with numpy, ffmpeg with libx264, libvpx and libopus. HyperFrames runs through npx; the browser scripts reuse the puppeteer-core it installs and its chrome-headless-shell (npx -y hyperframes@0.8.111 browser ensure downloads one). Tested with HyperFrames 0.8.111, GSAP 3.14.2, ffmpeg 8.1, Python 3.9, numpy 2.0.

Workflow

  1. Brief and facts. Before writing a word of script, check every product name, number and claim against the product's own sources (site, docs, code) and keep a facts file next to the project: claim → source, a made up on screen list (example data, names, compressed timing), and a don't claim list. When sources disagree, leave the number out or round it to something that stays true ("40+ games"). Open every URL of the product that will appear on screen with curl -L; never open a third party's (competitors, shops, links in example data). Made-up URLs and e-mails use example.com or a .test domain only, never a real-looking one. See references/story.md.
  2. Ask only the decisions that are the user's: where the video will live (homepage with sound, feed autoplay, ad), voice or music-led, which claims to make, permission to use generated or third-party assets, any real person's name on screen. Decide the rest yourself and write the decisions down. For a client, pitch 3–4 concepts and get one picked before any script exists (references/story.md, "Concepts before production").
  3. Script as a beat table: time, picture, super/caption, sound. Hook in the first 3 s on the product's own surface, brand by 5 s, one task end to end, one CTA. Keep it working with the sound off. Get it approved before anything paid runs (a voice take, a voice test, licensed music): show the spoken lines as they will be read and the cost (tts.mjs --dry-run and voice-test.mjs --dry-run print characters and credits). Revise until the user says go; every rewrite after the take costs a re-take.
  4. Start the project with bash <skill>/scripts/new-project.sh <slug> and run the build loop once on the untouched template, so a broken toolchain shows up before your content does. Keep to the layout and names in "Projects and files" below.
  5. Assets: record footage (references/footage.md), take clean screenshots, rebuild UI from the product's CSS and real data, vendor fonts and the official logo. Draw illustrations yourself.
  6. Voiceover (voice spots): offer 3–4 candidate voices and let the user pick from voice-test.mjs samples of the real script, then tts.mjs, build_vo.py, build_captions.py. The printed total becomes data-duration. Key every visual beat to word times (references/audio.md).
  7. Compose: replace the template's beats in the project's index.html and scene.js, following references/composition.md. Log a cue for every sound with cue(t, type, {gain}).
  8. Sound: probe.mjs --cues, then build_mix.py. Report the measured loudness, not the targets.
  9. Review: frames every 0.5–1 s with probe.mjs --shots, then lint and check, then the render, then frames extracted from the final MP4. Look at them; check samples and can miss a shot. Hold the frames against "What a good result looks like" below and fix what fails before showing the user.
  10. Deliver and document: render, verify with ffprobe and ebur128, encode for the destination (references/delivery.md), and write the project README: sources and licenses, what is made up, measured loudness, what it cost, posting notes, rebuild steps.

What a good result looks like

The tools above make a video correct. These make it worth watching; check every review pass against them.

  • The real thing, not a symbol for it. The product's own screen, output, map or document rather than an icon that stands for it. When the point is a sentence on a page, show that page and mark the sentence, so the viewer sees it really says so.
  • Every beat has its own idea. No two beats in a row with the same layout and the same move. One main transition style, one accent at the climax (references/composition.md, "Motion").
  • Nothing stands still. Something moves in every shot: a slow camera drift, the product doing its job, a counter, a cursor. A frame that holds for more than about 1.5 s outside the end card reads as a slide.
  • Picture follows the voice. Every visual change lands on the word that names it, or on the beat in a music-led piece. A change that comes half a second late reads as a mistake.
  • Lines written to be heard. Whole sentences with verbs, one thought per line, numbers written as they are said. A keyword staccato ("Fast. Simple. Secure.") sounds read off a slide.
  • It works with the sound off. Captions and supers carry the story; the video still makes sense muted.

Build loop

export HYPERFRAMES_NO_TELEMETRY=1 HYPERFRAMES_SKIP_SKILLS=1   # no telemetry, no skills update check
HF="npx -y hyperframes@0.8.111"                                # pin the version you tested with

node scripts/tts.mjs && python3 scripts/build_vo.py && python3 scripts/build_captions.py   # voice spots only
node scripts/probe.mjs --cues audio/cues.json        # SFX cue log from the scene
python3 scripts/build_mix.py                         # → audio/mix.wav, audio/loudness.json
node scripts/probe.mjs --shots every:0.5 --out review/frames
$HF lint .                                           # 0 errors
$HF check --samples 24 --timeout 15000 --caption-zone "x0=0;y0=.86;x1=1;y1=1" .
$HF render --quality delivery --fps 60 --output renders/<slug>-v1.mp4 .   # add --video-frame-format png for pixel art / UI footage

Rerun from the step you changed: a scene edit that moves a cue needs --cues and build_mix.py again; a script edit needs the whole voice chain. The render embeds audio/mix.wav as it is on disk.

Projects and files

Details and the migration steps are in references/projects.md.

  • Home: every project is $PROMO_VIDEO_HOME/<slug>/ (default ~/promo-videos/<slug>/), one per video. All of its languages and formats stay in that one project.
  • Never committed: projects live outside every git repository, media and composition code alike. Reusable code goes into this skill, not into another project or a product repository.
  • Fixed layout: README.md, facts.md, script.md, index.html, assets/, audio/, scripts/, optional research/, capture/ and concepts/, then review/ (scratch), renders/ (deliverables) and archive/ (rejected versions). No other top-level directories.
  • Render names: renders/<slug>[-<lang>][-<variant>]-v<N>.mp4, sidecar captions with the same stem. A file that has been sent or posted is never overwritten; the next cut is the next version.
  • Publication records stay with the system that publishes, pinned to the render's SHA-256. The project README lists each live post's date, platform and URL.

Rules that are not negotiable

  • Keys stay in .env. tts.mjs reads ELEVENLABS_API_KEY from the environment or a .env file and never prints it. Never put a key in a prompt, a script, a log or a README. Do not read .env*, credentials or keys in the product's repository while researching it.
  • Nothing from a video project is committed, and no project is started inside a repository.
  • A local render is not a publication. Never run hyperframes publish, upload, or post without the user's explicit go-ahead for that destination. Record a platform, date and URL only after the post is live and you have opened it.
  • Network stays narrow. The skill reaches jsDelivr (GSAP and Inter, checked against SHA-256 in new-project.sh), the ElevenLabs API, and the product's own domains. probe.mjs blocks every request that is not the local composition; record-canvas.mjs only loads the plan URL's host plus the hosts listed in its allow. Do not widen either to make a page load; add a host to allow only after the user confirms it belongs to the product. Made-up addresses use example.com or .test (references/story.md).
  • No invented facts. No player counts, revenue, ratings, testimonials, endorsements or competitor claims you cannot source. Example data is labelled as made up in the README. Do not put words in a third party's mouth (an AI assistant's answer, a customer quote); show placeholders instead.
  • Other brands. When a scene borrows another product's look (a chat, an editor), take tokens and layout from real screenshots and source, but leave out its logo, name and UI chrome unless the user has the rights. Keep legal disclaimers out of the picture unless asked; put them in the README and the post text.
  • Determinism. No Math.random, no clocks, no infinite repeats, no network at render time. Seed every RNG.
  • Accessibility. At most 3 flashes in any one second (WCAG 2.3.1), no saturated red flashes; captions or supers carry the story for viewers without sound.

Gotchas that cost the most time

  • Build the timeline inside document.fonts.ready.then(build). A synchronous build, or one gated on document.fonts.load(), rendered blank frames.
  • Every font family needs an @font-face with a vendored file, or lint fails with font_family_without_font_face. Subset fonts miss glyphs (Google's latin subset has no → or ✓): draw those as SVG.
  • Every <video> needs an id, or the renderer freezes it (media_missing_id). Write videos as static markup: ones created from JS may not be discovered. HyperFrames controls the opacity of active clips, so animate a wrapper <div>. Overlapping clips need different data-track-index values.
  • Hard-code the composition id and duration in scene.js. Reading them from the root at script time threw under check.
  • Do not measure text during the build: fonts may still be settling. Give elements explicit absolute layout.
  • Pass immediateRender: false to every fromTo after the first one on the same element and property. Otherwise its from values are applied when the timeline is built and show before the tween starts.
  • HyperFrames turns the whole soundtrack down when the true peak after its AAC encode is above −1 dBTP. One sibilant cost a render 4.4 dB. A 16 kHz low-pass before the limiter fixes it; build_mix.py does that and checks.
  • render --resolution only supersamples. A 9:16 or 4:5 version is its own composition with its own framing.
  • Do not silence check with data-layout-ignore on content layers: with everything ignored it reports "Timeline did not advance" and checks nothing. Use data-layout-allow-overflow and data-layout-allow-occlusion where an overlap is intended.
  • snapshot sends frames to Gemini when GEMINI_API_KEY is set; pass --describe false unless the user wants that.

References

  • references/story.md: facts sheet, concepts for client work, story shapes for voice spots and music-led trailers, beat grids, captions rules, accessibility, other aspect ratios.
  • references/composition.md: HyperFrames structure, the timeline and frame(t), one camera, motion and easing, cursor, video clips, fonts, SVG and text gotchas, checks.
  • references/audio.md: ElevenLabs voiceover and re-takes, music licensing and back-timing, synthesized SFX, rendering a game's own audio offline, the mix and the AAC guard.
  • references/footage.md: recording canvas gameplay with input plans, cutting clips, clean page screenshots, rebuilding a product's UI from its CSS and real states.
  • references/projects.md: where projects live, the directory layout, render names and versions, publication records, finishing a project, moving an old project into the layout.
  • references/delivery.md: the review loop, render settings, platform notes (X, YouTube, LinkedIn, DEV.to, self-hosted website), the project README and publication records.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.