poster-grid-mv

Verified@siosan/poster-grid-mv
Use it in Cohub

Paste this into your agent's chat — it fetches the skill and follows it.

Use the skill: poster-grid-mv. Run `npx -y @neta-art/neta-search-cli@latest skills use @siosan/poster-grid-mv --via site` and follow what it prints. Read its complete output, redirecting it to a temporary file first if necessary, and resolve relative paths from the skill directory it prints.
Use in Neta Studio Open it in Neta Studio and see what people made with it.
Install from a terminal
npx -y @neta-art/neta-search-cli@latest skills use @siosan/poster-grid-mv
Summary

Turn a clip, picture, card or scene into a square beat-synced grid music video, ready to play and download.

  • Start from a video, a picture, a character or place card, or a described scene. Without a video, a 10-second clip is made first.
  • A thin grey grid cuts the footage into cells, yet it still reads as one picture. Centre cells play the clip, outer cells flash, show icons and big text on the beat.
  • Three looks: Poster Grid (cells vary on their own), Style Infection (effects spread between cells), Temporal Mosaic (echoes, stutters, reverse, freeze). Pick one or get all three.
  • Use your own track and the video runs exactly as long as it. With no song, music is made to fit the footage, 10 to 30 seconds, with the clip's sound underneath.
  • You get a square video page in your world with one tab per look and a download link. Footage with a clear subject and lively motion works best.
SKILL.md
# Poster Grid Music Video

One video clip in (no clip? a picture, a card in the world or a described scene is first turned into a 10-second clip), plus the user's music if they have it; out comes a square music video. With the user's track it is exactly as long as the track. Without one, a BGM is made for this footage — its genre, tempo and mood read from the frames — and the video is as long as the footage (10–30 s), with the clip's own sound kept underneath. A thin grey grid splits the frame, the footage stays one continuous image the viewer always recognizes, and every cell reacts to the music, each in its own way. The video is delivered on a page in the world, one tab per look.

Read this file fully before running anything or writing files. Resolve relative paths from this skill's directory; below, `$SK` is that directory.

## Language

Reply in the user's language, whatever language this file and the script output are in. This holds for every message of the run, above all the final handover: translate what the scripts print. The BGM brief is written in English (it goes to the music model, not to the user). Text the user sees in the deliverable (Work title, page title, tabs, buttons, and the title and words drawn in the video) follows the user's language. Names and text the user typed stay as they wrote them.

## When to use / when not

Use when the user wants a grid music video from footage, with or without a music track: a video they supply, or, when they have none, a picture, a character or place card in this world, or a scene they describe, which this skill first turns into one short clip. Pasting one of the three "poster-grid music video" prompts counts.

Not for:
- A plain generated video with no grid edit: say this skill makes grid music videos, and stop.
- A song with lyrics or vocals, or music without a grid video. The BGM made here is an instrumental score for this one video; it is not a song-writing tool.
- Ordinary edits with no grid (trim, subtitles, a single filter): say this skill makes grid music videos, and stop.

## Inputs

| Item | Required | Counts as given when |
|---|---|---|
| Footage | yes, one of the four rows below | Checked in this order; the first that applies is the route. |
| ↳ a video | | A video file is attached or named (path in the message, a file in `uploads/`), or the user names a video Work in this world. "My clip" with exactly one video in `uploads/` counts. No generation, no credits. |
| ↳ a picture | | No video, but an image is attached, named, newly uploaded, or is an image Work the user names. It is animated as the first frame of the clip (two pictures → first and last frame). |
| ↳ a card | | No video or picture, but the user names a character or place card in this world ("make one with <name>"). Its cover is the look reference. |
| ↳ a scene | | Nothing visual, but the user describes who / where / what happens ("a samurai cuts through rain"). Text-to-video. |
| Music | no (default: a BGM made for this footage) | An audio file is attached or named, or the user names an audio Work in this world → that track, nothing is generated. The user says to use the clip's own sound → `--audio source`, nothing is generated. Otherwise — including a song title alone, which has no file to analyse — step 0b makes a BGM (one music generation). Missing music is never a reason to ask. |
| Look | no (default `poster`) | Pick it from the user's words; see the table below. |
| Title, ticker, glyphs, colours | no | Taken from what the footage shows and how the music sounds (`references/creative.md`), in the user's language. |
| Music section | no (whole track) | Only with the user's track: when they name start/end times, or the track is over 240 s. A generated BGM is cut automatically. |
| Clip sound under the BGM | no (0.35) | Only with a generated BGM. "Keep the dialogue loud" → `--clip-gain 0.6`; "music only" → `--clip-gain 0`. |

| User says | `modes` |
|---|---|
| poster grid, independent cell variations, prompt 1, nothing specific | `["poster"]` |
| style infection, waves spreading between cells, chain reactions, prompt 2 | `["infection"]` |
| temporal, time offsets, echoes, stutter, reverse, freeze, prompt 3 | `["temporal"]` |
| all three, every version, compare them | `["poster", "infection", "temporal"]` |

If there is no footage of any of the four kinds, ask once in one message: upload a video or a picture, name a card (list up to five character or place cards from `world/`), or describe a scene. Then stop. Once there is footage, do not ask anything and do not wait for a go-ahead: build through to delivery, scoring it yourself when there is no music. When a choice is unclear (the look, a title), take the default, say in one line which one you took, and continue.

To find the files: check the paths in the user's message, then `ls uploads/`, then `node /mods/neta/neta work list` (a video or audio Work's file is the first entry of `media` in `works/<id>.json`, relative to that file).

## Steps

`$RUN` is the run folder, default `_runs/poster-grid-mv/<YYYYmmdd-HHMMSS>/` under the workspace root. If the user or the environment asks for outputs in a particular folder, use that folder as `$RUN`. Pass the same `$RUN` to every script and never move, rename or delete it afterwards.

0. **Source clip (only without a video).** Say in one line that a short clip is being made first (one video generation, about 10 s). Then:
   ```bash
   python3 $SK/scripts/footage.py --root $RUN --image <picture>[,<picture2>] [--scene "<what happens, in the user's words>"] --title "<subject> · <'grid MV footage' in the user's language>"
   python3 $SK/scripts/footage.py --root $RUN --card world/<dir>/<name>.md [--scene "…"] --title "…"
   python3 $SK/scripts/footage.py --root $RUN --scene "<who, where, what happens>" --title "…"
   ```
   Give this tool call a timeout of at least 480 s. It uses the user's selected video model first and falls back to the next available one (MiniMax H3 → Seedance 2.0 Fast → Seedance 2.0) when a model refuses the submit; for each model it reads the declaration and picks 10 s, the lowest resolution ≥ 720p, a square frame, and sound. Once a submit is accepted it keeps the task in `$RUN/source-clip.json` and never submits again. `"status":"generating"` means run the exact same command again: it only polls and never submits a second generation. `"status":"ready"` prints `video`; use it as `<footage>` below. Without user music, step 0b scores it (its own generated sound goes under the BGM). `"status":"busy"`: every model was busy and nothing was charged. Don't ask the user or offer other routes; run the same command again after a minute or two. `"status":"blocked"`: credits or permission. Tell the user plainly and stop. `"status":"failed"`: tell the user and stop; offer one more try (a new paid generation) and do not run it unasked. If `fell_back_from` is set in the `ready` output, say in one line which model made the clip (its price can differ).
0b. **Soundtrack (only without the user's music and without `--audio source`).** The BGM is made to fit the footage, so look first:
   ```bash
   python3 $SK/scripts/bgm.py --video <footage> --root $RUN
   ```
   It prints `"status":"describe-bgm"` and writes `look` (12 frames of the footage in order). Open it and write one music brief in English, under 200 characters: genre, tempo in BPM, mood, lead instruments and how the energy moves (a build, a hit), matched to what the footage shows and how fast it moves; end with "instrumental, no vocals". Pace matters most: fast action, cuts and running → 120–150 BPM; slow, wide or quiet shots → 70–95 BPM. Do not ask the user for it. Say in one line that a BGM is being made (one music generation), then:
   ```bash
   python3 $SK/scripts/bgm.py --video <footage> --root $RUN --brief "<brief>" --title "<subject> · <'BGM' in the user's language>" [--clip-gain 0.35]
   ```
   Give this tool call a timeout of at least 480 s. It submits one generation (the user's selected music model first, falling back on a busy or refusing one), keeps the task in `$RUN/bgm-task.json` and never submits again. From the returned track it keeps the strongest stretch as long as the footage (at least 10 s, at most 30 s), starting on a downbeat, mixes the footage's own sound underneath, and prints `"status":"ready"` with `audio` (`$RUN/soundtrack.m4a`): pass that as `--audio` below. `generating` → run the same command again (it only polls). `busy` → nothing was charged; run it again after a minute or two, without asking the user. `blocked` → credits or permission: tell the user plainly and stop. `failed` → tell the user, offer one more try or a music file of their own, and stop. The full track is saved in the world as an audio Work. If `fell_back_from` is set, say which model made it.
1. **Prepare.** Probe both inputs, analyse the beat, write defaults:
   ```bash
   python3 $SK/scripts/prepare.py --video <footage> --audio <music, $RUN/soundtrack.m4a, or source> --root $RUN --lang <user language code>
   ```
   Add `--music-start/--music-end` only for a section the user named. Prints music length, BPM, sections, and writes `$RUN/contact.jpg`, `analysis.json`, `footage.json`, `config.json`. A non-zero exit prints the reason. Relay it in plain words and stop if it is about the user's files (no video stream, no audio, track over 240 s: ask for a section).
2. **Decide the look.** Read `references/creative.md`, look at `$RUN/contact.jpg`, and write into `$RUN/config.json`: `modes`, `lang`, `title`, `ticker`, `glyphs`, `shout`, `palette`, `icons`, `focus_x/focus_y`, `center`, `intensity`. Put what the user asked for first. Build only from this file.
3. **Preview stills** (fast, no video), one per mode:
   ```bash
   python3 $SK/scripts/render.py --root $RUN --mode <mode> --stills 8
   ```
   Add `--at 8.0,16.5` to see chosen moments instead of evenly spaced ones. Look at `$RUN/qa/stills-<mode>.jpg`. The subject must be recognizable in every still, the glyphs and ticker readable, and the accent colours visible against the footage. If not, fix `config.json` (focus, palette, intensity) and redo this step once.
4. **Render and QA** every mode in `modes`:
   ```bash
   nohup bash $SK/scripts/run.sh $RUN > /dev/null 2>&1 &
   ```
   Then wait: poll every 30–60 s until `$RUN/qa/DONE` or `$RUN/qa/FAILED` exists. It takes about 2–3 minutes per mode for 40 s of music at 1080 px on 4 cores. Tell the user once that it is rendering; do not report every poll. `run.sh` renders `out/mv-<mode>.mp4` and runs `check.py`, which writes `$RUN/qa/report.json`. It passes when every video is square, matches the soundtrack length to one frame, has audio, shows the grey grid, keeps the footage readable, is dense but never buried, and has no long flashes.
   On `FAILED`, read `$RUN/qa/run.log`, apply the hint it prints (usually `intensity`), and run `run.sh` again, once. Still failing: tell the user what failed and stop. Do not hand over an unchecked video.
   Look at `$RUN/qa/contact-<mode>.jpg` before delivering.
5. **Deliver** as below.

## Deliver

A **web Work**: a square player page with the video(s), one tab per look, a download link.

1. Slug: `grid-mv-<short-subject>` in kebab-case; `ls works/webs/` first and pick a free one. Title: what the video is, in the user's language (e.g. the subject plus "grid music video" in their words).
2. `node /mods/neta/neta work init web works/webs/<slug> --title "<title>"`
3. `python3 $SK/scripts/package.py --root $RUN --out works/webs/<slug> --title "<title>" [--credit "<footage / music credit>"]`. Pass `--credit` when the user gave one, or when the footage or music carries a licence that asks for attribution (CC-BY). It refuses if QA did not pass. For a language its dictionary lacks it prints a note. Then write a `labels.json` with the same keys in the user's language and re-run with `--labels`.
4. `node /mods/neta/neta work publish web works/webs/<slug> --title "<title>"`. If it returns `world_slug_required`, the world has no public address: ask the user once whether to give it one (suggest a readable kebab-case slug from the world's name), and stop. Only on their yes, run `node /mods/neta/neta world slug set <slug>` and publish again.
5. `node /mods/neta/neta work thumbnail set works/webs/<slug> --file works/webs/<slug>/cover.png` so the card shows a grid frame instead of the play button.
6. Place the card on the Board: `node /mods/neta/.agents/skills/board-ops/scripts/place.mjs --work works/webs/<slug>`.
7. Hand over with an `interaction-components` card targeting the Work (id from the publish output). Then two lines at most: the look(s) inside, the length, and the soundtrack — the user's track, or the BGM made for this footage (its style in a few words; the full track is saved in the world as its own audio Work). Remind the user that you cannot hear the result, so they should play it with sound on.

Before handing over, confirm `works/webs/<slug>/media/` holds one `mv-<mode>.mp4` per mode and `meta.json` says `ready`.

## Out of scope

- Do not change world settings (WORLD.md config, visual style, slug) except the slug on the user's explicit yes in Deliver step 4. Do not delete files that existed before the run.
- Do not modify the user's footage or music files, memory, character cards, installed scripts, existing Works or roleplays. Re-renders write a new run folder or overwrite only this run's `out/`.
- Make only the video page asked for; no extra posters, materials or Works, and do not act on your own next-step suggestions.
- Generate nothing but the one source clip of step 0 (only through `footage.py`, only when no video was given) and the one BGM of step 0b (only through `bgm.py`, only when the user gave no music and did not ask for the clip's own sound). Never call `neta generate` yourself. No songs with vocals, no extra images, no lyric captions, no other aspect ratios in this version.

## When to stop

- No footage of any kind (no video, picture, card or scene): ask once, then stop. Never pick a card or invent a scene on your own. Missing music is not a stop: step 0b scores the footage.
- The source clip or the BGM generation failed (`failed` or `blocked`): say so and stop; a retry only on the user's yes (for the BGM, also offer a music file of their own). `busy` is not a failure: rerun the same command.
- The user's track is longer than 240 s: ask which section to use, then stop.
- QA still failing after one retry: say which check failed, and stop.

## Gotchas

- The grid lines are drawn on even pixels, two pixels wide, so each line fills whole 2×2 colour blocks of the yuv420p video. Lines that straddled two blocks took the colour of the footage beside them and failed the `grid` check on saturated footage — a green map scored 0.24–0.49 whatever `intensity` or palette was used, and the run stopped without a video. If `grid` still fails, the hint is real: lower `intensity`.
- `footage.py` is the only way a clip is generated here. A generation is paid when submitted: after a timeout or a cut-off chat, rerun the same command (it polls the saved task); never run a video generation yourself and never start a new run folder to retry.
- The generated clip is an intermediate: it is saved as a video Work in the world but is not the deliverable and is not placed on the Board. Only the grid MV page is handed over.
- `bgm.py` is the only way music is generated here, and it follows the same rule: after a timeout, rerun the same command with the same `$RUN`; it polls the saved task and never submits a second BGM. Once `soundtrack.json` exists it returns at once (a new `--clip-gain` only re-mixes).
- Write the brief from the frames, not from the title. A brief that ignores what is on screen (a calm lakeside given drum and bass) makes the grid fight its own music.
- The music model returns a full song (often 2–3 minutes). Do not hand that whole track to `prepare.py`: always pass `soundtrack.m4a`, which is cut to the footage. With the BGM, the video length follows the footage (10–30 s); a clip shorter than 10 s loops on downbeats, a clip longer than 30 s uses its first 30 s (`source_start` picks another part).
- The BPM `prepare.py` measures on the cut can differ from the brief by a few BPM; that is the real tempo and the cuts follow it.

- Beat tracking uses log-spaced bands plus a low-end boost. A plain spectral flux locked onto off-beat hi-hats in tests. If the stills look off the beat, check `bpm` in `analysis.json` against the track; a half or double tempo is harmless.
- The video length comes from the soundtrack `prepare.py` analysed (the user's track, or `soundtrack.m4a`, already cut to the footage). Do not trim the output by hand: `check.py` compares it with the analysed length.
- The first `render.py` call decodes the footage into a frame cache in the system temp folder; later calls reuse it. That first call is the slow one.
- Fonts: Latin HUD text uses the bundled JetBrains Mono, Latin glyphs and shout letters use the bundled Archivo Black (both OFL, `assets/fonts/`), and CJK uses the system Noto Sans CJK. The renderer splits mixed text into per-script runs, so a line like `SINTEL // 龙之少女` uses both fonts. Do not swap in other fonts or add outlines to text: outlined type was the first thing a creator called ugly.
- The palette is the look. Colour-negative or hue-swap effects on warm footage turn cells bright blue or green and look cheap, which is why every treatment maps into ink, paper, accent and accent2. Keep accents that come from the footage.
- Drum & bass and trap often analyse at half tempo. `prepare.py` doubles the tempo when the 8ths carry enough energy. If `bpm` is still half what the user says, the visuals stay in sync but the BPM readout shows half.
- Footage cut from a trailer or an edit has fades to black and letterboxing. Both are handled (see `references/creative.md`), so do not pre-process the user's file.
- Keep each glyph to one character and each shout word to one grid row. A cell is about 1/6 of the frame.
- `package.py` refuses to run without a passing `qa/report.json`. Fix the render; do not edit the report.