Motion-graphics edit + editing interface
Talk to the user in their language. Write captions in the speaker's exact words (never paraphrase).
Paths: ME = this skill folder, W = one project folder per video. All scripts are bash/python/node; scripts/env.sh finds ffmpeg, node, python, Chrome and the bundled engine (engine/). First time: see README.md → Installation (npm install in the skill folder, pip install -r requirements.txt).
Rules (non-negotiable)
- Official logos only for any brand mentioned — never a redrawn "inspired" icon. Get it from the brand's website (
capture_site.js) or ask the user. - Brand charter: for a client, read colours, fonts and logo from their website (
captures/info.json) and write the palette at the top ofparts/top.js. For the user's own videos, ask for their charter once and keep it intheme.json+top.js. - Website captures: capture the client's site (full pages, desktop + mobile) and use them in panels (scrolling browser, cropped official illustrations). Cookie banners are hidden, never accepted. Logged-in product screens are not reachable: say so and offer to integrate screenshots the user provides.
- Rhythm: full screen ↔ panels (zoom OUT in, zoom IN out), punch-ins on strong words (~1 every 2-3 s), exactly one signature moment (logo / name reveal), background music ducked under the voice, sound effects (whoosh on every zoom, pops, clicks, typing, impact on the reveal), light flash on jump cuts.
- Paid ads: nothing important in the top 14 % (y < 270) nor below y ≈ 1250 (Reels/TikTok UI); captions raised (
CAPDY); no captions inside panels (the panel shows the words as titles); end card = brand logo + URL + the speaker's words; no invented claims or numbers. - Captions never on the face. LTR languages →
"sens": "ltr"inprojet.json(the build fixes word order); RTL (Arabic) →"rtl". Brand name as a pill (fx: "pill", colours inprojet.json → pill). - Mix: the user sets voice / music / effects in the editor. Defaults: voice 100 %, music 46 %, effects 36 %. Always loudness-normalise to -14 LUFS.
- Every animation is a registry ELEMENT (see below) so the user can select, shift, hide and comment it. No animation outside the registry.
- Anchors: every
wS(card, word)must point at the word the animation illustrates — runscripts/ancres.pyand read the table; never guess an index.
Workflow
- Prepare:
bash $ME/scripts/preparer.sh <video> <W> <lang> "<Whisper prompt: proper nouns>"→ copied source, full-frame crop, frames, likely cuts, word-level transcript. Then the person cut-out in the background (~1 s/frame; don't run Whisper at the same time — RAM):python $ME/engine/mask.py u2net_human_seg <W> && python $ME/engine/make_head.py <W>. - Client (if any):
node $ME/scripts/capture_site.js <url> <W>→captures/,assets/logo-*.png, colours/fonts. Look at the captures; crop useful visuals intoW/assets/(illustrations, a long page for a scrolling browser). - Captions: write
W/cartes.json(exact words + Whisper timings; 2-5-word cards;hot,fx) →python $ME/scripts/cartes.py <W>. Template:templates/cartes.EXAMPLE.json. - Edit plan: split the script into beats (hook, problem, product, proof, CTA…), choose for each: full screen or panel, which concrete object to show, one signature moment, sounds. Write
W/plan-montage.md(templatetemplates/plan.EXAMPLE.md) and show it briefly to the user before animating. - projet.json (template
templates/projet.EXAMPLE.json):nom,titre,langue,sens,outro,vol,musique_gain,pill,coupes,cadrage(shots that go into a panel → measuring window),cadrage_forceif the mask catches the background. Thenpython $ME/scripts/framing.py <W>. - Scenes in
W/parts/:top.js←templates/top.EXAMPLE.js(palette,CUTS/SEGB,PANELS, zoomsZP,CAPDY);primitives.js←templates/primitives.js;gfx.js←templates/gfx.EXAMPLE.js(SCENES_ED, oneEL(...)per animation, layers, end card, fonts). - Sounds:
W/sons.json(templatetemplates/sons.EXAMPLE.json):musique{bpm, intro_filtree, coupure = silence before a reveal, accords} andevents=[element|null, time|"fin+x", "sound[:param]", gain, [repeat, step]]. Sounds: pop, bulle, clic, shutter, succes, buzz, tick, touche, impact, tape, whoosh_in:dur, whoosh_out:dur, ding:freq:dur, shimmer:dur, riser:dur, souffle:dur. Link each sound to its element so it follows when the element is shifted or hidden. - Anchors:
python $ME/scripts/ancres.py <W>. - Previews:
python $ME/scripts/build.py <W>thennode $ME/engine/04_render_frames.js <W> preview t1 t2 …→ contact sheet → review like a demanding editor (readable? face clear? ad safe zones?) and fix before rendering. - Render:
bash $ME/scripts/rendu.sh <W>(background, 3-6 min) →<nom>.mp4(+.srt/.txt), stems inmixage/pistes/, editor media. Check 6-8 frames of the MP4, -14 LUFS, size. - Editor: run
python $ME/scripts/editor_server.py <W> <port>(in Claude Code desktop: add it to.claude/launch.jsonand use the preview tool), openhttp://localhost:<port>/editeur/. Verify the preview, click-to-select and playback work. - Watch comments: monitor
W/comments.jsonand emit one line per new comment withstatus == "à faire"(command inreferences/editor.md).
Processing a comment
- Read it (
target=el:<id>|scene:<id>|cap:<n>|audio:<track>,t, clickx,y). - Set status "en cours":
POST /api/comments/status {"id":…, "status":"en cours"}. - Change the element in
parts/gfx.js(ortop.jsfor zooms/panels,cartes.jsonfor captions,sons.jsonfor sounds), staying within the charter and the registry. If the request is vague, apply the best option and mention an alternative in the reply. python $ME/scripts/build.py <W>(+sons.pyif sounds change) → the editor reloads its preview by itself. Check 1-2 preview frames.- Status "fait" + a short
reply(what changed) — shown in the editor. Remind the user they can click "Rendre la vidéo". - A general preference ("I never want X") → add it to the Rules of this file.
Element registry (contract with the editor)
EL(id, name, scene, layer, [x,y,w,h], ()=>[start,end], t=>{…draw…})
layer:'panel'(drawn inside the panel, behind the popping head) ·'full'(full screen, in front) ·'none'(global: flashes, end card).treceived = time shifted by the editor (EDITS.off); draw only inside your window; hidden ifEDITS.hide[id].- The box
[x,y,w,h]is used for clicking in the preview: keep it accurate. SCENES_ED= scenes listed on the left of the editor (id,label,a,b,kind).- Zooms: each
ZPentry is elementzoom<i>(can be removed in the editor).
Project files
projet.json · cartes.json → caps.json (caps.orig.json = editor base) · sons.json · parts/{top,primitives,gfx}.js · framing.js · edits.json (editor settings: off, hide, caps, vol) · comments.json · captures/, assets/ · mixage/pistes/ · editeur/ · <nom>.mp4/.srt/.txt.