Files
hyperframes/skills/music-to-video/SKILL.md
T
WaterrrForever b9be0b2625 feat(skills,studio,media-use): the intent layer, review loop, and user memory — BRIEF.md, companion mode, recipes; /website-to-video folds into /product-launch-video (#2133)
* feat(studio,cli): per-frame board comments, self-refreshing storyboard, status-aware preview landing

Per-frame comment boxes on the storyboard board batch into
.hyperframes/frame-comments.json (a resubmit wins per frame; unconsumed
comments on other frames are kept). Submitted-but-unconsumed comments
stay visible — a toolbar banner plus a per-tile echo — until the agent
consumes the file; the banner also says what to do next (reply anything
in the agent chat).

The board keeps itself current: GET /projects/:id/signature exposes the
watcher-cached project signature, the storyboard payload carries the
signature it was derived from, and the view polls at 2s (hidden tabs
skipped, re-checked on visibility), refetching in place with no loading
flash. Posters bake the signature into their URL so tiles fill in as
sketches land and a poster that failed mid-write retries on the next
version; the empty state upgrades itself when STORYBOARD.md appears,
and its handoff prompt now points the agent at the review loop and uses
the parser's real status vocabulary (outline, not planned).

preview lands the browser on the storyboard view while the board is the
review surface — any frame built, or pure planning (srcs declared, none
on disk yet) — and on the timeline once the video is assembled.

* feat(skills): the review loop — plan, sketch, build as one shared process

hyperframes-core/references/review-loop.md is the single source for the
three-pass collaborative review: the plan proposed on a live board
(§ 1), wireframe sketches marked built with one layout question (§ 2 —
real words on plain blocks, run no CLI; a confirmed board is itself a
valid deliverable when the user asked for a storyboard, not a video),
the build dressing confirmed layouts (§ 3, worker or inline), and the
final look (§ 4). Autonomous runs skip every gate and keep one question
before render.

The three narrative workflows' Steps 3/4/6 collapse to references plus
their sketch stand-ins (captured-asset blocks for product-launch-video,
plain code panels for pr-to-video); the confirmed-sketch handoff stays
in each frame-worker prompt. general-video plans on a board for
multi-scene narrative pieces in collaborative mode — its sketch pass is
layout-before-animation with the user watching. The router treats
"I want a storyboard" as a process request rather than a route, and
closes exploratory intake by recommending a route plus how the run will
review.

The supporting contracts land next door: the comments channel (silent
submit, one reply picks it up, check the file before the words) in
brief-contract § 1; the sidecar schema and the built status rung in
storyboard-format; the mode question asked first and alone in the three
workflows' Step 0.

* feat(media-use): user memory — remembered preferences and frozen recipes

Two tiers of memory on media-use's existing two-tier storage split.

Preferences (lightweight): confirmed brief answers — destination, aspect,
language, mode, voice, style preset — recorded to the project's
.media/preferences.json (committed, the team inherits it) and promoted
to the personal ~/.media/preferences.json once the same value is
confirmed in two different projects (a sightings ledger accumulates the
cross-project evidence user-side, since project files can't see each
other). prefs.mjs get/record; merge reads project-over-user; a changed
value restarts its provenance.

Recipes (heavyweight): one approved run frozen as a named, versioned
bundle — frame.md, the storyboard skeleton (structure kept: durations,
transitions, srcs, Video direction; statuses reset to outline; content
blanked to per-frame fill-ins naming the beat's role), and the confirmed
brief values. Named folders, not content hashes: re-freezing bumps
version and archives <name>@v<N>; a freeze is already confirmed, so it
promotes to the user tier immediately. recipe.mjs freeze/list/use, plus
resolve --type recipe --entity <name> delegating like grade/lut.

16 new node --test cases; the media-use lib suite is 168/168.

* feat(skills): wire user memory into the brief and the review loop

brief-contract § 2 gains Remembered defaults: read the merged
preferences before Round 2 and let a remembered value become the
recommended option with a receipt naming its source project. Memory
changes the default, never the question — every ask-marked field still
gets asked, and what the request says this time beats what was picked
last time. Record only what the user actually confirmed (a defaulted
voice nobody chose is not an answer; a "go" that accepts the
recommended defaults is). The first record announces itself once;
after that the receipts carry the reminder. In autonomous mode a
remembered value becomes the decided value, receipt included.

The three narrative workflows read the remembered defaults before
Round 2, record the confirmed answers at the Step 0 gate, record the
chosen preset at the Step 2 gate (pr-to-video excepted — its preset is
fixed), and fall back to the remembered voice when the request names
none. general-video's discovery reads the same defaults.

Recipes wire in at both ends: Step 0 checks for a matching recipe
before the mode question — one question, plural-aware, and adopting
one fills the brief, skips the design step, and drafts the storyboard
from the frozen skeleton while every review gate still runs. The
review loop's final look (§ 4) offers the freeze once after approval,
and the confirmation teaches the recall phrase — the name is something
the system reminds the user of, never something they must remember.
The router recognizes a named recipe or "like last time" as a route.

* docs(skills): the sketch pass names check, not the deprecated validate

* feat(skills): intent-layer references — process, route briefs, capability menu, BRIEF.md format

* feat(media-use): brief skeleton as the recipe's fourth artifact; flow/storyboard preference keys

* feat(skills): the intent layer conducts every brief — workflows execute BRIEF.md

* feat(skills): retire the mode preference key; sync catalog surfaces for intent layer

* refactor(skills): dedupe router vs intent-layer guidance — one owner per rule

* feat(skills): the design ask — own spec, pick by eye from showcases, or defer

* docs(skills): the design ask says the honest line on capture routes

* feat(skills): product-launch-video absorbs website-to-video as the tour angle

* refactor(skills): keep product-launch-video pristine — a tour is brief intent, not a pipeline branch

* feat(skills): production loop + genre lenses; general-video goes freeform (route yours, laws hold)

* refactor(skills): /hyperframes is the front door - route tables and scope lists leave the workflows

* docs(skills): review-loop pass across skill catalog

* fix(cli): pass project dir to openStudioBrowser in background-server path

* feat(skills): add pitch-round reference - verbalized sampling concept gate

* feat(skills): wire pitch round into intent layer - completeness triage + route eligibility

* feat(skills): editorial capability recommendations, handoff disciplines, menu-probe split

* feat(skills): pitches carry their machinery; source-only-formed requests pitch the telling

* feat(skills): companion goes director - ceiling treatment plus blueprint/rule citation discipline

* fix(scripts): sandbox npx-leak guard - private npm global prefix keeps npx on the branch CLI

* chore(skills): resync manifest hash after formatter pass reflowed general-video tables

* fix(skills): recipe freeze reads workflow from BRIEF.md; style_preset records require workflow scope

Two holes found by a live companion-run freeze: the agent-supplied --workflow
contradicted the run's actual workflow (recipe.json said faceless-explainer,
brief-skeleton said general-video), and the style_preset lookup missed because
the preference had been recorded under the bare key.

- freezeRecipe resolves the workflow from BRIEF.md frontmatter; the flag is a
  fallback for briefless projects and a contradicting flag is ignored (noted).
- recordPreference refuses a bare style_preset — the scoped key is the only
  writable shape; freeze tolerates legacy bare records via read fallback.
- review-loop § 4 / media-use SKILL / brief-format wording follow the machinery.
2026-07-15 21:19:14 +08:00

17 KiB
Raw Blame History

name, description
name description
music-to-video Turn a music track (an audio file, a video to pull audio from, or a track generated from a mood brief) into a beat-synced video — lyric video, slideshow, or kinetic promo. The music drives all pacing; any user-supplied images/videos are cut onto the same beat grid, and a complete video needs zero assets. Narrated pieces → the input-matched workflow (see /hyperframes). Unclear → /hyperframes.

First, keep this skill fresh — run silently, don't ask: npx hyperframes skills update music-to-video. A fast no-op when everything is current; otherwise it refreshes this skill plus the core domain skills it depends on before you rely on them.

music-to-video — one music-grounded, beat-synced video workflow

Use this skill to turn a music track into a beat-synced HyperFrames video. You analyze the track once, lay out the frames, fill in a per-frame plan, and build each frame as a composition. The input is a music track plus optional user images or videos — there is no narration and no website capture. Typography and templates are the floor (a complete video needs zero assets); any media the user supplies is cut in on the same beat grid.

You are the orchestrator. Work in videos/<project>/. Run the steps in order and pass each Gate before moving on. Two steps need the user: Step 3 (plan approval) and Step 6 (render approval) — both are checkpoint gates per ../hyperframes-core/references/brief-contract.md (read it before Step 0): in autonomous mode, post the summary as a heads-up and proceed instead of waiting. Do every step yourself except Step 4, where you dispatch one sub-agent per frame. Keep design and motion rules out of this file — they live in references/ and the frame-worker sub-agent.

SKILL_DIR = this skill directory. PROJECT_DIR = videos/<project-name>/.

Workflow: Step 0 setup → hyperframes.json + assets/bgm.mp3; Step 1 analyze → audiomap.json; Step 2 skeleton → STORYBOARD.md (frames, groups TBD); Step 3 plan → complete STORYBOARD.md + frame.md; Step 4 build → compositions/frames/NN-*.html; Step 5 assemble → index.html; Step 6 render → renders/video.mp4.

Two ideas that shape everything

  • One analyzer, and you trust it. analyze-beatgrid.py is the only beat analyzer — never re-measure beats with another tool or by ear. Its energy / density / rolls / onsets / silences are always reliable. Its bpm and beats_sec are reliable only when the music is genuinely rhythmic; on calm music the grid is a metronome the tracker imposed, so pace by phrases and energy instead and never hard-cut to it. Deciding which case you're in is each frame's pacing (Step 2).
  • One frame = one file; groups live inside. Step 2 cuts the track into frames, and each frame becomes one composition file compositions/frames/NN-<frame_id>.html, built by one frame-worker. A frame can subdivide into groups (each a template or a motion-primitives combo). Extra density goes inside a group, so frame count tracks distinct treatments, not beats — a fast track does not blow up the number of sub-agents.

Step 0: Setup, BGM, and inputs

Goal: Establish the music source, create the HyperFrames project, and note any user-supplied media.

The brief starts at the intent layer. Opening rule, in order: (1) BRIEF.md exists → read it and ask nothing it answers — its flow/storyboard derive the mode (brief contract § 1). (2) No BRIEF.md but the project exists → resume from what's on disk; never re-interrogate. (3) A fresh creation request that arrived here directly → read /hyperframes and run its intent layer (§ 4): it confirms this route's must-haves (the music source, destination → aspect — route-briefs.md § /music-to-video) and announces what stays deferred — brand and genre are chosen at Step 3 by design. Write BRIEF.md immediately after init (never before — init refuses a non-empty directory) and record the preference-backed answers (brief-format.md). Edit requests skip all of this.

The music is the spine — establish one track before anything else. This skill is tuned for fast, high-energy BGM: a strong beat grid drives the cuts (calm tracks work, but pace by phrase rather than beat). If the user supplied audio — a music file, or a video to pull audio from — use it. Otherwise choose the mood from the request and generate a track through /media-use (references/bgm.md). Before the first authenticated provider action, run npx hyperframes auth status and relay its output verbatim. If signed out, apply one branch:

  • Collaborative: wait for sign-in or an explicit choice to continue offline with the local provider.
  • Autonomous: state the status and continue through the available local provider.

If no offline provider can satisfy the required music capability, surface the blocker. Never write keys into a per-repo .env. Auth ownership and offline fallbacks live in /media-use § Providers. The resulting track lands at assets/bgm.mp3. Stage supplied images or videos so frames can use them on the beat grid; otherwise typography carries the video.

Lyric videos: for lyrics synced to the vocals, get word/line timing by transcribing the track via /media-use, or ask the user for the lyrics text and place lines on the beat grid.

Initialize only if hyperframes.json is missing. Name <project> from the brief in kebab-case, such as midnight-drive-loop — never a timestamp. init checks the installed skills against the latest on GitHub and updates the global set if any are out of date.

npx hyperframes init "videos/<project>" --non-interactive --example=blank
mkdir -p "$PROJECT_DIR/assets" "$PROJECT_DIR/renders"
cp "<user-music>" "$PROJECT_DIR/assets/bgm.mp3"   # extract from a video first if needed
# only if the user gave you images/videos:
node <SKILL_DIR>/scripts/stage-assets.mjs --from <dir> --hyperframes "$PROJECT_DIR" --into public

The brand (font + palette) is chosen at Step 3, not here. Don't pick a genre or a track type up front — assets are just an optional ingredient, and the genre emerges from the per-frame choices.

Gate: hyperframes.json + assets/bgm.mp3 exist; aspect / length / fps and (if any) the asset inventory are noted.


Step 1: Analyze the music

Goal: Produce the one canonical timing analysis the whole video is built on.

analyze-beatgrid.py is the only beat analyzer — never re-measure beats with another tool or by ear. It reads the track once and writes audiomap.json: energy phases (level / density / feel), onsets + onset_rate, rolls, silences, hard_stops, key_moments, phrases, tempo / grid, and audio.duration_sec. It's deterministic — the same file always gives the same map. Most fields are reliable on any music; bpm and beats_sec are reliable only when the music is genuinely rhythmic, and judging that is the call you make at Step 2.

Prerequisites: Python 3 with librosa, numpy, and soundfile available. If import fails, install them into the active Python environment before running the analyzer:

python3 -m pip install librosa numpy soundfile
python3 <SKILL_DIR>/scripts/analyze-beatgrid.py "$PROJECT_DIR/assets/bgm.mp3" \
  -o "$PROJECT_DIR/audiomap.json" --print

Gate: audiomap.json exists; audio.duration_sec is known.


Step 2: Frame skeleton (structure only)

Goal: Read the music and lay out the frames — the skeleton of STORYBOARD.md.

Read references/frame-skeleton.md. Turn audiomap.json into the skeleton of STORYBOARD.md yourself — there is no intermediate JSON. Cut the track into frames at real musical changes (hard_stops, SURGE / DROP key_moments, the edges of a roll, a stretch with no onsets, a big energy jump), snapping every boundary to an audiomap anchor. For each frame set span_sec, pacing (the verdict from Step 1's trust call — beat_cut when the grid is real, phrase_flow when it's a metronome imposed on calm music), mood, and a one-line feel (the plain music situation Step 3 matches a template against). Only classify and lay out here: leave every frame's ### Groups as TBD (Step 3) and the frontmatter style blank — no templates, copy, color, or fonts. Expect ~16 frames.

Gate: frames tile the track (first at 0, last at duration_s); each carries span_sec + pacing + mood + feel; every ### Groups is TBD; no content anywhere.


Step 3: Fill the plan (user-gated)

Goal: Turn the skeleton into an approved, complete STORYBOARD.md.

Read references/planning.md, storyboard-format.md, template-catalog.md, motion-primitive-catalog.md, and montage.md (only if the user supplied assets). Editing the same file in place, do two things:

  1. Pick the brand. Choose one preset from ../hyperframes-creative/frame-presets/ using the table in ../hyperframes-creative/references/design-spec.md (match the track's mood; only its fonts and colors matter — templates own composition). Copy it into frame.md unmodified and fill the frontmatter style (font + a ≤46 swatch palette) from it.
  2. Fill every frame. Decide its groups and give each a treatment: a matched template from the catalog (with bound params and real audiomap anchors), a free-compose from the primitive catalog, or an asset treatment that obeys pacing. Write the copy. You own WHAT (template / primitives + content + anchors); the frame-worker owns HOW — never write millisecond tweens into the storyboard.
node <SKILL_DIR>/scripts/validate-plan.mjs --storyboard "$PROJECT_DIR/STORYBOARD.md" \
  --audiomap "$PROJECT_DIR/audiomap.json" --templates <SKILL_DIR>/references/templates

Fix every (hard errors: duration mismatch, frames not tiling the track, a missing src); warnings are best-effort. Then show the user a frame-by-frame summary and iterate until they approve. In autonomous mode this is a checkpoint gate: post the summary as a heads-up and proceed (the validate-plan.mjs pass is a quality gate and still blocks).

Gate: frame.md is a verbatim preset copy; validate-plan.mjs exits 0; the user approved the plan (autonomous: the summary was posted as a heads-up).


Step 4: Build frames from the plan

Goal: Build every frame as a self-contained composition file.

Create compositions/frames/. Read sub-agents/frame-worker.md and ../hyperframes-core/references/subagent-dispatch.md. Dispatch one frame-worker per frame, in parallel where possible (otherwise in waves). Each worker gets exactly one frame and this context:

PROJECT_DIR: <abs path>
frame_id: <NN-frame_id>              # = the frame file stem, e.g. 02-f2; the composition id
Your block: the `## Frame N — <frame_id>` block in PROJECT_DIR/STORYBOARD.md
audiomap: PROJECT_DIR/audiomap.json
frame.md: PROJECT_DIR/frame.md
Materials: for each group, <SKILL_DIR>/references/templates/<id>/index.html (templates) and
           <SKILL_DIR>/references/motion-primitives/<id>/ (free); staged assets/ (asset groups)
Contracts: ../hyperframes-core/references/sub-compositions.md + determinism-rules.md
Canvas: <w>×<h>   Pacing: <beat_cut|phrase_flow>
Write to: PROJECT_DIR/compositions/frames/<frame_id>.html

The worker forks the cited materials, converts every anchor to frame-local seconds (local_t = track_t span_sec[0]), gates its groups with 0ms cuts, and writes one seek-safe frame file. The worker never runs the hyperframes CLI — those commands operate on the assembled project, which doesn't exist yet, so they'd report on the wrong files. The worker just writes to the contract and stops; you verify after assembly (Step 6). As each worker returns, you can confirm its file landed on disk.

Gate: every frame has its compositions/frames/NN-*.html on disk.


Step 5: Assemble

Goal: Wire the built frames + BGM into the playable index.html.

assemble-index.mjs is deterministic — no subagent, no judgment. It references each frame file at its cumulative data-start, mounts assets/bgm.mp3 on track 11, and hard-cuts frame → frame (frames tile the track with no gaps, so there is no transition injector).

node <SKILL_DIR>/scripts/assemble-index.mjs --storyboard "$PROJECT_DIR/STORYBOARD.md" \
  --hyperframes "$PROJECT_DIR" --audiomap "$PROJECT_DIR/audiomap.json"

Fix any it reports — a missing or blank frame file means that worker wrote a partial file; re-dispatch it (Step 4) and re-assemble.

Gate: index.html exists; total duration == audiomap.audio.duration_sec.


Step 6: Verify and render

Goal: Verify the assembled video, get user approval, and render the final MP4.

Run the CLI on the assembled project — that's the correct unit (the per-frame workers couldn't run it). check runs structural lint and the headless-browser runtime, layout, motion, and contrast gate in one pass; --snapshots also emits the review frames.

( cd "$PROJECT_DIR" && npx hyperframes check . --snapshots )

Inspect at t=0, each frame start, the strongest DROP / SURGE, every hard_stops[].t, and the final frame. On failure, make the cheapest safe fix yourself: edit the offending compositions/frames/NN-*.html. Never change duration or audio timing to hide a sync issue. Once the gates pass, pause for user review, then render only on approval (autonomous mode: ask the one kept question — "preview first, or render?" — then deliver the MP4 with the contact sheet):

( cd "$PROJECT_DIR" && npx hyperframes render . --skill=music-to-video -q draft -o renders/video.mp4 --fps 30 )

Gate: check passed and the snapshots were inspected; the user approved (autonomous: checks passed and the delivery includes the contact sheet); renders/video.mp4 exists with audio, duration == audiomap.audio.duration_sec. The final reply states the MP4 path and duration.


Resume table

You have Continue from
assets/bgm.mp3 only Step 1
audiomap.json Step 2
STORYBOARD.md (skeleton) Step 3
STORYBOARD.md (complete) Step 4
all frame files Step 5
index.html Step 6

Quick Reference

Formats: landscape 1920x1080 by default; portrait 1080x1920; square 1080x1080. Set the canvas once in the storyboard frontmatter (canvas: { w, h, fps }).

Scripts under scripts/: analyze-beatgrid.py (the one analyzer), validate-plan.mjs (plan check), assemble-index.mjs (index assembly), stage-assets.mjs (stage user media), lib/storyboard.mjs (vendored parser). Everything else is the hyperframes CLI.

Read When
references/frame-skeleton.md Step 2: read the music, lay out the frames, set pacing
references/planning.md · storyboard-format.md Step 3: pick the brand, fill each frame, write the plan
references/template-catalog.md Step 3: pick a template per group
references/motion-primitive-catalog.md Step 3/4: L0 recipes for free-compose
references/montage.md Step 3/4: asset treatments (beat-cut / ken-burns)
sub-agents/frame-worker.md Step 4: dispatch + build one frame
../hyperframes-core/references/subagent-dispatch.md Step 4: dispatch sub-agents safely
../hyperframes-creative/references/design-spec.md Step 3: pick the preset (the brand)

Directory layout

music-to-video/
  SKILL.md
  references/   frame-skeleton.md · planning.md · storyboard-format.md
                template-catalog.md · motion-primitive-catalog.md · montage.md
                templates/<id>/          { index.html (+ assets/ · program.json) }  ← L1 catalog impls
                motion-primitives/<id>/  { index.html } (+ ../assets/gsap.min.js shared by recipes) ← L0 catalog impls
  scripts/      analyze-beatgrid.py · assemble-index.mjs · validate-plan.mjs · stage-assets.mjs · lib/storyboard.mjs
  sub-agents/   frame-worker.md   ← the one subagent (one per frame)